fix: keep selected map area after save

This commit is contained in:
Nico 2026-06-04 13:31:32 -07:00
parent 64b8071578
commit a862eba0fa
4 changed files with 37 additions and 11 deletions

View File

@ -6,7 +6,7 @@ import {
saveLocationMapDraft,
} from "../api/locationMaps";
import getApiErrorMessage from "../lib/getApiErrorMessage";
import { prepareObjectsForSave } from "../lib/locationMapUtils";
import { getObjectKey, prepareObjectsForSave } from "../lib/locationMapUtils";
export default function useLocationMapDraftActions({
activeHouseholdId,
@ -16,6 +16,7 @@ export default function useLocationMapDraftActions({
locationId,
mapDraft,
objects,
selectedObjectKey,
setEditorTool,
setMapState,
setMode,
@ -23,11 +24,15 @@ export default function useLocationMapDraftActions({
syncMapDraftFromState,
toast,
}) {
const applyDraftResponse = useCallback((response, nextMode = "edit", nextPreviewDraft = true) => {
const selectedObjectIndex = selectedObjectKey
? objects.findIndex((object) => getObjectKey(object) === selectedObjectKey)
: -1;
const applyDraftResponse = useCallback((response, nextMode = "edit", nextPreviewDraft = true, options = {}) => {
setMapState(response.data);
setMode(nextMode);
setPreviewDraft(nextPreviewDraft);
syncMapDraftFromState(response.data, nextMode, nextPreviewDraft);
syncMapDraftFromState(response.data, nextMode, nextPreviewDraft, options);
}, [setMapState, setMode, setPreviewDraft, syncMapDraftFromState]);
const handleCreateBlank = useCallback(async () => {
@ -68,14 +73,17 @@ export default function useLocationMapDraftActions({
map: mapDraft,
objects: prepareObjectsForSave(objects),
});
applyDraftResponse(response);
applyDraftResponse(response, "edit", true, { preserveSelectedIndex: selectedObjectIndex });
toast.success("Saved draft", "Map draft saved");
} catch (error) {
toast.error("Save draft failed", getApiErrorMessage(error, "Failed to save map draft"));
} finally {
endSaving();
}
}, [activeHouseholdId, applyDraftResponse, beginSaving, endSaving, locationId, mapDraft, objects, toast]);
}, [
activeHouseholdId, applyDraftResponse, beginSaving, endSaving, locationId,
mapDraft, objects, selectedObjectIndex, toast,
]);
const handlePublish = useCallback(async () => {
if (!activeHouseholdId || !locationId) return;
@ -88,7 +96,7 @@ export default function useLocationMapDraftActions({
objects: prepareObjectsForSave(objects),
});
savedPendingDraft = true;
applyDraftResponse(saveResponse);
applyDraftResponse(saveResponse, "edit", true, { preserveSelectedIndex: selectedObjectIndex });
}
const response = await publishLocationMapDraft(activeHouseholdId, locationId);
@ -108,7 +116,7 @@ export default function useLocationMapDraftActions({
}
}, [
activeHouseholdId, applyDraftResponse, beginSaving, endSaving, hasUnsavedChanges,
locationId, mapDraft, objects, setEditorTool, toast,
locationId, mapDraft, objects, selectedObjectIndex, setEditorTool, toast,
]);
return { handleCreateBlank, handleCreateFromZones, handlePublish, handleSaveDraft };

View File

@ -1,7 +1,14 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { getLocationMap } from "../api/locationMaps";
import getApiErrorMessage from "../lib/getApiErrorMessage";
import { DEFAULT_MAP_FILTERS, DEFAULT_MAP_SIZE, mapForMode, normalizeMapObject, objectsForMode } from "../lib/locationMapUtils";
import {
DEFAULT_MAP_FILTERS,
DEFAULT_MAP_SIZE,
getObjectKey,
mapForMode,
normalizeMapObject,
objectsForMode,
} from "../lib/locationMapUtils";
const DEFAULT_DRAFT_MAP = {
name: "Store Map",
@ -46,18 +53,25 @@ export default function useLocationMapDraftState({
setMapDraft(DEFAULT_DRAFT_MAP);
}, []);
const syncMapDraftFromState = useCallback((nextState, nextMode, nextPreviewDraft) => {
const syncMapDraftFromState = useCallback((nextState, nextMode, nextPreviewDraft, options = {}) => {
const nextMap = mapForMode(nextState, nextMode, nextPreviewDraft);
const nextObjects = objectsForMode(nextState, nextMode, nextPreviewDraft);
if (!nextMap) return;
const normalizedObjects = nextObjects.map(normalizeMapObject);
const preserveSelectedIndex = Number.isInteger(options.preserveSelectedIndex)
? options.preserveSelectedIndex
: -1;
const preservedSelectedObject = preserveSelectedIndex >= 0
? normalizedObjects[preserveSelectedIndex]
: null;
setMapDraft({
name: nextMap.name || "Store Map",
width: nextMap.width || DEFAULT_MAP_SIZE.width,
height: nextMap.height || DEFAULT_MAP_SIZE.height,
});
setObjects(nextObjects.map(normalizeMapObject));
setSelectedObjectKey(null);
setObjects(normalizedObjects);
setSelectedObjectKey(preservedSelectedObject ? getObjectKey(preservedSelectedObject) : null);
setHistory([]);
setFuture([]);
setHasUnsavedChanges(false);

View File

@ -254,6 +254,7 @@ export default function LocationMapManager() {
locationId,
mapDraft,
objects,
selectedObjectKey,
setEditorTool,
setMapState,
setMode,

View File

@ -1418,6 +1418,9 @@ test("admin save progress locks draft controls", async ({ page }) => {
releaseSave();
await expect(page.locator(".location-map-status")).toHaveText("Draft");
await expect(page.getByRole("button", { name: "Save Draft" })).toBeDisabled();
await expect(page.getByRole("textbox", { name: "Label" })).toBeEnabled();
await expect(page.getByRole("textbox", { name: "Label" })).toHaveValue("Saving Bakery");
await expect(page.getByRole("button", { name: "Map area Saving Bakery" })).toHaveAttribute("aria-pressed", "true");
});
test("admin save progress locks empty-canvas recovery actions", async ({ page }) => {