221 lines
5.4 KiB
JavaScript
221 lines
5.4 KiB
JavaScript
import { useCallback, useEffect, useRef } from "react";
|
|
import useMobileMapAutoFit from "./useMobileMapAutoFit";
|
|
import { getObjectKey } from "../lib/locationMapUtils";
|
|
|
|
function cloneMapObjects(objects = []) {
|
|
return objects.map((object) => ({ ...object }));
|
|
}
|
|
|
|
function getHistorySnapshot(snapshot, fallbackSelectedObjectKey = null) {
|
|
if (Array.isArray(snapshot)) {
|
|
return {
|
|
objects: cloneMapObjects(snapshot),
|
|
selectedObjectKey: fallbackSelectedObjectKey,
|
|
};
|
|
}
|
|
|
|
return {
|
|
objects: cloneMapObjects(snapshot?.objects || []),
|
|
selectedObjectKey: snapshot?.selectedObjectKey ?? null,
|
|
};
|
|
}
|
|
|
|
function createHistorySnapshot(objects, selectedObjectKey) {
|
|
return {
|
|
objects: cloneMapObjects(objects),
|
|
selectedObjectKey: selectedObjectKey ?? null,
|
|
};
|
|
}
|
|
|
|
function isTextEditingTarget(target) {
|
|
return Boolean(target?.closest?.("input, textarea, select, [contenteditable]"));
|
|
}
|
|
|
|
export default function useLocationMapViewControls({
|
|
filters,
|
|
future = [],
|
|
hasUnsavedChanges,
|
|
history = [],
|
|
mapSize,
|
|
mapState,
|
|
mode,
|
|
objects,
|
|
previewDraft,
|
|
saving,
|
|
selectedObjectKey,
|
|
setFuture,
|
|
setHasUnsavedChanges,
|
|
setHistory,
|
|
setLayersOpen,
|
|
setMode,
|
|
setObjects,
|
|
setPreviewDraft,
|
|
setSelectedObjectKey,
|
|
setZoom,
|
|
svgRef,
|
|
syncMapDraftFromState,
|
|
zoom,
|
|
}) {
|
|
const shortcutControlsRef = useRef({
|
|
handleRedo: () => {},
|
|
handleUndo: () => {},
|
|
mode: "view",
|
|
saving: false,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!selectedObjectKey) return;
|
|
|
|
const selectedMapObject = objects.find((object) => getObjectKey(object) === selectedObjectKey);
|
|
if (!selectedMapObject || !filters.showZones || selectedMapObject.visible === false) {
|
|
setSelectedObjectKey(null);
|
|
}
|
|
}, [filters.showZones, objects, selectedObjectKey, setSelectedObjectKey]);
|
|
|
|
const fitMapToCanvas = useMobileMapAutoFit({
|
|
mapSize,
|
|
mapState,
|
|
mode,
|
|
setZoom,
|
|
svgRef,
|
|
zoom,
|
|
});
|
|
|
|
const handleUndo = useCallback(() => {
|
|
if (history.length === 0) return;
|
|
|
|
const priorSnapshot = getHistorySnapshot(history[history.length - 1], selectedObjectKey);
|
|
setFuture([
|
|
createHistorySnapshot(objects, selectedObjectKey),
|
|
...future.slice(0, 19),
|
|
]);
|
|
setObjects(priorSnapshot.objects);
|
|
setSelectedObjectKey(priorSnapshot.selectedObjectKey);
|
|
setHasUnsavedChanges(history.length > 1);
|
|
setHistory(history.slice(0, -1));
|
|
}, [
|
|
future, history, objects, selectedObjectKey, setFuture, setHasUnsavedChanges, setHistory,
|
|
setObjects, setSelectedObjectKey,
|
|
]);
|
|
|
|
const handleRedo = useCallback(() => {
|
|
if (future.length === 0) return;
|
|
|
|
const nextSnapshot = getHistorySnapshot(future[0], selectedObjectKey);
|
|
setHistory([
|
|
...history.slice(-19),
|
|
createHistorySnapshot(objects, selectedObjectKey),
|
|
]);
|
|
setObjects(nextSnapshot.objects);
|
|
setSelectedObjectKey(nextSnapshot.selectedObjectKey);
|
|
setHasUnsavedChanges(true);
|
|
setFuture(future.slice(1));
|
|
}, [
|
|
future, history, objects, selectedObjectKey, setFuture, setHasUnsavedChanges, setHistory,
|
|
setObjects, setSelectedObjectKey,
|
|
]);
|
|
|
|
shortcutControlsRef.current = {
|
|
handleRedo,
|
|
handleUndo,
|
|
mode,
|
|
saving,
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (typeof window === "undefined") {
|
|
return undefined;
|
|
}
|
|
|
|
const handleKeyDown = (event) => {
|
|
if (
|
|
event.defaultPrevented ||
|
|
event.altKey ||
|
|
!(event.ctrlKey || event.metaKey) ||
|
|
isTextEditingTarget(event.target)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const controls = shortcutControlsRef.current;
|
|
if (controls.mode !== "edit" || controls.saving) {
|
|
return;
|
|
}
|
|
|
|
const key = event.key.toLowerCase();
|
|
const wantsUndo = key === "z" && !event.shiftKey;
|
|
const wantsRedo = key === "y" || (key === "z" && event.shiftKey);
|
|
|
|
if (wantsUndo) {
|
|
event.preventDefault();
|
|
controls.handleUndo();
|
|
return;
|
|
}
|
|
|
|
if (wantsRedo) {
|
|
event.preventDefault();
|
|
controls.handleRedo();
|
|
}
|
|
};
|
|
|
|
window.addEventListener("keydown", handleKeyDown, true);
|
|
return () => window.removeEventListener("keydown", handleKeyDown, true);
|
|
}, []);
|
|
|
|
const handlePreviewDraft = useCallback(() => {
|
|
setMode("view");
|
|
setLayersOpen(false);
|
|
setPreviewDraft(true);
|
|
setSelectedObjectKey(null);
|
|
}, [setLayersOpen, setMode, setPreviewDraft, setSelectedObjectKey]);
|
|
|
|
const handleViewMode = useCallback(() => {
|
|
setMode("view");
|
|
setLayersOpen(false);
|
|
if ((mode === "edit" || previewDraft) && (hasUnsavedChanges || mapState?.draft_map)) {
|
|
setPreviewDraft(true);
|
|
return;
|
|
}
|
|
setPreviewDraft(false);
|
|
if (mapState) {
|
|
syncMapDraftFromState(mapState, "view", false);
|
|
}
|
|
}, [
|
|
hasUnsavedChanges,
|
|
mapState,
|
|
mode,
|
|
previewDraft,
|
|
setLayersOpen,
|
|
setMode,
|
|
setPreviewDraft,
|
|
setSelectedObjectKey,
|
|
syncMapDraftFromState,
|
|
]);
|
|
|
|
const handleEditMode = useCallback(() => {
|
|
setMode("edit");
|
|
setLayersOpen(false);
|
|
setPreviewDraft(true);
|
|
if (!previewDraft && mapState && !hasUnsavedChanges) {
|
|
syncMapDraftFromState(mapState, "edit", true);
|
|
}
|
|
}, [
|
|
hasUnsavedChanges,
|
|
mapState,
|
|
previewDraft,
|
|
setLayersOpen,
|
|
setMode,
|
|
setPreviewDraft,
|
|
syncMapDraftFromState,
|
|
]);
|
|
|
|
return {
|
|
handleEditMode,
|
|
handleFitMap: fitMapToCanvas,
|
|
handlePreviewDraft,
|
|
handleRedo,
|
|
handleUndo,
|
|
handleViewMode,
|
|
};
|
|
}
|