From a2cc8f703ace2087d9d40bfa1d300fb0e9883233 Mon Sep 17 00:00:00 2001 From: Thomas McAuliffe Date: Fri, 17 May 2024 15:45:36 +0100 Subject: [PATCH 1/6] feat: add undo-stack on a per map basis --- hera/editor/MapEditor.tsx | 138 +++++++++++++++++++++++----- hera/editor/Types.tsx | 4 + hera/editor/lib/updateUndoStack.tsx | 12 +++ ui/Storage.tsx | 4 + 4 files changed, 136 insertions(+), 22 deletions(-) diff --git a/hera/editor/MapEditor.tsx b/hera/editor/MapEditor.tsx index 70447499..e9003e03 100644 --- a/hera/editor/MapEditor.tsx +++ b/hera/editor/MapEditor.tsx @@ -26,6 +26,7 @@ import validateMap from '@deities/athena/lib/validateMap.tsx'; import withModifiers from '@deities/athena/lib/withModifiers.tsx'; import { Biome, Biomes } from '@deities/athena/map/Biome.tsx'; import { DoubleSize, TileSize } from '@deities/athena/map/Configuration.tsx'; +import { PlainMap } from '@deities/athena/map/PlainMap.tsx'; import { Bot, HumanPlayer, PlayerID } from '@deities/athena/map/Player.tsx'; import { toTeamArray } from '@deities/athena/map/Team.tsx'; import MapData, { SizeVector } from '@deities/athena/MapData.tsx'; @@ -89,7 +90,10 @@ import useZoom from './hooks/useZoom.tsx'; import BiomeIcon from './lib/BiomeIcon.tsx'; import canFillTile from './lib/canFillTile.tsx'; import getMapValidationErrorText from './lib/getMapValidationErrorText.tsx'; -import updateUndoStack from './lib/updateUndoStack.tsx'; +import updateUndoStack, { + UNDO_STACK_INDEX_KEY, + UNDO_STACK_KEY, +} from './lib/updateUndoStack.tsx'; import ZoomButton from './lib/ZoomButton.tsx'; import MapEditorControlPanel from './panels/MapEditorControlPanel.tsx'; import ResizeHandle from './ResizeHandle.tsx'; @@ -103,15 +107,39 @@ import { PreviousMapEditorState, SaveMapFunction, SetMapFunction, + UndoStack, } from './Types.tsx'; const generateName = nameGenerator(); +export function decodeUndoStack( + encodedUndoStack: string | null, +): UndoStack | undefined { + if (!encodedUndoStack) { + return undefined; + } + + try { + return JSON.parse(encodedUndoStack).map( + ([key, value]: [string, PlainMap]) => [key, MapData.fromObject(value)], + ); + } catch { + return undefined; + } +} + +const getUndoStack = (id?: string) => + decodeUndoStack(Storage.getItem(UNDO_STACK_KEY(id))); + +const getUndoStackIndex = (id?: string) => { + const index = Storage.getItem(UNDO_STACK_INDEX_KEY(id)); + return index ? Number.parseInt(index, 10) : undefined; +}; + const startAction = { type: 'Start', } as const; -const MAP_KEY = 'map-editor-previous-map'; const EFFECTS_KEY = 'map-editor-previous-effects'; const getDefaultScenario = (effects: Effects) => @@ -125,6 +153,8 @@ const getEditorBaseState = ( mapObject: Pick | null = null, mode: EditorMode = 'design', scenario?: Scenario, + undoStack?: UndoStack, + undoStackIndex?: number, ): EditorState => { const startScenario = new Set([{ actions: [] }]); let effects: Effects = mapObject?.effects @@ -133,6 +163,7 @@ const getEditorBaseState = ( if (!effects.has('Start')) { effects = new Map([...effects, ['Start', startScenario]]); } + return { effects, isDrawing: false, @@ -143,8 +174,8 @@ const getEditorBaseState = ( selected: { tile: Plain.id, }, - undoStack: [['initial', map]], - undoStackIndex: null, + undoStack: undoStack ?? [['initial', map]], + undoStackIndex: undoStackIndex ?? null, }; }; @@ -241,7 +272,14 @@ export default function MapEditor({ usePlayMusic(map.config.biome); const [editor, _setEditorState] = useState(() => - getEditorBaseState(map, mapObject, mode, scenario), + getEditorBaseState( + map, + mapObject, + mode, + scenario, + getUndoStack(mapObject?.id), + getUndoStackIndex(mapObject?.id), + ), ); const [previousEffects, setPreviousEffects] = useState(editor.effects); @@ -319,9 +357,21 @@ export default function MapEditor({ const [previousState, setPreviousState] = useState(() => { try { + const effects = Storage.getItem(EFFECTS_KEY) || ''; + const undoStack = getUndoStack(mapObject?.id); + + if (!undoStack) { + return null; + } + + const undoStackIndex = getUndoStackIndex(mapObject?.id); + const map = undoStack[undoStackIndex ?? -1][1]; + return { - effects: Storage.getItem(EFFECTS_KEY) || '', - map: MapData.fromJSON(Storage.getItem(MAP_KEY) || ''), + effects, + map, + undoStack, + undoStackIndex, }; // eslint-disable-next-line no-empty } catch {} @@ -329,30 +379,37 @@ export default function MapEditor({ }); const setMap: SetMapFunction = useCallback( - (type, map) => { + (type, map, undoStack) => { _setMap(map); if (type !== 'cleanup') { - updateUndoStack({ setEditorState }, editor, [ - type === 'resize' - ? `resize-${map.size.height}-${map.size.width}` - : type === 'biome' - ? `biome-${map.config.biome}` - : 'checkpoint', - map, - ]); + updateUndoStack( + { setEditorState }, + undoStack ? { ...editor, undoStack } : editor, + [ + type === 'resize' + ? `resize-${map.size.height}-${map.size.width}` + : type === 'biome' + ? `biome-${map.config.biome}` + : 'checkpoint', + map, + ], + mapObject?.id, + ); } setInternalMapID(internalMapID + 1); }, - [editor, internalMapID, setEditorState], + [editor, internalMapID, setEditorState, mapObject?.id], ); const updatePreviousMap = useCallback( - (map?: MapData, editorEffects?: Effects) => { + (map?: MapData, editorEffects?: Effects, editorUndoStack?: UndoStack) => { const effects = JSON.stringify( editorEffects ? encodeEffects(editorEffects) : '', ); - Storage.setItem(MAP_KEY, JSON.stringify(map)); - setPreviousState(map ? { effects, map } : null); + + setPreviousState( + map ? { effects, map, undoStack: editorUndoStack } : null, + ); }, [], ); @@ -457,10 +514,18 @@ export default function MapEditor({ const isNew = type === 'New' || !mapObject?.id; setSaveState(null); + + const stack = editor.undoStack.map(([key, value]) => [ + key, + value.toJSON(), + ]); + if (isNew) { + const id = generateName(); createMap( { effects: editor.effects, + id, map, mapName, tags: mapObject?.id @@ -469,6 +534,15 @@ export default function MapEditor({ }, setSaveState, ); + Storage.removeItem(UNDO_STACK_KEY('')); + Storage.removeItem(UNDO_STACK_INDEX_KEY('')); + Storage.setItem(UNDO_STACK_KEY(id), JSON.stringify(stack)); + if (editor.undoStackIndex !== null) { + Storage.setItem( + UNDO_STACK_INDEX_KEY(id), + `${editor?.undoStackIndex}`, + ); + } } else { updateMap( { @@ -481,14 +555,23 @@ export default function MapEditor({ type, setSaveState, ); + Storage.setItem(UNDO_STACK_KEY(mapObject?.id), JSON.stringify(stack)); + if (editor.undoStackIndex !== null) { + Storage.setItem( + UNDO_STACK_INDEX_KEY(mapObject?.id), + `${editor?.undoStackIndex}`, + ); + } } }, [ createMap, editor.effects, + editor.undoStackIndex, mapName, mapObject?.id, setMap, + editor.undoStack, tags, updateMap, withHumanPlayer, @@ -574,6 +657,7 @@ export default function MapEditor({ undoStack.length, ), ); + Storage.setItem(UNDO_STACK_INDEX_KEY(mapObject?.id), `${index}`); if (index > -1 && index < undoStack.length) { const [, newMap] = undoStack.at(index) || []; if (newMap) { @@ -659,7 +743,15 @@ export default function MapEditor({ document.removeEventListener('keydown', keydownListener); document.removeEventListener('keyup', keyupListener); }; - }, [editor, saveMap, setEditorState, setMap, tilted, toggleDeleteEntity]); + }, [ + editor, + saveMap, + setEditorState, + setMap, + tilted, + toggleDeleteEntity, + mapObject?.id, + ]); useInput( 'select', @@ -712,7 +804,7 @@ export default function MapEditor({ const restorePreviousState = useCallback(() => { if (previousState) { - setMap('reset', previousState.map); + setMap('reset', previousState.map, previousState.undoStack); setPreviousState(null); _setEditorState( getEditorBaseState( @@ -722,6 +814,8 @@ export default function MapEditor({ }, editor.mode, editor.scenario, + previousState.undoStack, + previousState.undoStackIndex, ), ); } diff --git a/hera/editor/Types.tsx b/hera/editor/Types.tsx index 161b3f55..5b36b934 100644 --- a/hera/editor/Types.tsx +++ b/hera/editor/Types.tsx @@ -65,11 +65,13 @@ export type SetEditorStateFunction = (editor: Partial) => void; export type SetMapFunction = ( type: 'biome' | 'cleanup' | 'heal' | 'reset' | 'resize' | 'teams', map: MapData, + undoStack?: UndoStack, ) => void; type MapSaveType = 'New' | 'Update' | 'Disk' | 'Export'; export type MapCreateVariables = Readonly<{ effects: Effects; + id: string; map: MapData; mapName: string; tags: ReadonlyArray; @@ -110,6 +112,8 @@ export type MapObject = Readonly<{ export type PreviousMapEditorState = Readonly<{ effects: string; map: MapData; + undoStack?: UndoStack; + undoStackIndex?: number; }>; export type MapEditorSaveMessageId = diff --git a/hera/editor/lib/updateUndoStack.tsx b/hera/editor/lib/updateUndoStack.tsx index a0905aab..a7d9bdac 100644 --- a/hera/editor/lib/updateUndoStack.tsx +++ b/hera/editor/lib/updateUndoStack.tsx @@ -1,9 +1,17 @@ +import Storage from '@deities/ui/Storage.tsx'; import { EditorState, SetEditorStateFunction, UndoEntry } from '../Types.tsx'; +export const UNDO_STACK_KEY = (id: string | undefined) => + `map-editor-undo-stack-${id ? `${id}` : 'fallback'}`; + +export const UNDO_STACK_INDEX_KEY = (id: string | undefined) => + `map-editor-undo-stack-index-${id ? `${id}` : 'fallback'}`; + export default function updateUndoStack( { setEditorState }: { setEditorState: SetEditorStateFunction }, { undoStack, undoStackIndex }: EditorState, entry: UndoEntry, + id: string | undefined, ) { const lastKey = undoStack.at( undoStackIndex != null ? undoStackIndex : -1, @@ -24,4 +32,8 @@ export default function updateUndoStack( ], undoStackIndex: null, }); + + const stack = undoStack.map(([key, value]) => [key, value.toJSON()]); + + Storage.setItem(UNDO_STACK_KEY(id), JSON.stringify(stack)); } diff --git a/ui/Storage.tsx b/ui/Storage.tsx index b72d6f69..8bfdb611 100644 --- a/ui/Storage.tsx +++ b/ui/Storage.tsx @@ -14,6 +14,10 @@ export default { return localStorage.getItem(`${namespace}${key}`); }, + removeItem(key: string) { + return localStorage.removeItem(`${namespace}${key}`); + }, + setItem(key: string, value: string) { if (value === null) { localStorage.removeItem(`${namespace}${key}`); From d8c6d8d38212709435d252cb96b358ef8cad5cc9 Mon Sep 17 00:00:00 2001 From: Thomas McAuliffe Date: Fri, 17 May 2024 16:08:20 +0100 Subject: [PATCH 2/6] fix: make updateUndoStack param optional --- hera/editor/lib/updateUndoStack.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hera/editor/lib/updateUndoStack.tsx b/hera/editor/lib/updateUndoStack.tsx index a7d9bdac..615d14d7 100644 --- a/hera/editor/lib/updateUndoStack.tsx +++ b/hera/editor/lib/updateUndoStack.tsx @@ -11,7 +11,7 @@ export default function updateUndoStack( { setEditorState }: { setEditorState: SetEditorStateFunction }, { undoStack, undoStackIndex }: EditorState, entry: UndoEntry, - id: string | undefined, + id?: string, ) { const lastKey = undoStack.at( undoStackIndex != null ? undoStackIndex : -1, @@ -33,7 +33,8 @@ export default function updateUndoStack( undoStackIndex: null, }); - const stack = undoStack.map(([key, value]) => [key, value.toJSON()]); - - Storage.setItem(UNDO_STACK_KEY(id), JSON.stringify(stack)); + if (id) { + const stack = undoStack.map(([key, value]) => [key, value.toJSON()]); + Storage.setItem(UNDO_STACK_KEY(id), JSON.stringify(stack)); + } } From c6f4a0482723a5c462b17c5540e7487af03dd976 Mon Sep 17 00:00:00 2001 From: Thomas McAuliffe Date: Sun, 19 May 2024 19:22:09 +0100 Subject: [PATCH 3/6] feat: make suggestions - change stack keys - remove id from MapCreateVariables type - use null instead of undefined - use array.length - 1 to calc last index --- docs/content/examples/map-editor.tsx | 6 ++-- hera/editor/MapEditor.tsx | 42 +++++++++++++++------------- hera/editor/Types.tsx | 1 - hera/editor/lib/updateUndoStack.tsx | 6 ++-- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/docs/content/examples/map-editor.tsx b/docs/content/examples/map-editor.tsx index 7eb890b9..162f62b4 100644 --- a/docs/content/examples/map-editor.tsx +++ b/docs/content/examples/map-editor.tsx @@ -80,6 +80,8 @@ export default function MapEditorExample() { const handleMapUpdate = useCallback( (variables: MapCreateVariables | MapUpdateVariables) => { + const nameToSlug = toSlug(variables.mapName); + setMapObject({ campaigns: { edges: [], @@ -90,9 +92,9 @@ export default function MapEditorExample() { username: viewer.username, }, effects: JSON.stringify(encodeEffects(variables.effects)), - id: 'id' in variables ? variables.id : '', + id: 'id' in variables ? variables.id : nameToSlug, name: variables.mapName, - slug: toSlug(variables.mapName), + slug: nameToSlug, state: JSON.stringify(variables.map.toJSON()), tags: variables.tags, }); diff --git a/hera/editor/MapEditor.tsx b/hera/editor/MapEditor.tsx index e9003e03..91db2e05 100644 --- a/hera/editor/MapEditor.tsx +++ b/hera/editor/MapEditor.tsx @@ -33,6 +33,7 @@ import MapData, { SizeVector } from '@deities/athena/MapData.tsx'; import getFirstOrThrow from '@deities/hephaestus/getFirstOrThrow.tsx'; import isPresent from '@deities/hephaestus/isPresent.tsx'; import random from '@deities/hephaestus/random.tsx'; +import toSlug from '@deities/hephaestus/toSlug.tsx'; import { ClientGame } from '@deities/hermes/game/toClientGame.tsx'; import { isIOS } from '@deities/ui/Browser.tsx'; import isControlElement from '@deities/ui/controls/isControlElement.tsx'; @@ -91,8 +92,8 @@ import BiomeIcon from './lib/BiomeIcon.tsx'; import canFillTile from './lib/canFillTile.tsx'; import getMapValidationErrorText from './lib/getMapValidationErrorText.tsx'; import updateUndoStack, { - UNDO_STACK_INDEX_KEY, - UNDO_STACK_KEY, + getUndoStackIndexKey, + getUndoStackKey, } from './lib/updateUndoStack.tsx'; import ZoomButton from './lib/ZoomButton.tsx'; import MapEditorControlPanel from './panels/MapEditorControlPanel.tsx'; @@ -114,9 +115,9 @@ const generateName = nameGenerator(); export function decodeUndoStack( encodedUndoStack: string | null, -): UndoStack | undefined { +): UndoStack | null { if (!encodedUndoStack) { - return undefined; + return null; } try { @@ -124,15 +125,15 @@ export function decodeUndoStack( ([key, value]: [string, PlainMap]) => [key, MapData.fromObject(value)], ); } catch { - return undefined; + return null; } } const getUndoStack = (id?: string) => - decodeUndoStack(Storage.getItem(UNDO_STACK_KEY(id))); + decodeUndoStack(Storage.getItem(getUndoStackKey(id))); const getUndoStackIndex = (id?: string) => { - const index = Storage.getItem(UNDO_STACK_INDEX_KEY(id)); + const index = Storage.getItem(getUndoStackIndexKey(id)); return index ? Number.parseInt(index, 10) : undefined; }; @@ -153,8 +154,8 @@ const getEditorBaseState = ( mapObject: Pick | null = null, mode: EditorMode = 'design', scenario?: Scenario, - undoStack?: UndoStack, - undoStackIndex?: number, + undoStack?: UndoStack | null, + undoStackIndex?: number | null, ): EditorState => { const startScenario = new Set([{ actions: [] }]); let effects: Effects = mapObject?.effects @@ -364,8 +365,8 @@ export default function MapEditor({ return null; } - const undoStackIndex = getUndoStackIndex(mapObject?.id); - const map = undoStack[undoStackIndex ?? -1][1]; + const undoStackIndex = getUndoStackIndex(mapObject?.id) ?? -1; + const map = undoStack[undoStackIndex ?? undoStack.length - 1][1]; return { effects, @@ -521,11 +522,9 @@ export default function MapEditor({ ]); if (isNew) { - const id = generateName(); createMap( { effects: editor.effects, - id, map, mapName, tags: mapObject?.id @@ -534,12 +533,15 @@ export default function MapEditor({ }, setSaveState, ); - Storage.removeItem(UNDO_STACK_KEY('')); - Storage.removeItem(UNDO_STACK_INDEX_KEY('')); - Storage.setItem(UNDO_STACK_KEY(id), JSON.stringify(stack)); + Storage.removeItem(getUndoStackKey('')); + Storage.removeItem(getUndoStackIndexKey('')); + Storage.setItem( + getUndoStackKey(toSlug(mapName)), + JSON.stringify(stack), + ); if (editor.undoStackIndex !== null) { Storage.setItem( - UNDO_STACK_INDEX_KEY(id), + getUndoStackIndexKey(toSlug(mapName)), `${editor?.undoStackIndex}`, ); } @@ -555,10 +557,10 @@ export default function MapEditor({ type, setSaveState, ); - Storage.setItem(UNDO_STACK_KEY(mapObject?.id), JSON.stringify(stack)); + Storage.setItem(getUndoStackKey(mapObject?.id), JSON.stringify(stack)); if (editor.undoStackIndex !== null) { Storage.setItem( - UNDO_STACK_INDEX_KEY(mapObject?.id), + getUndoStackIndexKey(mapObject?.id), `${editor?.undoStackIndex}`, ); } @@ -657,7 +659,7 @@ export default function MapEditor({ undoStack.length, ), ); - Storage.setItem(UNDO_STACK_INDEX_KEY(mapObject?.id), `${index}`); + Storage.setItem(getUndoStackIndexKey(mapObject?.id), `${index}`); if (index > -1 && index < undoStack.length) { const [, newMap] = undoStack.at(index) || []; if (newMap) { diff --git a/hera/editor/Types.tsx b/hera/editor/Types.tsx index 5b36b934..17503e60 100644 --- a/hera/editor/Types.tsx +++ b/hera/editor/Types.tsx @@ -71,7 +71,6 @@ export type SetMapFunction = ( type MapSaveType = 'New' | 'Update' | 'Disk' | 'Export'; export type MapCreateVariables = Readonly<{ effects: Effects; - id: string; map: MapData; mapName: string; tags: ReadonlyArray; diff --git a/hera/editor/lib/updateUndoStack.tsx b/hera/editor/lib/updateUndoStack.tsx index 615d14d7..ec87867c 100644 --- a/hera/editor/lib/updateUndoStack.tsx +++ b/hera/editor/lib/updateUndoStack.tsx @@ -1,10 +1,10 @@ import Storage from '@deities/ui/Storage.tsx'; import { EditorState, SetEditorStateFunction, UndoEntry } from '../Types.tsx'; -export const UNDO_STACK_KEY = (id: string | undefined) => +export const getUndoStackKey = (id: string | undefined) => `map-editor-undo-stack-${id ? `${id}` : 'fallback'}`; -export const UNDO_STACK_INDEX_KEY = (id: string | undefined) => +export const getUndoStackIndexKey = (id: string | undefined) => `map-editor-undo-stack-index-${id ? `${id}` : 'fallback'}`; export default function updateUndoStack( @@ -35,6 +35,6 @@ export default function updateUndoStack( if (id) { const stack = undoStack.map(([key, value]) => [key, value.toJSON()]); - Storage.setItem(UNDO_STACK_KEY(id), JSON.stringify(stack)); + Storage.setItem(getUndoStackKey(id), JSON.stringify(stack)); } } From 2c773b58033d93fcbdb8673fb033dc3ac8c9b704 Mon Sep 17 00:00:00 2001 From: Thomas McAuliffe Date: Mon, 20 May 2024 11:42:03 +0100 Subject: [PATCH 4/6] fix: undostack across saves --- hera/editor/MapEditor.tsx | 54 ++++++++++++++++++++++------- hera/editor/lib/updateUndoStack.tsx | 7 ---- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/hera/editor/MapEditor.tsx b/hera/editor/MapEditor.tsx index 91db2e05..b12b50e0 100644 --- a/hera/editor/MapEditor.tsx +++ b/hera/editor/MapEditor.tsx @@ -201,6 +201,23 @@ export type BaseMapEditorProps = Readonly<{ setHasChanges: (hasChanges: boolean) => void; }>; +const getInitialMapFromUndoStack = (id?: string) => { + const undoStack = getUndoStack(id); + + if (!undoStack) { + return null; + } + + const undoStackIndex = getUndoStackIndex(id) ?? undoStack.length - 1; + const map = undoStack.at(undoStackIndex)?.[1]; + + if (!map) { + return null; + } + + return { map, undoStack, undoStackIndex }; +}; + export default function MapEditor({ animationSpeed, campaignLock, @@ -244,7 +261,8 @@ export default function MapEditor({ (size = new SizeVector(random(10, 15), random(10, 15))): MapData => { return withHumanPlayer( mapObject - ? MapData.fromJSON(mapObject.state)! + ? getInitialMapFromUndoStack(mapObject?.id)?.map ?? + MapData.fromJSON(mapObject.state)! : withModifiers( generateSea( generateBuildings( @@ -359,20 +377,18 @@ export default function MapEditor({ useState(() => { try { const effects = Storage.getItem(EFFECTS_KEY) || ''; - const undoStack = getUndoStack(mapObject?.id); - if (!undoStack) { + const stateFromStorage = getInitialMapFromUndoStack(mapObject?.id); + + if (!stateFromStorage) { return null; } - const undoStackIndex = getUndoStackIndex(mapObject?.id) ?? -1; - const map = undoStack[undoStackIndex ?? undoStack.length - 1][1]; - return { effects, - map, - undoStack, - undoStackIndex, + map: stateFromStorage.map, + undoStack: stateFromStorage.undoStack, + undoStackIndex: stateFromStorage.undoStackIndex, }; // eslint-disable-next-line no-empty } catch {} @@ -394,12 +410,11 @@ export default function MapEditor({ : 'checkpoint', map, ], - mapObject?.id, ); } setInternalMapID(internalMapID + 1); }, - [editor, internalMapID, setEditorState, mapObject?.id], + [editor, internalMapID, setEditorState], ); const updatePreviousMap = useCallback( @@ -659,7 +674,9 @@ export default function MapEditor({ undoStack.length, ), ); - Storage.setItem(getUndoStackIndexKey(mapObject?.id), `${index}`); + if (Storage.getItem(getUndoStackKey(mapObject?.id)) !== null) { + Storage.setItem(getUndoStackIndexKey(mapObject?.id), `${index}`); + } if (index > -1 && index < undoStack.length) { const [, newMap] = undoStack.at(index) || []; if (newMap) { @@ -778,6 +795,9 @@ export default function MapEditor({ setMap('reset', newMap); _setEditorState(getEditorBaseState(newMap, mapObject, mode, scenario)); updatePreviousMap(); + // TODO: delete fallback here + Storage.removeItem(getUndoStackKey('')); + Storage.removeItem(getUndoStackIndexKey('')); }, [getInitialMap, mapObject, mode, scenario, setMap, updatePreviousMap]); const fillMap = useCallback(() => { @@ -850,6 +870,16 @@ export default function MapEditor({ } }, [saveState]); + useEffect(() => { + if (editor.undoStack) { + const stack = editor.undoStack.map(([key, value]) => [ + key, + value.toJSON(), + ]); + Storage.setItem(getUndoStackKey(mapObject?.id), JSON.stringify(stack)); + } + }, [editor.undoStack, mapObject?.id]); + // Disabling the context menu is handled globally in production, // so this is only needed for the Map Editor in development. if (process.env.NODE_ENV === 'development') { diff --git a/hera/editor/lib/updateUndoStack.tsx b/hera/editor/lib/updateUndoStack.tsx index ec87867c..c21358bc 100644 --- a/hera/editor/lib/updateUndoStack.tsx +++ b/hera/editor/lib/updateUndoStack.tsx @@ -1,4 +1,3 @@ -import Storage from '@deities/ui/Storage.tsx'; import { EditorState, SetEditorStateFunction, UndoEntry } from '../Types.tsx'; export const getUndoStackKey = (id: string | undefined) => @@ -11,7 +10,6 @@ export default function updateUndoStack( { setEditorState }: { setEditorState: SetEditorStateFunction }, { undoStack, undoStackIndex }: EditorState, entry: UndoEntry, - id?: string, ) { const lastKey = undoStack.at( undoStackIndex != null ? undoStackIndex : -1, @@ -32,9 +30,4 @@ export default function updateUndoStack( ], undoStackIndex: null, }); - - if (id) { - const stack = undoStack.map(([key, value]) => [key, value.toJSON()]); - Storage.setItem(getUndoStackKey(id), JSON.stringify(stack)); - } } From f5de162c87dacc45f005c6fb32e23520fd0228d7 Mon Sep 17 00:00:00 2001 From: Thomas McAuliffe Date: Mon, 20 May 2024 11:46:08 +0100 Subject: [PATCH 5/6] chore: remove TODO --- hera/editor/MapEditor.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/hera/editor/MapEditor.tsx b/hera/editor/MapEditor.tsx index b12b50e0..4d52fc00 100644 --- a/hera/editor/MapEditor.tsx +++ b/hera/editor/MapEditor.tsx @@ -795,7 +795,6 @@ export default function MapEditor({ setMap('reset', newMap); _setEditorState(getEditorBaseState(newMap, mapObject, mode, scenario)); updatePreviousMap(); - // TODO: delete fallback here Storage.removeItem(getUndoStackKey('')); Storage.removeItem(getUndoStackIndexKey('')); }, [getInitialMap, mapObject, mode, scenario, setMap, updatePreviousMap]); From 3ce9fed51ee4a5b28f4271b7c8013d6bfd1905bc Mon Sep 17 00:00:00 2001 From: Thomas McAuliffe Date: Wed, 22 May 2024 19:12:19 +0100 Subject: [PATCH 6/6] chore: make amendments - nameToSlug -> slug - getUndoStackKey -> getUndoStackKeyFor - getUndoStackKeyIndex -> getUndoStackKeyIndexFor - remove empty line --- docs/content/examples/map-editor.tsx | 6 ++--- hera/editor/MapEditor.tsx | 37 ++++++++++++++++------------ hera/editor/lib/updateUndoStack.tsx | 4 +-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/docs/content/examples/map-editor.tsx b/docs/content/examples/map-editor.tsx index 162f62b4..2fe6f8c5 100644 --- a/docs/content/examples/map-editor.tsx +++ b/docs/content/examples/map-editor.tsx @@ -80,7 +80,7 @@ export default function MapEditorExample() { const handleMapUpdate = useCallback( (variables: MapCreateVariables | MapUpdateVariables) => { - const nameToSlug = toSlug(variables.mapName); + const slug = toSlug(variables.mapName); setMapObject({ campaigns: { @@ -92,9 +92,9 @@ export default function MapEditorExample() { username: viewer.username, }, effects: JSON.stringify(encodeEffects(variables.effects)), - id: 'id' in variables ? variables.id : nameToSlug, + id: 'id' in variables ? variables.id : slug, name: variables.mapName, - slug: nameToSlug, + slug, state: JSON.stringify(variables.map.toJSON()), tags: variables.tags, }); diff --git a/hera/editor/MapEditor.tsx b/hera/editor/MapEditor.tsx index 4d52fc00..9289e15a 100644 --- a/hera/editor/MapEditor.tsx +++ b/hera/editor/MapEditor.tsx @@ -92,8 +92,8 @@ import BiomeIcon from './lib/BiomeIcon.tsx'; import canFillTile from './lib/canFillTile.tsx'; import getMapValidationErrorText from './lib/getMapValidationErrorText.tsx'; import updateUndoStack, { - getUndoStackIndexKey, - getUndoStackKey, + getUndoStackIndexKeyFor, + getUndoStackKeyFor, } from './lib/updateUndoStack.tsx'; import ZoomButton from './lib/ZoomButton.tsx'; import MapEditorControlPanel from './panels/MapEditorControlPanel.tsx'; @@ -130,10 +130,10 @@ export function decodeUndoStack( } const getUndoStack = (id?: string) => - decodeUndoStack(Storage.getItem(getUndoStackKey(id))); + decodeUndoStack(Storage.getItem(getUndoStackKeyFor(id))); const getUndoStackIndex = (id?: string) => { - const index = Storage.getItem(getUndoStackIndexKey(id)); + const index = Storage.getItem(getUndoStackIndexKeyFor(id)); return index ? Number.parseInt(index, 10) : undefined; }; @@ -377,7 +377,6 @@ export default function MapEditor({ useState(() => { try { const effects = Storage.getItem(EFFECTS_KEY) || ''; - const stateFromStorage = getInitialMapFromUndoStack(mapObject?.id); if (!stateFromStorage) { @@ -548,15 +547,15 @@ export default function MapEditor({ }, setSaveState, ); - Storage.removeItem(getUndoStackKey('')); - Storage.removeItem(getUndoStackIndexKey('')); + Storage.removeItem(getUndoStackKeyFor('')); + Storage.removeItem(getUndoStackIndexKeyFor('')); Storage.setItem( - getUndoStackKey(toSlug(mapName)), + getUndoStackKeyFor(toSlug(mapName)), JSON.stringify(stack), ); if (editor.undoStackIndex !== null) { Storage.setItem( - getUndoStackIndexKey(toSlug(mapName)), + getUndoStackIndexKeyFor(toSlug(mapName)), `${editor?.undoStackIndex}`, ); } @@ -572,10 +571,13 @@ export default function MapEditor({ type, setSaveState, ); - Storage.setItem(getUndoStackKey(mapObject?.id), JSON.stringify(stack)); + Storage.setItem( + getUndoStackKeyFor(mapObject?.id), + JSON.stringify(stack), + ); if (editor.undoStackIndex !== null) { Storage.setItem( - getUndoStackIndexKey(mapObject?.id), + getUndoStackIndexKeyFor(mapObject?.id), `${editor?.undoStackIndex}`, ); } @@ -674,8 +676,11 @@ export default function MapEditor({ undoStack.length, ), ); - if (Storage.getItem(getUndoStackKey(mapObject?.id)) !== null) { - Storage.setItem(getUndoStackIndexKey(mapObject?.id), `${index}`); + if (Storage.getItem(getUndoStackKeyFor(mapObject?.id)) !== null) { + Storage.setItem( + getUndoStackIndexKeyFor(mapObject?.id), + `${index}`, + ); } if (index > -1 && index < undoStack.length) { const [, newMap] = undoStack.at(index) || []; @@ -795,8 +800,8 @@ export default function MapEditor({ setMap('reset', newMap); _setEditorState(getEditorBaseState(newMap, mapObject, mode, scenario)); updatePreviousMap(); - Storage.removeItem(getUndoStackKey('')); - Storage.removeItem(getUndoStackIndexKey('')); + Storage.removeItem(getUndoStackKeyFor('')); + Storage.removeItem(getUndoStackIndexKeyFor('')); }, [getInitialMap, mapObject, mode, scenario, setMap, updatePreviousMap]); const fillMap = useCallback(() => { @@ -875,7 +880,7 @@ export default function MapEditor({ key, value.toJSON(), ]); - Storage.setItem(getUndoStackKey(mapObject?.id), JSON.stringify(stack)); + Storage.setItem(getUndoStackKeyFor(mapObject?.id), JSON.stringify(stack)); } }, [editor.undoStack, mapObject?.id]); diff --git a/hera/editor/lib/updateUndoStack.tsx b/hera/editor/lib/updateUndoStack.tsx index c21358bc..5e22eaa5 100644 --- a/hera/editor/lib/updateUndoStack.tsx +++ b/hera/editor/lib/updateUndoStack.tsx @@ -1,9 +1,9 @@ import { EditorState, SetEditorStateFunction, UndoEntry } from '../Types.tsx'; -export const getUndoStackKey = (id: string | undefined) => +export const getUndoStackKeyFor = (id: string | undefined) => `map-editor-undo-stack-${id ? `${id}` : 'fallback'}`; -export const getUndoStackIndexKey = (id: string | undefined) => +export const getUndoStackIndexKeyFor = (id: string | undefined) => `map-editor-undo-stack-index-${id ? `${id}` : 'fallback'}`; export default function updateUndoStack(