@treasuryspatial/viewer-react 0.1.44 → 0.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/AccessAssetsMenu.d.ts.map +1 -1
  2. package/dist/AccessAssetsMenu.js +53 -45
  3. package/dist/AssetUploadCard.d.ts +2 -1
  4. package/dist/AssetUploadCard.d.ts.map +1 -1
  5. package/dist/AssetUploadCard.js +3 -2
  6. package/dist/ViewControls.d.ts +19 -2
  7. package/dist/ViewControls.d.ts.map +1 -1
  8. package/dist/ViewControls.js +65 -16
  9. package/dist/ViewerCanvas.d.ts.map +1 -1
  10. package/dist/ViewerCanvas.js +15 -40
  11. package/dist/composerRouting.d.ts +6 -0
  12. package/dist/composerRouting.d.ts.map +1 -1
  13. package/dist/composerRouting.js +18 -9
  14. package/dist/index.d.ts +13 -6
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -4
  17. package/dist/tsconfig.tsbuildinfo +1 -1
  18. package/dist/useAdminGallery.d.ts +2 -30
  19. package/dist/useAdminGallery.d.ts.map +1 -1
  20. package/dist/useAdminGallery.js +15 -6
  21. package/dist/useAdminImageStream.d.ts +3 -31
  22. package/dist/useAdminImageStream.d.ts.map +1 -1
  23. package/dist/useAdminImageStream.js +22 -10
  24. package/dist/useComposerAccessGate.js +4 -4
  25. package/dist/useComposerAuthState.d.ts +1 -0
  26. package/dist/useComposerAuthState.d.ts.map +1 -1
  27. package/dist/useComposerAuthState.js +4 -1
  28. package/dist/useComposerModeDraftState.d.ts +21 -0
  29. package/dist/useComposerModeDraftState.d.ts.map +1 -0
  30. package/dist/useComposerModeDraftState.js +89 -0
  31. package/dist/useComposerUiState.d.ts +11 -4
  32. package/dist/useComposerUiState.d.ts.map +1 -1
  33. package/dist/useComposerUiState.js +103 -79
  34. package/dist/useComposerWorkspaceSession.d.ts +21 -0
  35. package/dist/useComposerWorkspaceSession.d.ts.map +1 -0
  36. package/dist/useComposerWorkspaceSession.js +291 -0
  37. package/dist/useGrasshopperSession.d.ts.map +1 -1
  38. package/dist/useGrasshopperSession.js +10 -4
  39. package/dist/useModeShellState.d.ts.map +1 -1
  40. package/dist/useModeShellState.js +52 -6
  41. package/dist/usePromptPackSelection.d.ts +20 -0
  42. package/dist/usePromptPackSelection.d.ts.map +1 -0
  43. package/dist/usePromptPackSelection.js +81 -0
  44. package/dist/usePromptPackSelectionByMode.d.ts +2 -1
  45. package/dist/usePromptPackSelectionByMode.d.ts.map +1 -1
  46. package/dist/usePromptPackSelectionByMode.js +35 -22
  47. package/dist/useRightRailTabs.js +1 -1
  48. package/dist/useSessionLogout.d.ts.map +1 -1
  49. package/dist/useSessionLogout.js +12 -1
  50. package/dist/useSessionRevocationPing.d.ts.map +1 -1
  51. package/dist/useSessionRevocationPing.js +15 -2
  52. package/dist/useShellPanelToggle.d.ts +7 -1
  53. package/dist/useShellPanelToggle.d.ts.map +1 -1
  54. package/dist/useShellPanelToggle.js +14 -6
  55. package/dist/workspaceSession.d.ts +36 -0
  56. package/dist/workspaceSession.d.ts.map +1 -0
  57. package/dist/workspaceSession.js +141 -0
  58. package/package.json +8 -8
@@ -1,119 +1,143 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  const resolveString = (value, fallback) => typeof value === "string" && value.length > 0 ? value : fallback;
3
- const modeKey = (mode, key) => `ui${key}_${mode}`;
4
3
  const resolveBoolean = (value, fallback) => typeof value === "boolean" ? value : fallback;
5
4
  const resolvePresetId = (value) => (typeof value === "string" ? value : null);
5
+ const resolveRightPanelTab = (value, fallback) => {
6
+ if (value === "stream")
7
+ return "feed";
8
+ return resolveString(value, fallback);
9
+ };
6
10
  const resolveDefaultPrimaryTab = (mode, defaultPrimaryTabByMode) => defaultPrimaryTabByMode?.[mode] ?? (mode === "imagine" ? "render" : "controls");
7
- export function useComposerUiState({ mode, sessionContext, setSessionContext, defaults, promptKitModes = ["imagine"], moduleKitModes = ["design", "map"], defaultPrimaryTabByMode, }) {
11
+ export function useComposerUiState({ mode, workspaceSession, setWorkspaceSession, defaults, promptKitModes = ["imagine"], moduleKitModes = ["design", "map"], defaultPrimaryTabByMode, }) {
8
12
  const modeDefaultTab = resolveDefaultPrimaryTab(mode, defaultPrimaryTabByMode);
9
- const initialPrimaryTab = resolveString(sessionContext?.[modeKey(mode, "primaryTab")] ?? sessionContext?.uiPrimaryTab, defaults?.primaryTab ?? modeDefaultTab);
10
- const initialPromptOpen = resolveBoolean(sessionContext?.[modeKey(mode, "promptKitOpen")] ?? sessionContext?.uiPromptKitOpen, defaults?.promptKitOpen ?? false);
11
- const initialModuleOpen = resolveBoolean(sessionContext?.[modeKey(mode, "moduleKitOpen")] ?? sessionContext?.uiModuleKitOpen, defaults?.moduleKitOpen ?? false);
12
- const initialPresetId = resolvePresetId(sessionContext?.[modeKey(mode, "activeImaginePresetId")] ??
13
- sessionContext?.uiActiveImaginePresetId) ?? defaults?.activeImaginePresetId ?? null;
14
- const initialControlOpen = resolveBoolean(sessionContext?.[modeKey(mode, "controlPanelOpen")] ??
15
- sessionContext?.uiControlPanelOpen, defaults?.controlPanelOpen ?? true);
16
- const initialRightOpen = resolveBoolean(sessionContext?.[modeKey(mode, "rightPanelOpen")] ?? sessionContext?.uiRightPanelOpen, defaults?.rightPanelOpen ?? false);
17
- const initialRightTab = resolveString(sessionContext?.[modeKey(mode, "rightPanelTab")] ?? sessionContext?.uiRightPanelTab, defaults?.rightPanelTab ?? "images");
18
- const [primaryTab, setPrimaryTabState] = useState(initialPrimaryTab);
19
- const [promptKitOpen, setPromptKitOpenState] = useState(initialPromptOpen);
20
- const [moduleKitOpen, setModuleKitOpenState] = useState(initialModuleOpen);
21
- const [activeImaginePresetId, setActiveImaginePresetIdState] = useState(initialPresetId);
22
- const [controlPanelOpen, setControlPanelOpenState] = useState(initialControlOpen);
23
- const [rightPanelOpen, setRightPanelOpenState] = useState(initialRightOpen);
24
- const [rightPanelTab, setRightPanelTabState] = useState(initialRightTab);
25
- const hydratedRef = useRef(false);
13
+ const resolveWorkspaceValue = useCallback(() => ({
14
+ primaryTab: resolveString(workspaceSession?.uiState.primaryTabByMode?.[mode], defaults?.primaryTab ?? modeDefaultTab),
15
+ promptKitOpen: resolveBoolean(workspaceSession?.uiState.promptKitOpenByMode?.[mode], defaults?.promptKitOpen ?? false),
16
+ moduleKitOpen: resolveBoolean(workspaceSession?.uiState.moduleKitOpenByMode?.[mode], defaults?.moduleKitOpen ?? false),
17
+ activeImaginePresetId: resolvePresetId(workspaceSession?.uiState.activeImaginePresetIdByMode?.[mode]) ??
18
+ defaults?.activeImaginePresetId ??
19
+ null,
20
+ autoGenerateEnabled: resolveBoolean(workspaceSession?.uiState.autoGenerateEnabledByMode?.[mode], defaults?.autoGenerateEnabled ?? false),
21
+ controlPanelOpen: resolveBoolean(workspaceSession?.uiState.controlPanelOpenByMode?.[mode], defaults?.controlPanelOpen ?? true),
22
+ rightPanelOpen: resolveBoolean(workspaceSession?.uiState.rightPanelOpenByMode?.[mode], defaults?.rightPanelOpen ?? false),
23
+ rightPanelTab: resolveRightPanelTab(workspaceSession?.uiState.rightPanelTabByMode?.[mode], defaults?.rightPanelTab ?? "feed"),
24
+ }), [defaults, mode, modeDefaultTab, workspaceSession]);
25
+ const initialValue = resolveWorkspaceValue();
26
+ const [primaryTab, setPrimaryTabState] = useState(initialValue.primaryTab);
27
+ const [promptKitOpen, setPromptKitOpenState] = useState(initialValue.promptKitOpen);
28
+ const [moduleKitOpen, setModuleKitOpenState] = useState(initialValue.moduleKitOpen);
29
+ const [activeImaginePresetId, setActiveImaginePresetIdState] = useState(initialValue.activeImaginePresetId);
30
+ const [autoGenerateEnabled, setAutoGenerateEnabledState] = useState(initialValue.autoGenerateEnabled);
31
+ const [controlPanelOpen, setControlPanelOpenState] = useState(initialValue.controlPanelOpen);
32
+ const [rightPanelOpen, setRightPanelOpenState] = useState(initialValue.rightPanelOpen);
33
+ const [rightPanelTab, setRightPanelTabState] = useState(initialValue.rightPanelTab);
34
+ const lastResolvedSignatureRef = useRef("");
26
35
  useEffect(() => {
27
- if (hydratedRef.current)
36
+ const next = resolveWorkspaceValue();
37
+ const nextSignature = JSON.stringify(next);
38
+ if (lastResolvedSignatureRef.current === nextSignature)
28
39
  return;
29
- hydratedRef.current = true;
30
- if (!sessionContext)
40
+ lastResolvedSignatureRef.current = nextSignature;
41
+ setPrimaryTabState(next.primaryTab);
42
+ setPromptKitOpenState(next.promptKitOpen);
43
+ setModuleKitOpenState(next.moduleKitOpen);
44
+ setActiveImaginePresetIdState(next.activeImaginePresetId);
45
+ setAutoGenerateEnabledState(next.autoGenerateEnabled);
46
+ setControlPanelOpenState(next.controlPanelOpen);
47
+ setRightPanelOpenState(next.rightPanelOpen);
48
+ setRightPanelTabState(next.rightPanelTab);
49
+ }, [resolveWorkspaceValue]);
50
+ const updateWorkspaceUiState = useCallback((updater) => {
51
+ if (!setWorkspaceSession)
31
52
  return;
32
- setPrimaryTabState(resolveString(sessionContext.uiPrimaryTab, initialPrimaryTab));
33
- setPromptKitOpenState(resolveBoolean(sessionContext.uiPromptKitOpen, initialPromptOpen));
34
- setModuleKitOpenState(resolveBoolean(sessionContext.uiModuleKitOpen, initialModuleOpen));
35
- setActiveImaginePresetIdState(resolvePresetId(sessionContext.uiActiveImaginePresetId) ?? initialPresetId);
36
- setControlPanelOpenState(resolveBoolean(sessionContext.uiControlPanelOpen, initialControlOpen));
37
- setRightPanelOpenState(resolveBoolean(sessionContext.uiRightPanelOpen, initialRightOpen));
38
- setRightPanelTabState(resolveString(sessionContext.uiRightPanelTab, initialRightTab));
39
- }, [
40
- initialControlOpen,
41
- initialModuleOpen,
42
- initialPresetId,
43
- initialPrimaryTab,
44
- initialPromptOpen,
45
- initialRightOpen,
46
- initialRightTab,
47
- sessionContext,
48
- ]);
49
- const setContext = useCallback((partial) => {
50
- if (!setSessionContext)
51
- return;
52
- setSessionContext(partial);
53
- }, [setSessionContext]);
53
+ setWorkspaceSession((session) => ({
54
+ ...session,
55
+ uiState: updater(session.uiState),
56
+ }));
57
+ }, [setWorkspaceSession]);
54
58
  const setPrimaryTab = useCallback((next) => {
55
59
  setPrimaryTabState(next);
56
- setContext({ uiPrimaryTab: next, [modeKey(mode, "primaryTab")]: next });
57
- }, [mode, setContext]);
60
+ updateWorkspaceUiState((uiState) => ({
61
+ ...uiState,
62
+ primaryTabByMode: { ...uiState.primaryTabByMode, [mode]: next },
63
+ }));
64
+ }, [mode, updateWorkspaceUiState]);
58
65
  const setPromptKitOpen = useCallback((next) => {
59
66
  setPromptKitOpenState(next);
60
- setContext({ uiPromptKitOpen: next, [modeKey(mode, "promptKitOpen")]: next });
61
- }, [mode, setContext]);
67
+ updateWorkspaceUiState((uiState) => ({
68
+ ...uiState,
69
+ promptKitOpenByMode: { ...uiState.promptKitOpenByMode, [mode]: next },
70
+ }));
71
+ }, [mode, updateWorkspaceUiState]);
62
72
  const setModuleKitOpen = useCallback((next) => {
63
73
  setModuleKitOpenState(next);
64
- setContext({ uiModuleKitOpen: next, [modeKey(mode, "moduleKitOpen")]: next });
65
- }, [mode, setContext]);
74
+ updateWorkspaceUiState((uiState) => ({
75
+ ...uiState,
76
+ moduleKitOpenByMode: { ...uiState.moduleKitOpenByMode, [mode]: next },
77
+ }));
78
+ }, [mode, updateWorkspaceUiState]);
66
79
  const setActiveImaginePresetId = useCallback((next) => {
67
80
  setActiveImaginePresetIdState(next);
68
- setContext({ uiActiveImaginePresetId: next, [modeKey(mode, "activeImaginePresetId")]: next });
69
- }, [mode, setContext]);
81
+ updateWorkspaceUiState((uiState) => ({
82
+ ...uiState,
83
+ activeImaginePresetIdByMode: { ...uiState.activeImaginePresetIdByMode, [mode]: next },
84
+ }));
85
+ }, [mode, updateWorkspaceUiState]);
86
+ const setAutoGenerateEnabled = useCallback((next) => {
87
+ setAutoGenerateEnabledState(next);
88
+ updateWorkspaceUiState((uiState) => ({
89
+ ...uiState,
90
+ autoGenerateEnabledByMode: { ...uiState.autoGenerateEnabledByMode, [mode]: next },
91
+ }));
92
+ }, [mode, updateWorkspaceUiState]);
70
93
  const setControlPanelOpen = useCallback((next) => {
71
94
  setControlPanelOpenState(next);
72
- setContext({ uiControlPanelOpen: next, [modeKey(mode, "controlPanelOpen")]: next });
73
- }, [mode, setContext]);
95
+ updateWorkspaceUiState((uiState) => ({
96
+ ...uiState,
97
+ controlPanelOpenByMode: { ...uiState.controlPanelOpenByMode, [mode]: next },
98
+ }));
99
+ }, [mode, updateWorkspaceUiState]);
74
100
  const setRightPanelOpen = useCallback((next) => {
75
101
  setRightPanelOpenState(next);
76
- setContext({ uiRightPanelOpen: next, [modeKey(mode, "rightPanelOpen")]: next });
77
- }, [mode, setContext]);
102
+ updateWorkspaceUiState((uiState) => ({
103
+ ...uiState,
104
+ rightPanelOpenByMode: { ...uiState.rightPanelOpenByMode, [mode]: next },
105
+ }));
106
+ }, [mode, updateWorkspaceUiState]);
78
107
  const setRightPanelTab = useCallback((next) => {
79
108
  setRightPanelTabState(next);
80
- setContext({ uiRightPanelTab: next, [modeKey(mode, "rightPanelTab")]: next });
81
- }, [mode, setContext]);
82
- const modeRef = useRef(null);
83
- useEffect(() => {
84
- if (modeRef.current === mode)
85
- return;
86
- modeRef.current = mode;
87
- if (!sessionContext)
88
- return;
89
- setPrimaryTabState(resolveString(sessionContext?.[modeKey(mode, "primaryTab")] ?? sessionContext?.uiPrimaryTab, resolveDefaultPrimaryTab(mode, defaultPrimaryTabByMode)));
90
- setPromptKitOpenState(resolveBoolean(sessionContext?.[modeKey(mode, "promptKitOpen")] ??
91
- sessionContext?.uiPromptKitOpen, false));
92
- setModuleKitOpenState(resolveBoolean(sessionContext?.[modeKey(mode, "moduleKitOpen")] ??
93
- sessionContext?.uiModuleKitOpen, false));
94
- setActiveImaginePresetIdState(resolvePresetId(sessionContext?.[modeKey(mode, "activeImaginePresetId")] ??
95
- sessionContext?.uiActiveImaginePresetId) ?? null);
96
- setControlPanelOpenState(resolveBoolean(sessionContext?.[modeKey(mode, "controlPanelOpen")] ??
97
- sessionContext?.uiControlPanelOpen, true));
98
- setRightPanelOpenState(resolveBoolean(sessionContext?.[modeKey(mode, "rightPanelOpen")] ??
99
- sessionContext?.uiRightPanelOpen, false));
100
- setRightPanelTabState(resolveString(sessionContext?.[modeKey(mode, "rightPanelTab")] ??
101
- sessionContext?.uiRightPanelTab, "images"));
102
- }, [defaultPrimaryTabByMode, mode, sessionContext]);
109
+ updateWorkspaceUiState((uiState) => ({
110
+ ...uiState,
111
+ rightPanelTabByMode: { ...uiState.rightPanelTabByMode, [mode]: next },
112
+ }));
113
+ }, [mode, updateWorkspaceUiState]);
103
114
  const gatedPromptKitOpen = useMemo(() => (promptKitModes.includes(mode) ? promptKitOpen : false), [mode, promptKitOpen, promptKitModes]);
104
115
  const gatedModuleKitOpen = useMemo(() => (moduleKitModes.includes(mode) ? moduleKitOpen : false), [mode, moduleKitOpen, moduleKitModes]);
116
+ const chooserOpen = gatedPromptKitOpen || gatedModuleKitOpen;
117
+ const setChooserOpen = useCallback((next) => {
118
+ if (promptKitModes.includes(mode)) {
119
+ setPromptKitOpen(next);
120
+ }
121
+ if (moduleKitModes.includes(mode)) {
122
+ setModuleKitOpen(next);
123
+ }
124
+ }, [mode, moduleKitModes, promptKitModes, setModuleKitOpen, setPromptKitOpen]);
105
125
  return {
106
126
  primaryTab,
107
127
  promptKitOpen: gatedPromptKitOpen,
108
128
  moduleKitOpen: gatedModuleKitOpen,
129
+ chooserOpen,
109
130
  activeImaginePresetId,
131
+ autoGenerateEnabled,
110
132
  controlPanelOpen,
111
133
  rightPanelOpen,
112
134
  rightPanelTab,
113
135
  setPrimaryTab,
114
136
  setPromptKitOpen,
115
137
  setModuleKitOpen,
138
+ setChooserOpen,
116
139
  setActiveImaginePresetId,
140
+ setAutoGenerateEnabled,
117
141
  setControlPanelOpen,
118
142
  setRightPanelOpen,
119
143
  setRightPanelTab,
@@ -0,0 +1,21 @@
1
+ import { type ComposerWorkspaceSession, type ComposerWorkspaceSessionUpdater } from './workspaceSession';
2
+ export type WorkspaceSessionRemoteStatus = 'idle' | 'loading' | 'ready' | 'error';
3
+ export type UseComposerWorkspaceSessionOptions<TDraft> = {
4
+ activeMode: string;
5
+ modeIds?: string[];
6
+ enabled?: boolean;
7
+ remoteEndpoint?: string;
8
+ storageKey?: string;
9
+ createDraft: () => TDraft;
10
+ restoreDraft: (raw: unknown) => TDraft;
11
+ };
12
+ export declare function useComposerWorkspaceSession<TDraft>({ activeMode, modeIds, enabled, remoteEndpoint, storageKey, createDraft, restoreDraft, }: UseComposerWorkspaceSessionOptions<TDraft>): {
13
+ workspaceSession: ComposerWorkspaceSession<TDraft>;
14
+ workspaceSessionHydrated: boolean;
15
+ remoteHydrated: boolean;
16
+ remoteStatus: WorkspaceSessionRemoteStatus;
17
+ remoteError: string | null;
18
+ setWorkspaceSession: (updater: ComposerWorkspaceSessionUpdater<TDraft>) => void;
19
+ clearWorkspaceSession: () => void;
20
+ };
21
+ //# sourceMappingURL=useComposerWorkspaceSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useComposerWorkspaceSession.d.ts","sourceRoot":"","sources":["../src/useComposerWorkspaceSession.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACrC,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,4BAA4B,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAElF,MAAM,MAAM,kCAAkC,CAAC,MAAM,IAAI;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;CACxC,CAAC;AA0CF,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,EAClD,UAAU,EACV,OAAY,EACZ,OAAe,EACf,cAAyC,EACzC,UAAgC,EAChC,WAAW,EACX,YAAY,GACb,EAAE,kCAAkC,CAAC,MAAM,CAAC,GAAG;IAC9C,gBAAgB,EAAE,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACnD,wBAAwB,EAAE,OAAO,CAAC;IAClC,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,4BAA4B,CAAC;IAC3C,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,mBAAmB,EAAE,CAAC,OAAO,EAAE,+BAA+B,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IAChF,qBAAqB,EAAE,MAAM,IAAI,CAAC;CACnC,CA2QA"}
@@ -0,0 +1,291 @@
1
+ 'use client';
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { createEmptyComposerWorkspaceSession, restoreComposerWorkspaceSession, } from './workspaceSession';
4
+ const DEFAULT_STORAGE_KEY = 'treasury:composer-workspace-session:v1';
5
+ const REMOTE_SAVE_DEBOUNCE_MS = 800;
6
+ const isAbortError = (error) => error instanceof Error && error.name === 'AbortError';
7
+ const readUpdatedAtMs = (session) => {
8
+ const parsed = Date.parse(session.updatedAt ?? '');
9
+ return Number.isFinite(parsed) ? parsed : null;
10
+ };
11
+ const shouldPreferLocalSession = (localSession, remoteSession) => {
12
+ const localUpdatedAtMs = readUpdatedAtMs(localSession);
13
+ const remoteUpdatedAtMs = readUpdatedAtMs(remoteSession);
14
+ if (localUpdatedAtMs !== null && remoteUpdatedAtMs !== null) {
15
+ return localUpdatedAtMs > remoteUpdatedAtMs;
16
+ }
17
+ if (localUpdatedAtMs !== null && remoteUpdatedAtMs === null) {
18
+ return true;
19
+ }
20
+ return false;
21
+ };
22
+ const resolveRemoteError = (payload, status, fallback) => {
23
+ if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
24
+ const record = payload;
25
+ const error = record.error;
26
+ if (typeof error === 'string' && error.trim())
27
+ return error.trim();
28
+ if (error && typeof error === 'object' && !Array.isArray(error)) {
29
+ const message = error.message;
30
+ if (typeof message === 'string' && message.trim())
31
+ return message.trim();
32
+ }
33
+ if (typeof record.message === 'string' && record.message.trim())
34
+ return record.message.trim();
35
+ }
36
+ return `${fallback} (${status || 0})`;
37
+ };
38
+ export function useComposerWorkspaceSession({ activeMode, modeIds = [], enabled = false, remoteEndpoint = '/api/workspace/session', storageKey = DEFAULT_STORAGE_KEY, createDraft, restoreDraft, }) {
39
+ const restoreOptions = useMemo(() => ({
40
+ createDraft,
41
+ restoreDraft,
42
+ activeMode,
43
+ modeIds,
44
+ }), [activeMode, createDraft, modeIds, restoreDraft]);
45
+ const normalizeSession = useCallback((value) => restoreComposerWorkspaceSession(value, restoreOptions), [restoreOptions]);
46
+ const createEmptySession = useCallback(() => createEmptyComposerWorkspaceSession(createDraft, { activeMode, modeIds }), [activeMode, createDraft, modeIds]);
47
+ const [workspaceSession, setWorkspaceSessionState] = useState(() => createEmptyComposerWorkspaceSession(createDraft, { activeMode, modeIds }));
48
+ const [workspaceSessionHydrated, setWorkspaceSessionHydrated] = useState(false);
49
+ const [remoteHydrated, setRemoteHydrated] = useState(!enabled);
50
+ const [remoteStatus, setRemoteStatus] = useState(enabled ? 'loading' : 'idle');
51
+ const [remoteError, setRemoteError] = useState(null);
52
+ const lastSyncedSignatureRef = useRef('');
53
+ const remoteSaveTimerRef = useRef(null);
54
+ const setWorkspaceSession = useCallback((updater) => {
55
+ setWorkspaceSessionState((prev) => {
56
+ const normalizedPrev = normalizeSession(prev);
57
+ const nextCandidate = updater(normalizedPrev);
58
+ const normalizedNext = normalizeSession({
59
+ ...nextCandidate,
60
+ activeMode: nextCandidate.activeMode || activeMode,
61
+ });
62
+ const previousSignature = JSON.stringify(normalizedPrev);
63
+ const nextSignature = JSON.stringify(normalizedNext);
64
+ if (previousSignature === nextSignature) {
65
+ return prev;
66
+ }
67
+ return {
68
+ ...normalizedNext,
69
+ updatedAt: new Date().toISOString(),
70
+ };
71
+ });
72
+ }, [activeMode, normalizeSession]);
73
+ const clearWorkspaceSession = useCallback(() => {
74
+ const next = createEmptySession();
75
+ const nextSignature = JSON.stringify(normalizeSession(next));
76
+ if (typeof window !== 'undefined') {
77
+ window.sessionStorage.removeItem(storageKey);
78
+ }
79
+ setWorkspaceSessionState(next);
80
+ setRemoteError(null);
81
+ lastSyncedSignatureRef.current = '';
82
+ if (!enabled) {
83
+ setRemoteStatus('idle');
84
+ setRemoteHydrated(true);
85
+ lastSyncedSignatureRef.current = nextSignature;
86
+ return;
87
+ }
88
+ setRemoteStatus('loading');
89
+ setRemoteHydrated(false);
90
+ void (async () => {
91
+ try {
92
+ const response = await fetch(remoteEndpoint, {
93
+ method: 'DELETE',
94
+ cache: 'no-store',
95
+ credentials: 'same-origin',
96
+ });
97
+ const payload = await response.json().catch(() => null);
98
+ if (!response.ok || !payload?.success) {
99
+ throw new Error(resolveRemoteError(payload, response.status, 'Workspace session delete failed'));
100
+ }
101
+ lastSyncedSignatureRef.current = nextSignature;
102
+ setRemoteStatus('ready');
103
+ setRemoteError(null);
104
+ }
105
+ catch (error) {
106
+ setRemoteStatus('error');
107
+ setRemoteError(error instanceof Error ? error.message : 'Workspace session delete failed');
108
+ }
109
+ finally {
110
+ setRemoteHydrated(true);
111
+ }
112
+ })();
113
+ }, [createEmptySession, enabled, normalizeSession, remoteEndpoint, storageKey]);
114
+ useEffect(() => {
115
+ if (typeof window === 'undefined') {
116
+ setWorkspaceSessionHydrated(true);
117
+ return;
118
+ }
119
+ try {
120
+ const raw = window.sessionStorage.getItem(storageKey);
121
+ if (!raw) {
122
+ setWorkspaceSessionState(createEmptySession());
123
+ }
124
+ else {
125
+ const parsed = JSON.parse(raw);
126
+ setWorkspaceSessionState(normalizeSession(parsed));
127
+ }
128
+ }
129
+ catch {
130
+ setWorkspaceSessionState(createEmptySession());
131
+ }
132
+ finally {
133
+ setWorkspaceSessionHydrated(true);
134
+ }
135
+ }, [createEmptySession, normalizeSession, storageKey]);
136
+ useEffect(() => {
137
+ if (!workspaceSessionHydrated)
138
+ return;
139
+ setWorkspaceSession((session) => {
140
+ if (session.activeMode === activeMode)
141
+ return session;
142
+ return { ...session, activeMode };
143
+ });
144
+ }, [activeMode, setWorkspaceSession, workspaceSessionHydrated]);
145
+ useEffect(() => {
146
+ if (!workspaceSessionHydrated || typeof window === 'undefined')
147
+ return;
148
+ try {
149
+ window.sessionStorage.setItem(storageKey, JSON.stringify(workspaceSession));
150
+ }
151
+ catch {
152
+ // Ignore crash-cache persistence failures.
153
+ }
154
+ }, [storageKey, workspaceSession, workspaceSessionHydrated]);
155
+ useEffect(() => {
156
+ if (!workspaceSessionHydrated)
157
+ return;
158
+ if (!enabled) {
159
+ setRemoteHydrated(true);
160
+ setRemoteStatus('idle');
161
+ setRemoteError(null);
162
+ return;
163
+ }
164
+ let cancelled = false;
165
+ const controller = new AbortController();
166
+ setRemoteHydrated(false);
167
+ setRemoteStatus('loading');
168
+ setRemoteError(null);
169
+ const loadRemoteWorkspaceSession = async () => {
170
+ try {
171
+ const response = await fetch(remoteEndpoint, {
172
+ method: 'GET',
173
+ cache: 'no-store',
174
+ credentials: 'same-origin',
175
+ signal: controller.signal,
176
+ });
177
+ const payload = await response.json().catch(() => null);
178
+ if (!response.ok || !payload?.success) {
179
+ throw new Error(resolveRemoteError(payload, response.status, 'Workspace session load failed'));
180
+ }
181
+ if (cancelled)
182
+ return;
183
+ const remoteSessionRaw = payload?.session ?? null;
184
+ setWorkspaceSessionState((prev) => {
185
+ const localSession = normalizeSession(prev);
186
+ if (!remoteSessionRaw) {
187
+ return localSession;
188
+ }
189
+ const remoteSession = normalizeSession(remoteSessionRaw);
190
+ if (shouldPreferLocalSession(localSession, remoteSession)) {
191
+ return localSession;
192
+ }
193
+ lastSyncedSignatureRef.current = JSON.stringify(remoteSession);
194
+ const localSignature = JSON.stringify(localSession);
195
+ const remoteSignature = lastSyncedSignatureRef.current;
196
+ return localSignature === remoteSignature ? prev : remoteSession;
197
+ });
198
+ setRemoteStatus('ready');
199
+ setRemoteError(null);
200
+ }
201
+ catch (error) {
202
+ if (cancelled || isAbortError(error))
203
+ return;
204
+ setRemoteStatus('error');
205
+ setRemoteError(error instanceof Error ? error.message : 'Workspace session load failed');
206
+ }
207
+ finally {
208
+ if (!cancelled) {
209
+ setRemoteHydrated(true);
210
+ }
211
+ }
212
+ };
213
+ void loadRemoteWorkspaceSession();
214
+ return () => {
215
+ cancelled = true;
216
+ controller.abort();
217
+ };
218
+ }, [enabled, normalizeSession, remoteEndpoint, workspaceSessionHydrated]);
219
+ useEffect(() => {
220
+ if (!workspaceSessionHydrated)
221
+ return;
222
+ if (!enabled || !remoteHydrated)
223
+ return;
224
+ const normalizedSession = normalizeSession(workspaceSession);
225
+ const nextSignature = JSON.stringify(normalizedSession);
226
+ if (nextSignature === lastSyncedSignatureRef.current) {
227
+ return;
228
+ }
229
+ if (remoteSaveTimerRef.current) {
230
+ clearTimeout(remoteSaveTimerRef.current);
231
+ remoteSaveTimerRef.current = null;
232
+ }
233
+ remoteSaveTimerRef.current = setTimeout(() => {
234
+ const controller = new AbortController();
235
+ const persistRemoteWorkspaceSession = async () => {
236
+ try {
237
+ const response = await fetch(remoteEndpoint, {
238
+ method: 'PUT',
239
+ cache: 'no-store',
240
+ credentials: 'same-origin',
241
+ headers: {
242
+ 'Content-Type': 'application/json',
243
+ },
244
+ body: JSON.stringify({ session: normalizedSession }),
245
+ signal: controller.signal,
246
+ });
247
+ const payload = await response.json().catch(() => null);
248
+ if (!response.ok || !payload?.success) {
249
+ throw new Error(resolveRemoteError(payload, response.status, 'Workspace session save failed'));
250
+ }
251
+ const savedSession = normalizeSession(payload?.session ?? normalizedSession);
252
+ const savedSignature = JSON.stringify(savedSession);
253
+ lastSyncedSignatureRef.current = savedSignature;
254
+ setRemoteStatus('ready');
255
+ setRemoteError(null);
256
+ setWorkspaceSessionState((prev) => {
257
+ const localSession = normalizeSession(prev);
258
+ const localSignature = JSON.stringify(localSession);
259
+ if (localSignature === savedSignature || shouldPreferLocalSession(localSession, savedSession)) {
260
+ return prev;
261
+ }
262
+ return savedSession;
263
+ });
264
+ }
265
+ catch (error) {
266
+ if (isAbortError(error))
267
+ return;
268
+ setRemoteStatus('error');
269
+ setRemoteError(error instanceof Error ? error.message : 'Workspace session save failed');
270
+ }
271
+ };
272
+ void persistRemoteWorkspaceSession();
273
+ return () => controller.abort();
274
+ }, REMOTE_SAVE_DEBOUNCE_MS);
275
+ return () => {
276
+ if (remoteSaveTimerRef.current) {
277
+ clearTimeout(remoteSaveTimerRef.current);
278
+ remoteSaveTimerRef.current = null;
279
+ }
280
+ };
281
+ }, [enabled, normalizeSession, remoteEndpoint, remoteHydrated, workspaceSession, workspaceSessionHydrated]);
282
+ return {
283
+ workspaceSession,
284
+ workspaceSessionHydrated,
285
+ remoteHydrated,
286
+ remoteStatus,
287
+ remoteError,
288
+ setWorkspaceSession,
289
+ clearWorkspaceSession,
290
+ };
291
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"useGrasshopperSession.d.ts","sourceRoot":"","sources":["../src/useGrasshopperSession.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,YAAY,EACb,MAAM,6BAA6B,CAAC;AAQrC,KAAK,iBAAiB,GAAG,gBAAgB,GAAG;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAiGF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;QACnB,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;QAC5B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;IACnD,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,iBAAiB,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAClD,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACrD,KAAK,EAAE,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;IAC5F,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EACpC,QAA0B,EAC1B,OAAO,EACP,kBAAkB,EAClB,UAAU,EACV,OAAO,GACR,GAAE,yBAA8B,GAAG,uBAAuB,CAgI1D"}
1
+ {"version":3,"file":"useGrasshopperSession.d.ts","sourceRoot":"","sources":["../src/useGrasshopperSession.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,YAAY,EACb,MAAM,6BAA6B,CAAC;AAQrC,KAAK,iBAAiB,GAAG,gBAAgB,GAAG;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAuGF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;QACnB,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;QAC5B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;IACnD,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,iBAAiB,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAClD,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACrD,KAAK,EAAE,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;IAC5F,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EACpC,QAA0B,EAC1B,OAAO,EACP,kBAAkB,EAClB,UAAU,EACV,OAAO,GACR,GAAE,yBAA8B,GAAG,uBAAuB,CAgI1D"}
@@ -2,6 +2,8 @@ import { useCallback, useMemo, useRef, useState } from "react";
2
2
  import { buildGrasshopperDefaults, buildGrasshopperValues, solveGrasshopperFile, warmGrasshopperSolver, } from "@treasuryspatial/viewer-kit";
3
3
  const sanitizeGhKey = (value) => value.replace(/[^a-zA-Z0-9_-]/g, "_");
4
4
  const resolveGhParamName = (input, index) => {
5
+ if (input.paramName)
6
+ return input.paramName;
5
7
  if (input.name)
6
8
  return input.name;
7
9
  if (input.nickname)
@@ -56,10 +58,14 @@ const buildGhPanelField = (input, index) => {
56
58
  const label = resolveGhLabel(input, index);
57
59
  const type = (input.type ?? "").toLowerCase();
58
60
  const options = Array.isArray(input.options)
59
- ? input.options.map((option, optionIndex) => ({
60
- value: option?.value ?? optionIndex,
61
- label: option?.label ?? String(option?.value ?? optionIndex),
62
- }))
61
+ ? input.options.map((option, optionIndex) => {
62
+ const optionRecord = typeof option === "object" && option !== null ? option : null;
63
+ const value = optionRecord?.value ?? optionIndex;
64
+ return {
65
+ value: typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : optionIndex,
66
+ label: typeof optionRecord?.label === "string" ? optionRecord.label : String(value),
67
+ };
68
+ })
63
69
  : undefined;
64
70
  if (options && options.length > 0) {
65
71
  return { key, label, control: "select", options };
@@ -1 +1 @@
1
- {"version":3,"file":"useModeShellState.d.ts","sourceRoot":"","sources":["../src/useModeShellState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGtE,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAWF,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,MAAM,EACN,gBAAuB,EACvB,kBAA0B,EAC1B,oBAA4B,EAC5B,OAAc,GACf,EAAE,wBAAwB;;;;;;;;;;EA0E1B"}
1
+ {"version":3,"file":"useModeShellState.d.ts","sourceRoot":"","sources":["../src/useModeShellState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAKtE,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAWF,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,MAAM,EACN,gBAAuB,EACvB,kBAA0B,EAC1B,oBAA4B,EAC5B,OAAc,GACf,EAAE,wBAAwB;;;;;;;;;;EAyH1B"}