157 lines
5.3 KiB
JavaScript
157 lines
5.3 KiB
JavaScript
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import { getLocationMap } from "../api/locationMaps";
|
|
import getApiErrorMessage from "../lib/getApiErrorMessage";
|
|
import {
|
|
DEFAULT_MAP_FILTERS,
|
|
DEFAULT_MAP_SIZE,
|
|
getObjectKey,
|
|
mapForMode,
|
|
normalizeMapObject,
|
|
objectsForMode,
|
|
} from "../lib/locationMapUtils";
|
|
|
|
const DEFAULT_DRAFT_MAP = {
|
|
name: "Store Map",
|
|
width: DEFAULT_MAP_SIZE.width,
|
|
height: DEFAULT_MAP_SIZE.height,
|
|
};
|
|
|
|
export default function useLocationMapDraftState({
|
|
activeHousehold,
|
|
hasLoaded,
|
|
householdLoading,
|
|
locationId,
|
|
toast,
|
|
}) {
|
|
const [mapState, setMapState] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [loadError, setLoadError] = useState(null);
|
|
const [saving, setSaving] = useState(false);
|
|
const [savingAction, setSavingAction] = useState(null);
|
|
const [mode, setMode] = useState("setup");
|
|
const [editorTool, setEditorTool] = useState("pan");
|
|
const [previewDraft, setPreviewDraft] = useState(false);
|
|
const [mapDraft, setMapDraft] = useState(DEFAULT_DRAFT_MAP);
|
|
const [objects, setObjects] = useState([]);
|
|
const [selectedObjectKey, setSelectedObjectKey] = useState(null);
|
|
const [filters, setFilters] = useState(DEFAULT_MAP_FILTERS);
|
|
const [layersOpen, setLayersOpen] = useState(false);
|
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
|
const [zoom, setZoom] = useState(0.75);
|
|
const [history, setHistory] = useState([]);
|
|
const [future, setFuture] = useState([]);
|
|
const [dragState, setDragState] = useState(null);
|
|
const toastRef = useRef(toast);
|
|
const loadRequestRef = useRef(0);
|
|
|
|
const resetDraftState = useCallback(() => {
|
|
setObjects([]);
|
|
setSelectedObjectKey(null);
|
|
setHistory([]);
|
|
setFuture([]);
|
|
setHasUnsavedChanges(false);
|
|
setMapDraft(DEFAULT_DRAFT_MAP);
|
|
}, []);
|
|
|
|
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(normalizedObjects);
|
|
setSelectedObjectKey(preservedSelectedObject ? getObjectKey(preservedSelectedObject) : null);
|
|
setHistory([]);
|
|
setFuture([]);
|
|
setHasUnsavedChanges(false);
|
|
}, []);
|
|
|
|
const loadMap = useCallback(async () => {
|
|
const requestId = loadRequestRef.current + 1;
|
|
loadRequestRef.current = requestId;
|
|
|
|
if (!activeHousehold?.id || !locationId) {
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
setLoadError(null);
|
|
try {
|
|
const response = await getLocationMap(activeHousehold.id, locationId);
|
|
if (loadRequestRef.current !== requestId) return;
|
|
|
|
const nextState = response.data;
|
|
const nextCanManage = Boolean(
|
|
nextState.can_manage ?? ["owner", "admin"].includes(activeHousehold?.role)
|
|
);
|
|
setMapState(nextState);
|
|
setEditorTool("pan");
|
|
|
|
if (!nextState.draft_map && !nextState.published_map) {
|
|
setMode("setup");
|
|
setPreviewDraft(false);
|
|
resetDraftState();
|
|
return;
|
|
}
|
|
|
|
if (nextState.published_map) {
|
|
setMode("view");
|
|
setPreviewDraft(false);
|
|
syncMapDraftFromState(nextState, "view", false);
|
|
} else if (nextState.draft_map && nextCanManage) {
|
|
setMode("setup");
|
|
setPreviewDraft(true);
|
|
syncMapDraftFromState(nextState, "edit", true);
|
|
} else {
|
|
setMode("setup");
|
|
setPreviewDraft(false);
|
|
resetDraftState();
|
|
}
|
|
} catch (error) {
|
|
if (loadRequestRef.current !== requestId) return;
|
|
|
|
const message = getApiErrorMessage(error, "Failed to load map");
|
|
toastRef.current.error("Load map failed", message);
|
|
setMapState(null);
|
|
resetDraftState();
|
|
setLoadError(message);
|
|
} finally {
|
|
if (loadRequestRef.current === requestId) {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
}, [activeHousehold?.id, activeHousehold?.role, locationId, resetDraftState, syncMapDraftFromState]);
|
|
|
|
useEffect(() => { toastRef.current = toast; }, [toast]);
|
|
|
|
useEffect(() => {
|
|
if (!householdLoading && hasLoaded) {
|
|
loadMap();
|
|
}
|
|
}, [hasLoaded, householdLoading, loadMap]);
|
|
|
|
const beginSaving = useCallback((action) => { setSavingAction(action); setSaving(true); }, []);
|
|
|
|
const endSaving = useCallback(() => { setSaving(false); setSavingAction(null); }, []);
|
|
|
|
return {
|
|
beginSaving, dragState, editorTool, endSaving, filters, future, hasUnsavedChanges, history, layersOpen,
|
|
loadError, loadMap, loading, mapDraft, mapState, mode, objects, previewDraft, saving,
|
|
savingAction, selectedObjectKey, setDragState, setEditorTool, setFilters, setFuture,
|
|
setHasUnsavedChanges, setHistory, setLayersOpen, setMapState, setMode, setObjects,
|
|
setPreviewDraft, setSelectedObjectKey, setZoom, syncMapDraftFromState, zoom,
|
|
};
|
|
}
|