diff --git a/src/components/controls/DrawingToolSettings.tsx b/src/components/controls/DrawingToolSettings.tsx
index b9cd167..0ac1dff 100644
--- a/src/components/controls/DrawingToolSettings.tsx
+++ b/src/components/controls/DrawingToolSettings.tsx
@@ -17,9 +17,6 @@ import BrushTriangleIcon from "../../icons/BrushTriangleIcon";
import EraseAllIcon from "../../icons/EraseAllIcon";
import EraseIcon from "../../icons/EraseToolIcon";
-import UndoButton from "./shared/UndoButton";
-import RedoButton from "./shared/RedoButton";
-
import Divider from "../Divider";
import { useKeyboard } from "../../contexts/KeyboardContext";
@@ -62,10 +59,6 @@ function DrawingToolSettings({
onSettingChange({ type: "erase" });
} else if (shortcuts.drawBlend(event)) {
onSettingChange({ useBlending: !settings.useBlending });
- } else if (shortcuts.redo(event) && !disabledActions.includes("redo")) {
- onToolAction("mapRedo");
- } else if (shortcuts.undo(event) && !disabledActions.includes("undo")) {
- onToolAction("mapUndo");
}
}
useKeyboard(handleKeyDown);
@@ -155,15 +148,6 @@ function DrawingToolSettings({
useBlending={settings.useBlending}
onBlendingChange={(useBlending) => onSettingChange({ useBlending })}
/>
-
- onToolAction("mapUndo")}
- disabled={disabledActions.includes("undo")}
- />
- onToolAction("mapRedo")}
- disabled={disabledActions.includes("redo")}
- />
);
}
diff --git a/src/components/controls/FogToolSettings.tsx b/src/components/controls/FogToolSettings.tsx
index f227d1f..79d4cc9 100644
--- a/src/components/controls/FogToolSettings.tsx
+++ b/src/components/controls/FogToolSettings.tsx
@@ -13,8 +13,6 @@ import FogRemoveIcon from "../../icons/FogRemoveIcon";
import FogToggleIcon from "../../icons/FogToggleIcon";
import FogRectangleIcon from "../../icons/FogRectangleIcon";
-import UndoButton from "./shared/UndoButton";
-import RedoButton from "./shared/RedoButton";
import ToolSection from "./shared/ToolSection";
import Divider from "../Divider";
@@ -31,16 +29,9 @@ import {
type FogToolSettingsProps = {
settings: FogToolSettingsType;
onSettingChange: (change: Partial) => void;
- onToolAction: (action: string) => void;
- disabledActions: string[];
};
-function FogToolSettings({
- settings,
- onSettingChange,
- onToolAction,
- disabledActions,
-}: FogToolSettingsProps) {
+function FogToolSettings({ settings, onSettingChange }: FogToolSettingsProps) {
// Keyboard shortcuts
function handleKeyDown(event: KeyboardEvent) {
if (shortcuts.fogPolygon(event)) {
@@ -59,10 +50,6 @@ function FogToolSettings({
onSettingChange({ useFogCut: !settings.useFogCut });
} else if (shortcuts.fogRectangle(event)) {
onSettingChange({ type: "rectangle" });
- } else if (shortcuts.redo(event) && !disabledActions.includes("redo")) {
- onToolAction("fogRedo");
- } else if (shortcuts.undo(event) && !disabledActions.includes("undo")) {
- onToolAction("fogUndo");
}
}
@@ -134,15 +121,6 @@ function FogToolSettings({
useFogPreview={settings.preview}
onFogPreviewChange={(preview) => onSettingChange({ preview })}
/>
-
- onToolAction("fogUndo")}
- disabled={disabledActions.includes("undo")}
- />
- onToolAction("fogRedo")}
- disabled={disabledActions.includes("redo")}
- />
);
}
diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx
index 25d1bb6..eb5f684 100644
--- a/src/components/map/Map.tsx
+++ b/src/components/map/Map.tsx
@@ -26,7 +26,7 @@ import Session from "../../network/Session";
import { Drawing, DrawingState } from "../../types/Drawing";
import { Fog, FogState } from "../../types/Fog";
-import { Map as MapType, MapActions, MapToolId } from "../../types/Map";
+import { Map as MapType, MapToolId } from "../../types/Map";
import { MapState } from "../../types/MapState";
import { Settings } from "../../types/Settings";
import {
@@ -45,17 +45,12 @@ import useMapNotes from "../../hooks/useMapNotes";
type MapProps = {
map: MapType | null;
mapState: MapState | null;
- mapActions: MapActions;
onMapTokenStateChange: TokenStateChangeEventHandler;
onMapTokenStateRemove: TokenStateRemoveHandler;
onMapChange: MapChangeEventHandler;
onMapReset: MapResetEventHandler;
onMapDraw: (action: Action) => void;
- onMapDrawUndo: () => void;
- onMapDrawRedo: () => void;
onFogDraw: (action: Action) => void;
- onFogDrawUndo: () => void;
- onFogDrawRedo: () => void;
onMapNoteCreate: NoteCreateEventHander;
onMapNoteChange: NoteChangeEventHandler;
onMapNoteRemove: NoteRemoveEventHander;
@@ -65,22 +60,19 @@ type MapProps = {
allowNoteEditing: boolean;
disabledTokens: Record;
session: Session;
+ onUndo: () => void;
+ onRedo: () => void;
};
function Map({
map,
mapState,
- mapActions,
onMapTokenStateChange,
onMapTokenStateRemove,
onMapChange,
onMapReset,
onMapDraw,
- onMapDrawUndo,
- onMapDrawRedo,
onFogDraw,
- onFogDrawUndo,
- onFogDrawRedo,
onMapNoteCreate,
onMapNoteChange,
onMapNoteRemove,
@@ -90,6 +82,8 @@ function Map({
allowNoteEditing,
disabledTokens,
session,
+ onUndo,
+ onRedo,
}: MapProps) {
const { addToast } = useToasts();
@@ -110,18 +104,6 @@ function Map({
if (action === "eraseAll") {
onMapDraw(new RemoveStatesAction(drawShapes.map((s) => s.id)));
}
- if (action === "mapUndo") {
- onMapDrawUndo();
- }
- if (action === "mapRedo") {
- onMapDrawRedo();
- }
- if (action === "fogUndo") {
- onFogDrawUndo();
- }
- if (action === "fogRedo") {
- onFogDrawRedo();
- }
}
function handleMapShapeAdd(shape: Drawing) {
@@ -169,33 +151,13 @@ function Map({
}
const disabledSettings: {
- fog: string[];
drawing: string[];
} = {
- fog: [],
drawing: [],
};
if (drawShapes.length === 0) {
disabledSettings.drawing.push("erase");
}
- if (!mapState || mapActions.mapDrawActionIndex < 0) {
- disabledSettings.drawing.push("undo");
- }
- if (
- !mapState ||
- mapActions.mapDrawActionIndex === mapActions.mapDrawActions.length - 1
- ) {
- disabledSettings.drawing.push("redo");
- }
- if (!mapState || mapActions.fogDrawActionIndex < 0) {
- disabledSettings.fog.push("undo");
- }
- if (
- !mapState ||
- mapActions.fogDrawActionIndex === mapActions.fogDrawActions.length - 1
- ) {
- disabledSettings.fog.push("redo");
- }
const { tokens, tokenMenu, tokenDragOverlay } = useMapTokens(
map,
@@ -235,6 +197,8 @@ function Map({
onToolAction={handleToolAction}
disabledControls={disabledControls}
disabledSettings={disabledSettings}
+ onUndo={onUndo}
+ onRedo={onRedo}
/>
{tokenMenu}
{noteMenu}
diff --git a/src/components/map/MapControls.tsx b/src/components/map/MapControls.tsx
index 5e88a9e..68dc783 100644
--- a/src/components/map/MapControls.tsx
+++ b/src/components/map/MapControls.tsx
@@ -22,6 +22,9 @@ import FullScreenExitIcon from "../../icons/FullScreenExitIcon";
import NoteToolIcon from "../../icons/NoteToolIcon";
import SelectToolIcon from "../../icons/SelecToolIcon";
+import UndoButton from "../controls/shared/UndoButton";
+import RedoButton from "../controls/shared/RedoButton";
+
import useSetting from "../../hooks/useSetting";
import { Map, MapTool, MapToolId } from "../../types/Map";
@@ -48,6 +51,8 @@ type MapControlsProps = {
onToolAction: (actionId: string) => void;
disabledControls: MapToolId[];
disabledSettings: Partial>;
+ onUndo: () => void;
+ onRedo: () => void;
};
function MapContols({
@@ -62,6 +67,8 @@ function MapContols({
onToolAction,
disabledControls,
disabledSettings,
+ onUndo,
+ onRedo,
}: MapControlsProps) {
const [isExpanded, setIsExpanded] = useState(true);
const [fullScreen, setFullScreen] = useSetting("map.fullScreen");
@@ -144,6 +151,15 @@ function MapContols({
)),
},
+ {
+ id: "history",
+ component: (
+ <>
+
+
+ >
+ ),
+ },
];
let controls = null;
diff --git a/src/network/NetworkedMapAndTokens.tsx b/src/network/NetworkedMapAndTokens.tsx
index b0e0ddb..57c06e0 100644
--- a/src/network/NetworkedMapAndTokens.tsx
+++ b/src/network/NetworkedMapAndTokens.tsx
@@ -23,12 +23,7 @@ import TokenBar from "../components/token/TokenBar";
import GlobalImageDrop from "../components/image/GlobalImageDrop";
-import {
- Map as MapType,
- MapActions,
- MapActionsIndexKey,
- MapActionsKey,
-} from "../types/Map";
+import { Map as MapType, MapActions, MapAction } from "../types/Map";
import { MapState } from "../types/MapState";
import {
AssetManifest,
@@ -41,10 +36,8 @@ import { FogState } from "../types/Fog";
import { Note } from "../types/Note";
const defaultMapActions: MapActions = {
- mapDrawActions: [],
- mapDrawActionIndex: -1,
- fogDrawActions: [],
- fogDrawActionIndex: -1,
+ actions: [],
+ actionIndex: -1,
};
/**
@@ -233,118 +226,115 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
const [mapActions, setMapActions] = useState(defaultMapActions);
- function addMapActions(
- actions: Action[],
- indexKey: MapActionsIndexKey,
- actionsKey: MapActionsKey,
- shapesKey: "drawShapes" | "fogShapes"
- ) {
- setMapActions((prevMapActions) => {
+ function applyMapActionsToState(
+ mapState: MapState,
+ actions: MapAction[]
+ ): MapState {
+ for (let mapAction of actions) {
+ if (mapAction.type === "drawings") {
+ mapState.drawShapes = mapAction.action.execute(mapState.drawShapes);
+ } else if (mapAction.type === "fogs") {
+ mapState.fogShapes = mapAction.action.execute(mapState.fogShapes);
+ } else if (mapAction.type === "tokens") {
+ mapState.tokens = mapAction.action.execute(mapState.tokens);
+ } else if (mapAction.type === "notes") {
+ mapState.notes = mapAction.action.execute(mapState.notes);
+ }
+ }
+ return mapState;
+ }
+
+ function undoMapActionsToState(
+ mapState: MapState,
+ actions: MapAction[]
+ ): MapState {
+ for (let mapAction of actions) {
+ if (mapAction.type === "drawings") {
+ mapState.drawShapes = mapAction.action.undo(mapState.drawShapes);
+ } else if (mapAction.type === "fogs") {
+ mapState.fogShapes = mapAction.action.undo(mapState.fogShapes);
+ } else if (mapAction.type === "tokens") {
+ mapState.tokens = mapAction.action.undo(mapState.tokens);
+ } else if (mapAction.type === "notes") {
+ mapState.notes = mapAction.action.undo(mapState.notes);
+ }
+ }
+ return mapState;
+ }
+
+ function addActions(actions: MapAction[]) {
+ setMapActions((prevActions) => {
const newActions = [
- ...prevMapActions[actionsKey].slice(0, prevMapActions[indexKey] + 1),
- ...actions,
+ ...prevActions.actions.slice(0, prevActions.actionIndex + 1),
+ actions,
];
const newIndex = newActions.length - 1;
return {
- ...prevMapActions,
- [actionsKey]: newActions,
- [indexKey]: newIndex,
+ actions: newActions,
+ actionIndex: newIndex,
};
});
+
// Update map state by performing the actions on it
setCurrentMapState((prevMapState) => {
if (!prevMapState) {
return prevMapState;
}
- let shapes = prevMapState[shapesKey];
- for (let action of actions) {
- shapes = action.execute(shapes);
- }
- return {
- ...prevMapState,
- [shapesKey]: shapes,
- };
+ let state = { ...prevMapState };
+ state = applyMapActionsToState(state, actions);
+ return state;
});
}
- function updateActionIndex(
- change: number,
- indexKey: MapActionsIndexKey,
- actionsKey: MapActionsKey,
- shapesKey: "drawShapes" | "fogShapes"
- ) {
- const prevIndex = mapActions[indexKey];
+ function updateActionIndex(change: number) {
+ const prevIndex = mapActions.actionIndex;
const newIndex = Math.min(
- Math.max(mapActions[indexKey] + change, -1),
- mapActions[actionsKey].length - 1
+ Math.max(mapActions.actionIndex + change, -1),
+ mapActions.actions.length - 1
);
setMapActions((prevMapActions) => ({
...prevMapActions,
- [indexKey]: newIndex,
+ actionIndex: newIndex,
}));
// Update map state by either performing the actions or undoing them
setCurrentMapState((prevMapState) => {
- if (prevMapState) {
- let shapes = prevMapState[shapesKey];
- if (prevIndex < newIndex) {
- // Redo
- for (let i = prevIndex + 1; i < newIndex + 1; i++) {
- let action = mapActions[actionsKey][i];
- shapes = action.execute(shapes as any);
- }
- } else {
- // Undo
- for (let i = prevIndex; i > newIndex; i--) {
- let action = mapActions[actionsKey][i];
- shapes = action.undo(shapes as any);
- }
- }
- return {
- ...prevMapState,
- [shapesKey]: shapes,
- };
- } else {
+ if (!prevMapState) {
return prevMapState;
}
+ let state = { ...prevMapState };
+ if (prevIndex < newIndex) {
+ // Redo
+ for (let i = prevIndex + 1; i < newIndex + 1; i++) {
+ const actions = mapActions.actions[i];
+ state = applyMapActionsToState(state, actions);
+ }
+ } else {
+ // Undo
+ for (let i = prevIndex; i > newIndex; i--) {
+ const actions = mapActions.actions[i];
+ state = undoMapActionsToState(state, actions);
+ }
+ }
+ return state;
});
-
- return newIndex;
}
function handleMapDraw(action: Action) {
- addMapActions(
- [action],
- "mapDrawActionIndex",
- "mapDrawActions",
- "drawShapes"
- );
- }
-
- function handleMapDrawUndo() {
- updateActionIndex(-1, "mapDrawActionIndex", "mapDrawActions", "drawShapes");
- }
-
- function handleMapDrawRedo() {
- updateActionIndex(1, "mapDrawActionIndex", "mapDrawActions", "drawShapes");
+ addActions([{ type: "drawings", action }]);
}
function handleFogDraw(action: Action) {
- addMapActions(
- [action],
- "fogDrawActionIndex",
- "fogDrawActions",
- "fogShapes"
- );
+ addActions([{ type: "fogs", action }]);
}
- function handleFogDrawUndo() {
- updateActionIndex(-1, "fogDrawActionIndex", "fogDrawActions", "fogShapes");
+ function handleUndo() {
+ updateActionIndex(-1);
}
- function handleFogDrawRedo() {
- updateActionIndex(1, "fogDrawActionIndex", "fogDrawActions", "fogShapes");
+ function handleRedo() {
+ updateActionIndex(1);
}
// If map changes clear map actions
@@ -562,17 +552,12 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
diff --git a/src/types/Map.ts b/src/types/Map.ts
index 73ed57e..9b0714c 100644
--- a/src/types/Map.ts
+++ b/src/types/Map.ts
@@ -3,6 +3,8 @@ import Action from "../actions/Action";
import { DrawingState } from "./Drawing";
import { FogState } from "./Fog";
import { Grid } from "./Grid";
+import { Notes } from "./Note";
+import { TokenStates } from "./TokenState";
export type MapToolId =
| "map"
@@ -59,18 +61,21 @@ export type FileMap = BaseMap & {
export type Map = DefaultMap | FileMap;
-export type MapActions = {
- mapDrawActions: Action[];
- mapDrawActionIndex: number;
- fogDrawActions: Action[];
- fogDrawActionIndex: number;
+export type DrawingsAction = {
+ type: "drawings";
+ action: Action;
};
+export type FogsAction = { type: "fogs"; action: Action };
+export type TokensAction = { type: "tokens"; action: Action };
+export type NotesAction = { type: "notes"; action: Action };
-export type MapActionsKey = keyof Pick<
- MapActions,
- "mapDrawActions" | "fogDrawActions"
->;
-export type MapActionsIndexKey = keyof Pick<
- MapActions,
- "mapDrawActionIndex" | "fogDrawActionIndex"
->;
+export type MapAction =
+ | DrawingsAction
+ | FogsAction
+ | TokensAction
+ | NotesAction;
+
+export type MapActions = {
+ actions: MapAction[][];
+ actionIndex: number;
+};
diff --git a/src/types/MapState.ts b/src/types/MapState.ts
index feae906..0caeed8 100644
--- a/src/types/MapState.ts
+++ b/src/types/MapState.ts
@@ -1,15 +1,15 @@
import { DrawingState } from "./Drawing";
import { FogState } from "./Fog";
-import { Note } from "./Note";
-import { TokenState } from "./TokenState";
+import { Notes } from "./Note";
+import { TokenStates } from "./TokenState";
export type EditFlag = "drawing" | "tokens" | "notes" | "fog";
export type MapState = {
- tokens: Record;
+ tokens: TokenStates;
drawShapes: DrawingState;
fogShapes: FogState;
editFlags: Array;
- notes: Record;
+ notes: Notes;
mapId: string;
};
diff --git a/src/types/Note.ts b/src/types/Note.ts
index 2af949b..923509d 100644
--- a/src/types/Note.ts
+++ b/src/types/Note.ts
@@ -25,3 +25,5 @@ export type NoteDraggingOptions = {
noteId: string;
noteGroup: Konva.Node;
};
+
+export type Notes = Record;
diff --git a/src/types/TokenState.ts b/src/types/TokenState.ts
index 2e8aded..7432685 100644
--- a/src/types/TokenState.ts
+++ b/src/types/TokenState.ts
@@ -33,3 +33,5 @@ export type FileTokenState = BaseTokenState & {
};
export type TokenState = DefaultTokenState | FileTokenState;
+
+export type TokenStates = Record;