Move edit controls into map controls and add keyboard undo shortcuts

This commit is contained in:
Mitchell McCaffrey 2021-07-21 17:12:49 +10:00
parent 6d5293eeee
commit 20f48f173e
6 changed files with 113 additions and 109 deletions

View File

@ -13,6 +13,7 @@ import NetworkedMapPointer from "../../network/NetworkedMapPointer";
import SelectTool from "../tools/SelectTool"; import SelectTool from "../tools/SelectTool";
import { useSettings } from "../../contexts/SettingsContext"; import { useSettings } from "../../contexts/SettingsContext";
import { useUserId } from "../../contexts/UserIdContext";
import Action from "../../actions/Action"; import Action from "../../actions/Action";
import { import {
@ -41,10 +42,12 @@ import {
import useMapTokens from "../../hooks/useMapTokens"; import useMapTokens from "../../hooks/useMapTokens";
import useMapNotes from "../../hooks/useMapNotes"; import useMapNotes from "../../hooks/useMapNotes";
import { MapActions } from "../../hooks/useMapActions";
type MapProps = { type MapProps = {
map: MapType | null; map: MapType | null;
mapState: MapState | null; mapState: MapState | null;
mapActions: MapActions;
onMapTokenStateChange: TokenStateChangeEventHandler; onMapTokenStateChange: TokenStateChangeEventHandler;
onMapTokenStateRemove: TokenStateRemoveHandler; onMapTokenStateRemove: TokenStateRemoveHandler;
onMapChange: MapChangeEventHandler; onMapChange: MapChangeEventHandler;
@ -54,11 +57,7 @@ type MapProps = {
onMapNoteCreate: NoteCreateEventHander; onMapNoteCreate: NoteCreateEventHander;
onMapNoteChange: NoteChangeEventHandler; onMapNoteChange: NoteChangeEventHandler;
onMapNoteRemove: NoteRemoveEventHander; onMapNoteRemove: NoteRemoveEventHander;
allowMapDrawing: boolean;
allowFogDrawing: boolean;
allowMapChange: boolean; allowMapChange: boolean;
allowNoteEditing: boolean;
disabledTokens: Record<string, boolean>;
session: Session; session: Session;
onUndo: () => void; onUndo: () => void;
onRedo: () => void; onRedo: () => void;
@ -67,6 +66,7 @@ type MapProps = {
function Map({ function Map({
map, map,
mapState, mapState,
mapActions,
onMapTokenStateChange, onMapTokenStateChange,
onMapTokenStateRemove, onMapTokenStateRemove,
onMapChange, onMapChange,
@ -76,17 +76,15 @@ function Map({
onMapNoteCreate, onMapNoteCreate,
onMapNoteChange, onMapNoteChange,
onMapNoteRemove, onMapNoteRemove,
allowMapDrawing,
allowFogDrawing,
allowMapChange, allowMapChange,
allowNoteEditing,
disabledTokens,
session, session,
onUndo, onUndo,
onRedo, onRedo,
}: MapProps) { }: MapProps) {
const { addToast } = useToasts(); const { addToast } = useToasts();
const userId = useUserId();
const [selectedToolId, setSelectedToolId] = useState<MapToolId>("move"); const [selectedToolId, setSelectedToolId] = useState<MapToolId>("move");
const { settings, setSettings } = useSettings(); const { settings, setSettings } = useSettings();
@ -130,42 +128,12 @@ function Map({
onFogDraw(new EditStatesAction(shapes)); onFogDraw(new EditStatesAction(shapes));
} }
const disabledControls: MapToolId[] = [];
if (!allowMapDrawing) {
disabledControls.push("drawing");
}
if (!map) {
disabledControls.push("move");
disabledControls.push("measure");
disabledControls.push("pointer");
disabledControls.push("select");
}
if (!allowFogDrawing) {
disabledControls.push("fog");
}
if (!allowMapChange) {
disabledControls.push("map");
}
if (!allowNoteEditing) {
disabledControls.push("note");
}
const disabledSettings: {
drawing: string[];
} = {
drawing: [],
};
if (drawShapes.length === 0) {
disabledSettings.drawing.push("erase");
}
const { tokens, tokenMenu, tokenDragOverlay } = useMapTokens( const { tokens, tokenMenu, tokenDragOverlay } = useMapTokens(
map, map,
mapState, mapState,
onMapTokenStateChange, onMapTokenStateChange,
onMapTokenStateRemove, onMapTokenStateRemove,
selectedToolId, selectedToolId
disabledTokens
); );
const { notes, noteMenu, noteDragOverlay } = useMapNotes( const { notes, noteMenu, noteDragOverlay } = useMapNotes(
@ -175,7 +143,7 @@ function Map({
onMapNoteChange, onMapNoteChange,
onMapNoteRemove, onMapNoteRemove,
selectedToolId, selectedToolId,
allowNoteEditing !!(map?.owner === userId || mapState?.editFlags.includes("notes"))
); );
return ( return (
@ -188,15 +156,15 @@ function Map({
<MapControls <MapControls
onMapChange={onMapChange} onMapChange={onMapChange}
onMapReset={onMapReset} onMapReset={onMapReset}
currentMap={map} map={map}
currentMapState={mapState} mapState={mapState}
mapActions={mapActions}
allowMapChange={allowMapChange}
onSelectedToolChange={setSelectedToolId} onSelectedToolChange={setSelectedToolId}
selectedToolId={selectedToolId} selectedToolId={selectedToolId}
toolSettings={settings} toolSettings={settings}
onToolSettingChange={handleToolSettingChange} onToolSettingChange={handleToolSettingChange}
onToolAction={handleToolAction} onToolAction={handleToolAction}
disabledControls={disabledControls}
disabledSettings={disabledSettings}
onUndo={onUndo} onUndo={onUndo}
onRedo={onRedo} onRedo={onRedo}
/> />
@ -208,7 +176,6 @@ function Map({
} }
selectedToolId={selectedToolId} selectedToolId={selectedToolId}
onSelectedToolChange={setSelectedToolId} onSelectedToolChange={setSelectedToolId}
disabledControls={disabledControls}
> >
{map && map.showGrid && <MapGrid map={map} />} {map && map.showGrid && <MapGrid map={map} />}
<DrawingTool <DrawingTool
@ -231,7 +198,10 @@ function Map({
onShapeError={addToast} onShapeError={addToast}
active={selectedToolId === "fog"} active={selectedToolId === "fog"}
toolSettings={settings.fog} toolSettings={settings.fog}
editable={allowFogDrawing && !settings.fog.preview} editable={
!!(map?.owner === userId || mapState?.editFlags.includes("fog")) &&
!settings.fog.preview
}
/> />
<NetworkedMapPointer <NetworkedMapPointer
active={selectedToolId === "pointer"} active={selectedToolId === "pointer"}

View File

@ -38,19 +38,22 @@ import { Settings } from "../../types/Settings";
import { useKeyboard } from "../../contexts/KeyboardContext"; import { useKeyboard } from "../../contexts/KeyboardContext";
import shortcuts from "../../shortcuts"; import shortcuts from "../../shortcuts";
import { useUserId } from "../../contexts/UserIdContext";
import { isEmpty } from "../../helpers/shared";
import { MapActions } from "../../hooks/useMapActions";
type MapControlsProps = { type MapControlsProps = {
onMapChange: MapChangeEventHandler; onMapChange: MapChangeEventHandler;
onMapReset: MapResetEventHandler; onMapReset: MapResetEventHandler;
currentMap: Map | null; map: Map | null;
currentMapState: MapState | null; mapState: MapState | null;
mapActions: MapActions;
allowMapChange: boolean;
selectedToolId: MapToolId; selectedToolId: MapToolId;
onSelectedToolChange: (toolId: MapToolId) => void; onSelectedToolChange: (toolId: MapToolId) => void;
toolSettings: Settings; toolSettings: Settings;
onToolSettingChange: (change: Partial<Settings>) => void; onToolSettingChange: (change: Partial<Settings>) => void;
onToolAction: (actionId: string) => void; onToolAction: (actionId: string) => void;
disabledControls: MapToolId[];
disabledSettings: Partial<Record<keyof Settings, string[]>>;
onUndo: () => void; onUndo: () => void;
onRedo: () => void; onRedo: () => void;
}; };
@ -58,21 +61,62 @@ type MapControlsProps = {
function MapContols({ function MapContols({
onMapChange, onMapChange,
onMapReset, onMapReset,
currentMap, map,
currentMapState, mapState,
mapActions,
allowMapChange,
selectedToolId, selectedToolId,
onSelectedToolChange, onSelectedToolChange,
toolSettings, toolSettings,
onToolSettingChange, onToolSettingChange,
onToolAction, onToolAction,
disabledControls,
disabledSettings,
onUndo, onUndo,
onRedo, onRedo,
}: MapControlsProps) { }: MapControlsProps) {
const [isExpanded, setIsExpanded] = useState(true); const [isExpanded, setIsExpanded] = useState(true);
const [fullScreen, setFullScreen] = useSetting("map.fullScreen"); const [fullScreen, setFullScreen] = useSetting("map.fullScreen");
const userId = useUserId();
const isOwner = map && map.owner === userId;
const allowMapDrawing = isOwner || mapState?.editFlags.includes("drawing");
const allowFogDrawing = isOwner || mapState?.editFlags.includes("fog");
const allowNoteEditing = isOwner || mapState?.editFlags.includes("notes");
const disabledControls: MapToolId[] = [];
if (!allowMapDrawing) {
disabledControls.push("drawing");
}
if (!map) {
disabledControls.push("move");
disabledControls.push("measure");
disabledControls.push("pointer");
disabledControls.push("select");
}
if (!allowFogDrawing) {
disabledControls.push("fog");
}
if (!allowMapChange) {
disabledControls.push("map");
}
if (!allowNoteEditing) {
disabledControls.push("note");
}
if (!map || mapActions.actionIndex < 0) {
disabledControls.push("undo");
}
if (!map || mapActions.actionIndex === mapActions.actions.length - 1) {
disabledControls.push("redo");
}
const disabledSettings: Partial<Record<keyof Settings, string[]>> = {
drawing: [],
};
if (mapState && isEmpty(mapState.drawShapes)) {
disabledSettings.drawing?.push("erase");
}
const toolsById: Record<string, MapTool> = { const toolsById: Record<string, MapTool> = {
move: { move: {
id: "move", id: "move",
@ -131,8 +175,8 @@ function MapContols({
<SelectMapButton <SelectMapButton
onMapChange={onMapChange} onMapChange={onMapChange}
onMapReset={onMapReset} onMapReset={onMapReset}
currentMap={currentMap} currentMap={map}
currentMapState={currentMapState} currentMapState={mapState}
disabled={disabledControls.includes("map")} disabled={disabledControls.includes("map")}
/> />
), ),
@ -155,8 +199,14 @@ function MapContols({
id: "history", id: "history",
component: ( component: (
<> <>
<UndoButton onClick={onUndo} /> <UndoButton
<RedoButton onClick={onRedo} /> onClick={onUndo}
disabled={disabledControls.includes("undo")}
/>
<RedoButton
onClick={onRedo}
disabled={disabledControls.includes("redo")}
/>
</> </>
), ),
}, },
@ -218,9 +268,10 @@ function MapContols({
const Settings = toolsById[selectedToolId].SettingsComponent; const Settings = toolsById[selectedToolId].SettingsComponent;
if ( if (
!Settings || !Settings ||
selectedToolId === "move" || (selectedToolId !== "fog" &&
selectedToolId === "measure" || selectedToolId !== "drawing" &&
selectedToolId === "note" selectedToolId !== "pointer" &&
selectedToolId !== "select")
) { ) {
return null; return null;
} }
@ -277,6 +328,12 @@ function MapContols({
if (shortcuts.noteTool(event) && !disabledControls.includes("note")) { if (shortcuts.noteTool(event) && !disabledControls.includes("note")) {
onSelectedToolChange("note"); onSelectedToolChange("note");
} }
if (shortcuts.redo(event) && !disabledControls.includes("redo")) {
onRedo();
}
if (shortcuts.undo(event) && !disabledControls.includes("undo")) {
onUndo();
}
} }
useKeyboard(handleKeyDown); useKeyboard(handleKeyDown);

View File

@ -31,7 +31,6 @@ type MapInteractionProps = {
controls: React.ReactNode; controls: React.ReactNode;
selectedToolId: MapToolId; selectedToolId: MapToolId;
onSelectedToolChange: SelectedToolChangeEventHanlder; onSelectedToolChange: SelectedToolChangeEventHanlder;
disabledControls: MapToolId[];
}; };
function MapInteraction({ function MapInteraction({
@ -41,7 +40,6 @@ function MapInteraction({
controls, controls,
selectedToolId, selectedToolId,
onSelectedToolChange, onSelectedToolChange,
disabledControls,
}: MapInteractionProps) { }: MapInteractionProps) {
const [mapImage, mapImageStatus] = useMapImage(map); const [mapImage, mapImageStatus] = useMapImage(map);
@ -126,11 +124,7 @@ function MapInteraction({
// Stop active state on move icon from being selected // Stop active state on move icon from being selected
event.preventDefault(); event.preventDefault();
} }
if ( if (map && shortcuts.move(event) && selectedToolId !== "move") {
shortcuts.move(event) &&
selectedToolId !== "move" &&
!disabledControls.includes("move")
) {
event.preventDefault(); event.preventDefault();
previousSelectedToolRef.current = selectedToolId; previousSelectedToolRef.current = selectedToolId;
onSelectedToolChange("move"); onSelectedToolChange("move");

View File

@ -18,20 +18,37 @@ import Token from "../components/konva/Token";
import { KonvaEventObject } from "konva/lib/Node"; import { KonvaEventObject } from "konva/lib/Node";
import TokenMenu from "../components/token/TokenMenu"; import TokenMenu from "../components/token/TokenMenu";
import TokenDragOverlay from "../components/token/TokenDragOverlay"; import TokenDragOverlay from "../components/token/TokenDragOverlay";
import { useUserId } from "../contexts/UserIdContext";
function useMapTokens( function useMapTokens(
map: Map | null, map: Map | null,
mapState: MapState | null, mapState: MapState | null,
onTokenStateChange: TokenStateChangeEventHandler, onTokenStateChange: TokenStateChangeEventHandler,
onTokenStateRemove: TokenStateRemoveHandler, onTokenStateRemove: TokenStateRemoveHandler,
selectedToolId: MapToolId, selectedToolId: MapToolId
disabledTokens: Record<string, boolean>
) { ) {
const [isTokenMenuOpen, setIsTokenMenuOpen] = useState<boolean>(false); const [isTokenMenuOpen, setIsTokenMenuOpen] = useState<boolean>(false);
const [tokenMenuOptions, setTokenMenuOptions] = useState<TokenMenuOptions>(); const [tokenMenuOptions, setTokenMenuOptions] = useState<TokenMenuOptions>();
const [tokenDraggingOptions, setTokenDraggingOptions] = const [tokenDraggingOptions, setTokenDraggingOptions] =
useState<TokenDraggingOptions>(); useState<TokenDraggingOptions>();
const userId = useUserId();
const disabledTokens: Record<string, boolean> = {};
// If we have a map and state and have the token permission disabled
// and are not the map owner
if (
mapState &&
!mapState.editFlags.includes("tokens") &&
map?.owner !== userId
) {
for (let token of Object.values(mapState.tokens)) {
if (token.owner !== userId) {
disabledTokens[token.id] = true;
}
}
}
function handleTokenMenuOpen(tokenStateId: string, tokenImage: Konva.Node) { function handleTokenMenuOpen(tokenStateId: string, tokenImage: Konva.Node) {
setTokenMenuOptions({ tokenStateId, tokenImage }); setTokenMenuOptions({ tokenStateId, tokenImage });
setIsTokenMenuOpen(true); setIsTokenMenuOpen(true);

View File

@ -217,7 +217,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
await loadAssetManifestFromMap(newMap, newMapState); await loadAssetManifestFromMap(newMap, newMapState);
} }
const [_, addActions, updateActionIndex, resetActions] = const [mapActions, addActions, updateActionIndex, resetActions] =
useMapActions(setCurrentMapState); useMapActions(setCurrentMapState);
function handleMapReset(newMapState: MapState) { function handleMapReset(newMapState: MapState) {
@ -360,39 +360,6 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
const canChangeMap = !isLoading; const canChangeMap = !isLoading;
const canEditMapDrawing =
currentMap &&
currentMapState &&
(currentMapState.editFlags.includes("drawing") ||
currentMap?.owner === userId);
const canEditFogDrawing =
currentMap &&
currentMapState &&
(currentMapState.editFlags.includes("fog") || currentMap?.owner === userId);
const canEditNotes =
currentMap &&
currentMapState &&
(currentMapState.editFlags.includes("notes") ||
currentMap?.owner === userId);
const disabledMapTokens: Record<string, boolean> = {};
// If we have a map and state and have the token permission disabled
// and are not the map owner
if (
currentMapState &&
currentMap &&
!currentMapState.editFlags.includes("tokens") &&
currentMap?.owner !== userId
) {
for (let token of Object.values(currentMapState.tokens)) {
if (token.owner !== userId) {
disabledMapTokens[token.id] = true;
}
}
}
return ( return (
<GlobalImageDrop <GlobalImageDrop
onMapChange={handleMapChange} onMapChange={handleMapChange}
@ -401,6 +368,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
<Map <Map
map={currentMap} map={currentMap}
mapState={currentMapState} mapState={currentMapState}
mapActions={mapActions}
onMapTokenStateChange={handleMapTokenStateChange} onMapTokenStateChange={handleMapTokenStateChange}
onMapTokenStateRemove={handleMapTokenStateRemove} onMapTokenStateRemove={handleMapTokenStateRemove}
onMapChange={handleMapChange} onMapChange={handleMapChange}
@ -410,11 +378,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
onMapNoteCreate={handleNoteCreate} onMapNoteCreate={handleNoteCreate}
onMapNoteChange={handleNoteChange} onMapNoteChange={handleNoteChange}
onMapNoteRemove={handleNoteRemove} onMapNoteRemove={handleNoteRemove}
allowMapDrawing={!!canEditMapDrawing}
allowFogDrawing={!!canEditFogDrawing}
allowMapChange={canChangeMap} allowMapChange={canChangeMap}
allowNoteEditing={!!canEditNotes}
disabledTokens={disabledMapTokens}
session={session} session={session}
onUndo={handleUndo} onUndo={handleUndo}
onRedo={handleRedo} onRedo={handleRedo}

View File

@ -9,7 +9,9 @@ export type MapToolId =
| "drawing" | "drawing"
| "measure" | "measure"
| "pointer" | "pointer"
| "note"; | "note"
| "undo"
| "redo";
export type MapTool = { export type MapTool = {
id: MapToolId; id: MapToolId;