@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.
- package/dist/AccessAssetsMenu.d.ts.map +1 -1
- package/dist/AccessAssetsMenu.js +53 -45
- package/dist/AssetUploadCard.d.ts +2 -1
- package/dist/AssetUploadCard.d.ts.map +1 -1
- package/dist/AssetUploadCard.js +3 -2
- package/dist/ViewControls.d.ts +19 -2
- package/dist/ViewControls.d.ts.map +1 -1
- package/dist/ViewControls.js +65 -16
- package/dist/ViewerCanvas.d.ts.map +1 -1
- package/dist/ViewerCanvas.js +15 -40
- package/dist/composerRouting.d.ts +6 -0
- package/dist/composerRouting.d.ts.map +1 -1
- package/dist/composerRouting.js +18 -9
- package/dist/index.d.ts +13 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/useAdminGallery.d.ts +2 -30
- package/dist/useAdminGallery.d.ts.map +1 -1
- package/dist/useAdminGallery.js +15 -6
- package/dist/useAdminImageStream.d.ts +3 -31
- package/dist/useAdminImageStream.d.ts.map +1 -1
- package/dist/useAdminImageStream.js +22 -10
- package/dist/useComposerAccessGate.js +4 -4
- package/dist/useComposerAuthState.d.ts +1 -0
- package/dist/useComposerAuthState.d.ts.map +1 -1
- package/dist/useComposerAuthState.js +4 -1
- package/dist/useComposerModeDraftState.d.ts +21 -0
- package/dist/useComposerModeDraftState.d.ts.map +1 -0
- package/dist/useComposerModeDraftState.js +89 -0
- package/dist/useComposerUiState.d.ts +11 -4
- package/dist/useComposerUiState.d.ts.map +1 -1
- package/dist/useComposerUiState.js +103 -79
- package/dist/useComposerWorkspaceSession.d.ts +21 -0
- package/dist/useComposerWorkspaceSession.d.ts.map +1 -0
- package/dist/useComposerWorkspaceSession.js +291 -0
- package/dist/useGrasshopperSession.d.ts.map +1 -1
- package/dist/useGrasshopperSession.js +10 -4
- package/dist/useModeShellState.d.ts.map +1 -1
- package/dist/useModeShellState.js +52 -6
- package/dist/usePromptPackSelection.d.ts +20 -0
- package/dist/usePromptPackSelection.d.ts.map +1 -0
- package/dist/usePromptPackSelection.js +81 -0
- package/dist/usePromptPackSelectionByMode.d.ts +2 -1
- package/dist/usePromptPackSelectionByMode.d.ts.map +1 -1
- package/dist/usePromptPackSelectionByMode.js +35 -22
- package/dist/useRightRailTabs.js +1 -1
- package/dist/useSessionLogout.d.ts.map +1 -1
- package/dist/useSessionLogout.js +12 -1
- package/dist/useSessionRevocationPing.d.ts.map +1 -1
- package/dist/useSessionRevocationPing.js +15 -2
- package/dist/useShellPanelToggle.d.ts +7 -1
- package/dist/useShellPanelToggle.d.ts.map +1 -1
- package/dist/useShellPanelToggle.js +14 -6
- package/dist/workspaceSession.d.ts +36 -0
- package/dist/workspaceSession.d.ts.map +1 -0
- package/dist/workspaceSession.js +141 -0
- 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,
|
|
11
|
+
export function useComposerUiState({ mode, workspaceSession, setWorkspaceSession, defaults, promptKitModes = ["imagine"], moduleKitModes = ["design", "map"], defaultPrimaryTabByMode, }) {
|
|
8
12
|
const modeDefaultTab = resolveDefaultPrimaryTab(mode, defaultPrimaryTabByMode);
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const [
|
|
23
|
-
const [
|
|
24
|
-
const [
|
|
25
|
-
const
|
|
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
|
-
|
|
36
|
+
const next = resolveWorkspaceValue();
|
|
37
|
+
const nextSignature = JSON.stringify(next);
|
|
38
|
+
if (lastResolvedSignatureRef.current === nextSignature)
|
|
28
39
|
return;
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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;
|
|
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
|
-
|
|
61
|
-
|
|
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;
|
|
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"}
|