From 08152ff97801b5a870e1d5cd3134d569bfcb4228 Mon Sep 17 00:00:00 2001 From: Huarch Date: Fri, 17 Jul 2026 15:28:44 +0800 Subject: [PATCH] fix(map): restore saved styles as inactive drafts --- .../core/Controls/styleEditorUtils.test.ts | 23 +++++++++++ .../olmap/core/Controls/styleEditorUtils.ts | 41 +++++++++++++++++++ .../olmap/core/Controls/useStyleEditor.ts | 34 ++++----------- 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/components/olmap/core/Controls/styleEditorUtils.test.ts b/src/components/olmap/core/Controls/styleEditorUtils.test.ts index 3237e56..c7ce0f6 100644 --- a/src/components/olmap/core/Controls/styleEditorUtils.test.ts +++ b/src/components/olmap/core/Controls/styleEditorUtils.test.ts @@ -3,8 +3,10 @@ import { buildDynamicStyleTemplate, buildStyleVariables, getDefaultCustomBreaks, + hydrateStoredLayerStyleStates, resolveLayerStyle, requiresStyleApply, + selectStoredLayerStyles, validateStyleConfig, } from "./styleEditorUtils"; @@ -104,4 +106,25 @@ describe("styleEditorUtils", () => { }), ).toBe(true); }); + + it("hydrates workspace settings as inactive drafts", () => { + const storedPipeStyle = { + ...createDefaultLayerStyleState("pipes").styleConfig, + property: "flow", + showLabels: false, + }; + const restored = hydrateStoredLayerStyleStates( + { + version: 2, + layers: { pipes: storedPipeStyle }, + }, + 2, + ); + + expect(restored.find((state) => state.layerId === "pipes")?.styleConfig).toEqual( + storedPipeStyle, + ); + expect(restored.every((state) => !state.isActive)).toBe(true); + expect(selectStoredLayerStyles(restored).pipes).toEqual(storedPipeStyle); + }); }); diff --git a/src/components/olmap/core/Controls/styleEditorUtils.ts b/src/components/olmap/core/Controls/styleEditorUtils.ts index 6f59b49..4550aa8 100644 --- a/src/components/olmap/core/Controls/styleEditorUtils.ts +++ b/src/components/olmap/core/Controls/styleEditorUtils.ts @@ -4,11 +4,14 @@ import { calculateClassification } from "@utils/breaksClassification"; import { parseColor } from "@utils/parseColor"; import { + createDefaultLayerStyleStates, GRADIENT_PALETTES, RAINBOW_PALETTES, SINGLE_COLOR_PALETTES, } from "./styleEditorPresets"; import type { + DefaultLayerStyleId, + LayerStyleState, ResolvedLayerStyle, StyleConfig, StyleValidationResult, @@ -232,6 +235,44 @@ export const validateStyleConfig = (styleConfig: StyleConfig): StyleValidationRe return { valid: errors.length === 0, errors }; }; +export const hydrateStoredLayerStyleStates = ( + document: unknown, + expectedVersion: number, +): LayerStyleState[] => { + const defaults = createDefaultLayerStyleStates(); + if (!document || typeof document !== "object") return defaults; + + const storedDocument = document as { + version?: number; + layers?: Partial>; + }; + if (storedDocument.version !== expectedVersion || !storedDocument.layers) { + return defaults; + } + + return defaults.map((state) => { + const stored = storedDocument.layers?.[state.layerId as DefaultLayerStyleId]; + if (!stored || !validateStyleConfig(stored).valid) return state; + return { + ...state, + styleConfig: { + ...stored, + customBreaks: [...(stored.customBreaks || [])], + customColors: [...(stored.customColors || [])], + }, + legendConfig: { ...state.legendConfig, property: stored.property }, + isActive: false, + }; + }); +}; + +export const selectStoredLayerStyles = (states: LayerStyleState[]) => + Object.fromEntries( + states + .filter((state) => state.layerId === "junctions" || state.layerId === "pipes") + .map((state) => [state.layerId, state.styleConfig]), + ) as Partial>; + export const requiresStyleApply = ( applied: StyleConfig | undefined, draft: StyleConfig, diff --git a/src/components/olmap/core/Controls/useStyleEditor.ts b/src/components/olmap/core/Controls/useStyleEditor.ts index c708ce3..d168c7e 100644 --- a/src/components/olmap/core/Controls/useStyleEditor.ts +++ b/src/components/olmap/core/Controls/useStyleEditor.ts @@ -20,11 +20,13 @@ import { buildStyleVariables, getDefaultCustomBreaks, getDefaultCustomColors, + hydrateStoredLayerStyleStates, normalizeCustomBreaks, requiresStyleApply, resolveDimensions, resolveLayerStyle, resolveStyleColors, + selectStoredLayerStyles, validateStyleConfig, } from "./styleEditorUtils"; import type { @@ -885,23 +887,10 @@ export const useStyleEditor = ({ try { const raw = window.localStorage.getItem(storageKey); if (raw) { - const document = JSON.parse(raw) as { - version?: number; - layers?: Partial>; - }; - if (document.version === STYLE_STORAGE_VERSION && document.layers) { - restored = createDefaultLayerStyleStates().map((state) => { - if (!isDefaultLayerId(state.layerId)) return state; - const stored = document.layers?.[state.layerId]; - if (!stored || !validateStyleConfig(stored).valid) return state; - return { - ...state, - styleConfig: cloneStyleConfig(stored), - legendConfig: { ...state.legendConfig, property: stored.property }, - isActive: true, - }; - }); - } + restored = hydrateStoredLayerStyleStates( + JSON.parse(raw), + STYLE_STORAGE_VERSION, + ); } } catch (error) { console.warn("Restore layer styles failed", error); @@ -910,19 +899,14 @@ export const useStyleEditor = ({ setLayerStyleStates(restored); const restoredSelection = restored.find((state) => state.layerId === "junctions"); if (restoredSelection) setStyleConfig(cloneStyleConfig(restoredSelection.styleConfig)); - restored - .filter((state) => state.isActive && isDefaultLayerId(state.layerId)) - .forEach((state) => syncAuxiliaryLayers(state.layerId as DefaultLayerStyleId, state.styleConfig)); + syncAuxiliaryLayers("junctions", null); + syncAuxiliaryLayers("pipes", null); setPersistenceReady(true); }, [setLayerStyleStates, storageKey, syncAuxiliaryLayers]); useEffect(() => { if (!persistenceReady) return; - const layers = Object.fromEntries( - layerStyleStates - .filter((state) => state.isActive && isDefaultLayerId(state.layerId)) - .map((state) => [state.layerId, state.styleConfig]), - ); + const layers = selectStoredLayerStyles(layerStyleStates); try { window.localStorage.setItem( storageKey,