@konitif/workbench-runtime 0.284.1

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 (57) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +60 -0
  3. package/dist/boot/createDemoWorkbenchBootGraph.d.ts +9 -0
  4. package/dist/boot/createDemoWorkbenchBootGraph.js +180 -0
  5. package/dist/boot/createLegacyWorkbenchBootGraph.d.ts +34 -0
  6. package/dist/boot/createLegacyWorkbenchBootGraph.js +321 -0
  7. package/dist/boot/createWorkbenchBootSession.d.ts +23 -0
  8. package/dist/boot/createWorkbenchBootSession.js +90 -0
  9. package/dist/boot/index.d.ts +3 -0
  10. package/dist/boot/index.js +3 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.js +9 -0
  13. package/dist/runtime.d.ts +7 -0
  14. package/dist/runtime.js +2 -0
  15. package/dist/workbench/browserDetachedWindowHost.d.ts +3 -0
  16. package/dist/workbench/browserDetachedWindowHost.js +69 -0
  17. package/dist/workbench/browserWorkbenchHostEvents.d.ts +3 -0
  18. package/dist/workbench/browserWorkbenchHostEvents.js +35 -0
  19. package/dist/workbench/createWorkbenchStore.d.ts +129 -0
  20. package/dist/workbench/createWorkbenchStore.js +50 -0
  21. package/dist/workbench/createWorkbenchStoreRuntime.d.ts +154 -0
  22. package/dist/workbench/createWorkbenchStoreRuntime.js +242 -0
  23. package/dist/workbench/createWorkspaceModeController.d.ts +36 -0
  24. package/dist/workbench/createWorkspaceModeController.js +168 -0
  25. package/dist/workbench/workbenchDetachedWindowHost.d.ts +16 -0
  26. package/dist/workbench/workbenchDetachedWindowHost.js +1 -0
  27. package/dist/workbench/workbenchFocusPersistence.d.ts +15 -0
  28. package/dist/workbench/workbenchFocusPersistence.js +44 -0
  29. package/dist/workbench/workbenchHistoryActions.d.ts +21 -0
  30. package/dist/workbench/workbenchHistoryActions.js +45 -0
  31. package/dist/workbench/workbenchHistoryRuntime.d.ts +16 -0
  32. package/dist/workbench/workbenchHistoryRuntime.js +42 -0
  33. package/dist/workbench/workbenchHostEvents.d.ts +13 -0
  34. package/dist/workbench/workbenchHostEvents.js +1 -0
  35. package/dist/workbench/workbenchLayoutActions.d.ts +84 -0
  36. package/dist/workbench/workbenchLayoutActions.js +191 -0
  37. package/dist/workbench/workbenchPersistenceRuntime.d.ts +39 -0
  38. package/dist/workbench/workbenchPersistenceRuntime.js +168 -0
  39. package/dist/workbench/workbenchShellActions.d.ts +24 -0
  40. package/dist/workbench/workbenchShellActions.js +63 -0
  41. package/dist/workbench/workbenchShellPersistence.d.ts +18 -0
  42. package/dist/workbench/workbenchShellPersistence.js +124 -0
  43. package/dist/workbench/workbenchStateFactory.d.ts +22 -0
  44. package/dist/workbench/workbenchStateFactory.js +47 -0
  45. package/dist/workbench/workbenchToolRuntimeActions.d.ts +23 -0
  46. package/dist/workbench/workbenchToolRuntimeActions.js +98 -0
  47. package/dist/workbench/workbenchToolRuntimeUiStore.d.ts +17 -0
  48. package/dist/workbench/workbenchToolRuntimeUiStore.js +85 -0
  49. package/dist/workbench/workbenchWindowRuntime.d.ts +19 -0
  50. package/dist/workbench/workbenchWindowRuntime.js +85 -0
  51. package/dist/workbench/workbenchWorkspaceActions.d.ts +30 -0
  52. package/dist/workbench/workbenchWorkspaceActions.js +55 -0
  53. package/dist/workbench/workspaceHistoryController.d.ts +28 -0
  54. package/dist/workbench/workspaceHistoryController.js +126 -0
  55. package/dist/workbench/workspaceSyncController.d.ts +20 -0
  56. package/dist/workbench/workspaceSyncController.js +105 -0
  57. package/package.json +49 -0
@@ -0,0 +1,84 @@
1
+ import { createWorkspaceSessionState, type LayoutDockSide, type LayoutDockTarget, type LayoutEdge, type LayoutInteractionState, type LayoutMenuActionId, type LayoutMenuActionSelection, type LayoutMenuTarget, type ShellState, type SplitOrientation, type ToolCatalog, type Workspace, type WorkspaceFocus } from '@konitif/workbench/workspace-contracts';
2
+ import type { WorkbenchHistoryScope } from "./workspaceHistoryController.js";
3
+ export interface WorkbenchLayoutActionState {
4
+ workspace: Workspace;
5
+ focus: WorkspaceFocus;
6
+ shell: ShellState;
7
+ layoutInteraction: LayoutInteractionState;
8
+ }
9
+ export interface CreateWorkbenchLayoutActionsInput<TState extends WorkbenchLayoutActionState> {
10
+ toolCatalog: ToolCatalog;
11
+ getWorkbenchState(): TState;
12
+ createWorkbenchState(workspaceSession: ReturnType<typeof createWorkspaceSessionState>, shell: ShellState, layoutInteraction: LayoutInteractionState): TState;
13
+ updateWorkbenchState(updater: (state: TState) => TState, historyScope?: WorkbenchHistoryScope): void;
14
+ }
15
+ export declare function createWorkbenchLayoutActions<TState extends WorkbenchLayoutActionState>(input: CreateWorkbenchLayoutActionsInput<TState>): {
16
+ resizeSplit(splitId: string, sizes: [number, number]): void;
17
+ resizeSplitBoundary(rootSplitId: string, boundaryIndex: number, deltaRatio: number, mode?: "local" | "proportional"): void;
18
+ collapseSplit(splitId: string, removeChildIndex: 0 | 1): void;
19
+ collapseSplitBoundary(rootSplitId: string, boundaryIndex: number, removeSide: "start" | "end"): void;
20
+ openLayoutSplitMenu(panelId: string | null, edge: LayoutEdge, anchor: {
21
+ x: number;
22
+ y: number;
23
+ }, targets?: LayoutMenuTarget[], options?: {
24
+ source?: "workspace-edge" | "split-boundary" | "panel-menu";
25
+ initialActionId?: LayoutMenuActionId | null;
26
+ }): void;
27
+ selectLayoutSplitOrientation(orientation: SplitOrientation): void;
28
+ selectLayoutMenuAction(selection: LayoutMenuActionSelection | null): void;
29
+ hoverLayoutSplitSide(side: LayoutDockSide | null): void;
30
+ confirmLayoutSplitSide(side: LayoutDockSide): void;
31
+ updateLayoutSplitPreview(panelId: string | null, pointerRatio: number): void;
32
+ adjustLayoutSplitPreviewCuts(delta: number): void;
33
+ startLayoutSplitPreview(panelId: string, edge: LayoutEdge, orientation: SplitOrientation, cuts: number, pointerRatio: number): void;
34
+ startBoundaryPull(params: {
35
+ windowId?: string;
36
+ anchor: {
37
+ x: number;
38
+ y: number;
39
+ };
40
+ horizontalEdge?: "left" | "right" | null;
41
+ verticalEdge?: "top" | "bottom" | null;
42
+ source?: "edge" | "corner";
43
+ neutralThreshold?: number;
44
+ creationThreshold?: number;
45
+ }): void;
46
+ updateBoundaryPull(pointer: {
47
+ x: number;
48
+ y: number;
49
+ }, viewport: {
50
+ width: number;
51
+ height: number;
52
+ }): void;
53
+ commitBoundaryPull(): void;
54
+ startIntersectionResize(params: {
55
+ anchor: {
56
+ x: number;
57
+ y: number;
58
+ };
59
+ columnSplitId: string;
60
+ rowSplitId: string;
61
+ columnBaseSizes: [number, number];
62
+ rowBaseSizes: [number, number];
63
+ }): void;
64
+ updateIntersectionResize(pointer: {
65
+ x: number;
66
+ y: number;
67
+ }, dimensions: {
68
+ width: number;
69
+ height: number;
70
+ }): void;
71
+ commitIntersectionResize(): void;
72
+ startPanelDrag(panelId: string, sourceStackId: string, anchor: {
73
+ x: number;
74
+ y: number;
75
+ }): void;
76
+ updatePanelDrag(pointer: {
77
+ x: number;
78
+ y: number;
79
+ }, hoveredTarget: LayoutDockTarget | null): void;
80
+ commitPanelDock(): void;
81
+ commitLayoutSubdivideSelection(panelId: string, edge: LayoutEdge, orientation: SplitOrientation, cuts: number, pointerRatio: number): void;
82
+ commitLayoutSplitPreview(): void;
83
+ cancelLayoutInteraction(): void;
84
+ };
@@ -0,0 +1,191 @@
1
+ import { adjustLayoutSplitPreviewCuts, commitBoundaryPull, commitIntersectionResize, commitLayoutSplitPreview, commitLayoutSubdivideSelection, commitPanelDock, collapseSplit, collapseSplitBoundary, confirmLayoutSplitSide, createPeripheralBands, createWorkspaceSessionState, dismissLayoutInteraction, dispatchWorkspaceCommand, dockPanel, findPanelInWorkspace, getPreferredFocusedPanelId, joinPanelArea, openLayoutSplitMenu, previewLayoutSplitSide, resizeSplit, resizeSplitBoundary, selectLayoutMenuAction, selectLayoutSplitOrientation, startBoundaryPull, startIntersectionResize, startLayoutSplitPreview, startPanelDrag, subdividePanel, swapPanelArea, updateBoundaryPull, updateIntersectionResize, updateLayoutSplitPreview, updatePanelDrag } from '@konitif/workbench/workspace-contracts';
2
+ export function createWorkbenchLayoutActions(input) {
3
+ function resolveLayoutMenuHistoryScope(state) {
4
+ if (state.layoutInteraction.mode === 'split-menu') {
5
+ return state.layoutInteraction.source === 'panel-menu' ? 'app' : 'core';
6
+ }
7
+ if (state.layoutInteraction.mode === 'split-side-pick' || state.layoutInteraction.mode === 'split-preview') {
8
+ return state.layoutInteraction.panelId ? 'app' : 'core';
9
+ }
10
+ return 'core';
11
+ }
12
+ function applyLayoutInteractionResolution(state, resolution) {
13
+ if (resolution.kind === 'state') {
14
+ return {
15
+ ...state,
16
+ layoutInteraction: resolution.state
17
+ };
18
+ }
19
+ if (resolution.kind === 'commit-subdivide') {
20
+ const result = subdividePanel(state.workspace, resolution.subdivide);
21
+ const nextFocusedPanelId = resolution.subdivide.edge === 'left' || resolution.subdivide.edge === 'top'
22
+ ? result.createdPanelIds[0] ?? state.focus.activePanelId
23
+ : result.createdPanelIds[result.createdPanelIds.length - 1] ?? state.focus.activePanelId;
24
+ return input.createWorkbenchState(createWorkspaceSessionState(result.workspace, { activePanelId: nextFocusedPanelId ?? null }), state.shell, resolution.state);
25
+ }
26
+ if (resolution.kind === 'commit-boundary-pull') {
27
+ const result = createPeripheralBands(state.workspace, resolution.boundaryPull);
28
+ const nextFocusedPanelId = result.createdPanelIds[result.createdPanelIds.length - 1] ?? state.focus.activePanelId;
29
+ return input.createWorkbenchState(createWorkspaceSessionState(result.workspace, { activePanelId: nextFocusedPanelId ?? null }), state.shell, resolution.state);
30
+ }
31
+ if (resolution.kind === 'commit-intersection-resize') {
32
+ const nextWorkspace = resizeSplit(resizeSplit(state.workspace, {
33
+ splitId: resolution.intersectionResize.columnSplitId,
34
+ sizes: resolution.intersectionResize.columnSizes
35
+ }), {
36
+ splitId: resolution.intersectionResize.rowSplitId,
37
+ sizes: resolution.intersectionResize.rowSizes
38
+ });
39
+ return input.createWorkbenchState(createWorkspaceSessionState(nextWorkspace, state.focus), state.shell, resolution.state);
40
+ }
41
+ if (resolution.kind === 'commit-swap') {
42
+ return input.createWorkbenchState(createWorkspaceSessionState(swapPanelArea(state.workspace, resolution.swap), state.focus), state.shell, resolution.state);
43
+ }
44
+ if (resolution.kind === 'commit-join') {
45
+ const nextWorkspace = joinPanelArea(state.workspace, resolution.join);
46
+ const nextFocusedPanelId = resolution.join.siblingAreaPanelIds.find((panelId) => findPanelInWorkspace(nextWorkspace, panelId)) ??
47
+ (findPanelInWorkspace(nextWorkspace, state.focus.activePanelId ?? '')
48
+ ? state.focus.activePanelId
49
+ : getPreferredFocusedPanelId(nextWorkspace));
50
+ return input.createWorkbenchState(createWorkspaceSessionState(nextWorkspace, { activePanelId: nextFocusedPanelId ?? null }), state.shell, resolution.state);
51
+ }
52
+ if (resolution.kind === 'commit-dock') {
53
+ const nextWorkspace = dockPanel(state.workspace, resolution.dock);
54
+ return input.createWorkbenchState(createWorkspaceSessionState(nextWorkspace, { activePanelId: resolution.dock.panelId }), state.shell, resolution.state);
55
+ }
56
+ return input.createWorkbenchState(dispatchWorkspaceCommand(state, {
57
+ type: 'split-panel-to-side',
58
+ orientation: resolution.split.orientation,
59
+ side: resolution.split.side,
60
+ panelId: resolution.split.panelId ?? undefined
61
+ }, { toolCatalog: input.toolCatalog }), state.shell, resolution.state);
62
+ }
63
+ return {
64
+ resizeSplit(splitId, sizes) {
65
+ input.updateWorkbenchState((state) => input.createWorkbenchState(createWorkspaceSessionState(resizeSplit(state.workspace, { splitId, sizes }), state.focus), state.shell, state.layoutInteraction), 'core');
66
+ },
67
+ resizeSplitBoundary(rootSplitId, boundaryIndex, deltaRatio, mode = 'local') {
68
+ input.updateWorkbenchState((state) => input.createWorkbenchState(createWorkspaceSessionState(resizeSplitBoundary(state.workspace, { rootSplitId, boundaryIndex, deltaRatio, mode }), state.focus), state.shell, state.layoutInteraction), 'core');
69
+ },
70
+ collapseSplit(splitId, removeChildIndex) {
71
+ input.updateWorkbenchState((state) => {
72
+ const nextWorkspace = collapseSplit(state.workspace, { splitId, removeChildIndex });
73
+ const nextFocusedPanelId = findPanelInWorkspace(nextWorkspace, state.focus.activePanelId ?? '')
74
+ ? state.focus.activePanelId
75
+ : getPreferredFocusedPanelId(nextWorkspace);
76
+ return input.createWorkbenchState(createWorkspaceSessionState(nextWorkspace, { activePanelId: nextFocusedPanelId ?? null }), state.shell, state.layoutInteraction);
77
+ }, 'core');
78
+ },
79
+ collapseSplitBoundary(rootSplitId, boundaryIndex, removeSide) {
80
+ input.updateWorkbenchState((state) => {
81
+ const nextWorkspace = collapseSplitBoundary(state.workspace, { rootSplitId, boundaryIndex, removeSide });
82
+ const nextFocusedPanelId = findPanelInWorkspace(nextWorkspace, state.focus.activePanelId ?? '')
83
+ ? state.focus.activePanelId
84
+ : getPreferredFocusedPanelId(nextWorkspace);
85
+ return input.createWorkbenchState(createWorkspaceSessionState(nextWorkspace, { activePanelId: nextFocusedPanelId ?? null }), state.shell, state.layoutInteraction);
86
+ }, 'core');
87
+ },
88
+ openLayoutSplitMenu(panelId, edge, anchor, targets = [], options = {}) {
89
+ input.updateWorkbenchState((state) => ({
90
+ ...state,
91
+ layoutInteraction: openLayoutSplitMenu(panelId, edge, anchor, targets, options)
92
+ }));
93
+ },
94
+ selectLayoutSplitOrientation(orientation) {
95
+ input.updateWorkbenchState((state) => applyLayoutInteractionResolution(state, selectLayoutSplitOrientation(state.layoutInteraction, orientation)), resolveLayoutMenuHistoryScope(input.getWorkbenchState()));
96
+ },
97
+ selectLayoutMenuAction(selection) {
98
+ input.updateWorkbenchState((state) => applyLayoutInteractionResolution(state, selectLayoutMenuAction(state.layoutInteraction, selection)), resolveLayoutMenuHistoryScope(input.getWorkbenchState()));
99
+ },
100
+ hoverLayoutSplitSide(side) {
101
+ input.updateWorkbenchState((state) => ({
102
+ ...state,
103
+ layoutInteraction: previewLayoutSplitSide(state.layoutInteraction, side)
104
+ }));
105
+ },
106
+ confirmLayoutSplitSide(side) {
107
+ input.updateWorkbenchState((state) => applyLayoutInteractionResolution(state, confirmLayoutSplitSide(state.layoutInteraction, side)), resolveLayoutMenuHistoryScope(input.getWorkbenchState()));
108
+ },
109
+ updateLayoutSplitPreview(panelId, pointerRatio) {
110
+ input.updateWorkbenchState((state) => ({
111
+ ...state,
112
+ layoutInteraction: updateLayoutSplitPreview(state.layoutInteraction, panelId, pointerRatio)
113
+ }));
114
+ },
115
+ adjustLayoutSplitPreviewCuts(delta) {
116
+ input.updateWorkbenchState((state) => ({
117
+ ...state,
118
+ layoutInteraction: adjustLayoutSplitPreviewCuts(state.layoutInteraction, delta)
119
+ }));
120
+ },
121
+ startLayoutSplitPreview(panelId, edge, orientation, cuts, pointerRatio) {
122
+ input.updateWorkbenchState((state) => ({
123
+ ...state,
124
+ layoutInteraction: startLayoutSplitPreview({
125
+ panelId,
126
+ edge,
127
+ orientation,
128
+ cuts,
129
+ pointerRatio
130
+ })
131
+ }));
132
+ },
133
+ startBoundaryPull(params) {
134
+ input.updateWorkbenchState((state) => ({
135
+ ...state,
136
+ layoutInteraction: startBoundaryPull({ ...params, windowId: params.windowId ?? state.workspace.activeWindowId })
137
+ }));
138
+ },
139
+ updateBoundaryPull(pointer, viewport) {
140
+ input.updateWorkbenchState((state) => ({
141
+ ...state,
142
+ layoutInteraction: updateBoundaryPull(state.layoutInteraction, pointer, viewport)
143
+ }));
144
+ },
145
+ commitBoundaryPull() {
146
+ input.updateWorkbenchState((state) => applyLayoutInteractionResolution(state, commitBoundaryPull(state.layoutInteraction)), 'core');
147
+ },
148
+ startIntersectionResize(params) {
149
+ input.updateWorkbenchState((state) => ({
150
+ ...state,
151
+ layoutInteraction: startIntersectionResize(params)
152
+ }));
153
+ },
154
+ updateIntersectionResize(pointer, dimensions) {
155
+ input.updateWorkbenchState((state) => ({
156
+ ...state,
157
+ layoutInteraction: updateIntersectionResize(state.layoutInteraction, pointer, dimensions)
158
+ }));
159
+ },
160
+ commitIntersectionResize() {
161
+ input.updateWorkbenchState((state) => applyLayoutInteractionResolution(state, commitIntersectionResize(state.layoutInteraction)), 'core');
162
+ },
163
+ startPanelDrag(panelId, sourceStackId, anchor) {
164
+ input.updateWorkbenchState((state) => ({
165
+ ...state,
166
+ layoutInteraction: startPanelDrag(panelId, sourceStackId, anchor)
167
+ }));
168
+ },
169
+ updatePanelDrag(pointer, hoveredTarget) {
170
+ input.updateWorkbenchState((state) => ({
171
+ ...state,
172
+ layoutInteraction: updatePanelDrag(state.layoutInteraction, pointer, hoveredTarget)
173
+ }));
174
+ },
175
+ commitPanelDock() {
176
+ input.updateWorkbenchState((state) => applyLayoutInteractionResolution(state, commitPanelDock(state.layoutInteraction)), 'app');
177
+ },
178
+ commitLayoutSubdivideSelection(panelId, edge, orientation, cuts, pointerRatio) {
179
+ input.updateWorkbenchState((state) => applyLayoutInteractionResolution(state, commitLayoutSubdivideSelection({ panelId, edge, orientation, cuts, pointerRatio })), panelId ? 'app' : 'core');
180
+ },
181
+ commitLayoutSplitPreview() {
182
+ input.updateWorkbenchState((state) => applyLayoutInteractionResolution(state, commitLayoutSplitPreview(state.layoutInteraction)), resolveLayoutMenuHistoryScope(input.getWorkbenchState()));
183
+ },
184
+ cancelLayoutInteraction() {
185
+ input.updateWorkbenchState((state) => ({
186
+ ...state,
187
+ layoutInteraction: dismissLayoutInteraction()
188
+ }));
189
+ }
190
+ };
191
+ }
@@ -0,0 +1,39 @@
1
+ import { createWorkspaceSessionState, type ShellRegionId, type ShellState, type ShellWidgetCatalog, type Workspace, type WorkspaceFocus, type WorkspacePersistencePort } from '@konitif/workbench/workspace-contracts';
2
+ import type { WorkbenchShellPersistencePort } from "./workbenchShellPersistence.js";
3
+ import type { WorkbenchFocusPersistencePort } from "./workbenchFocusPersistence.js";
4
+ import type { WorkbenchHistoryScope } from "./workspaceHistoryController.js";
5
+ import type { WorkspaceSyncController } from "./workspaceSyncController.js";
6
+ import type { WorkbenchHostEvents } from "./workbenchHostEvents.js";
7
+ export interface WorkbenchPersistenceState {
8
+ workspace: Workspace;
9
+ focus: WorkspaceFocus;
10
+ shell: ShellState;
11
+ }
12
+ export interface CreateWorkbenchPersistenceRuntimeInput<TState extends WorkbenchPersistenceState> {
13
+ persistenceKey: string;
14
+ shellPersistenceKey: string;
15
+ focusPersistenceKey: string;
16
+ persistenceDebounceMs: number;
17
+ shellWidgetCatalog: ShellWidgetCatalog;
18
+ defaultOpenShellRegions?: ShellRegionId[];
19
+ initialWorkspace: Workspace;
20
+ initialShell: ShellState;
21
+ initialFocus: WorkspaceFocus;
22
+ workspacePersistence: WorkspacePersistencePort;
23
+ shellPersistence: WorkbenchShellPersistencePort;
24
+ focusPersistence: WorkbenchFocusPersistencePort;
25
+ workspaceSync: WorkspaceSyncController;
26
+ /** The caller selects its host; null explicitly disables notifications. */
27
+ hostEvents: WorkbenchHostEvents | null;
28
+ isPersistenceEnabled(): boolean;
29
+ getWorkbenchState(): TState;
30
+ createWorkbenchState(workspaceSession: ReturnType<typeof createWorkspaceSessionState>, shellState: ShellState): TState;
31
+ setWorkbenchState(nextState: TState, historyScope?: WorkbenchHistoryScope): void;
32
+ }
33
+ export interface WorkbenchPersistenceCoordinator<TState extends WorkbenchPersistenceState> {
34
+ handleStateChange(state: TState): void;
35
+ flush(): void;
36
+ /** Cancel pending work; call flush explicitly beforehand if desired. */
37
+ dispose(): void;
38
+ }
39
+ export declare function createWorkbenchPersistenceRuntime<TState extends WorkbenchPersistenceState>(input: CreateWorkbenchPersistenceRuntimeInput<TState>): WorkbenchPersistenceCoordinator<TState>;
@@ -0,0 +1,168 @@
1
+ import { createShellState, createWorkspaceSessionState, validateWorkspace } from '@konitif/workbench/workspace-contracts';
2
+ export function createWorkbenchPersistenceRuntime(input) {
3
+ if (input.hostEvents === undefined) {
4
+ throw new TypeError('hostEvents must be supplied explicitly (use null to disable notifications).');
5
+ }
6
+ let pendingPersistenceTimer = null;
7
+ let lastPersistedWorkspace = input.initialWorkspace;
8
+ let lastPersistedShellSignature = input.shellPersistence.serialize(input.initialShell);
9
+ let lastPersistedFocusSignature = input.focusPersistence.serialize(input.initialFocus);
10
+ let isApplyingRemoteWorkspace = false;
11
+ let disposed = false;
12
+ function createShellFallback() {
13
+ return createShellState(input.shellWidgetCatalog, { openRegionIds: input.defaultOpenShellRegions });
14
+ }
15
+ function handleStateChange(state) {
16
+ if (disposed)
17
+ return;
18
+ scheduleWorkspacePersistence(state.workspace, state.shell, state.focus);
19
+ publishWorkspaceSync(state.workspace, state.shell, state.focus);
20
+ }
21
+ function scheduleWorkspacePersistence(workspace, shell, focus) {
22
+ const shellSignature = input.shellPersistence.serialize(shell);
23
+ const focusSignature = input.focusPersistence.serialize(focus);
24
+ if (!input.isPersistenceEnabled()) {
25
+ clearPendingPersistence();
26
+ return;
27
+ }
28
+ if (workspace === lastPersistedWorkspace &&
29
+ shellSignature === lastPersistedShellSignature &&
30
+ focusSignature === lastPersistedFocusSignature) {
31
+ return;
32
+ }
33
+ clearPendingPersistence();
34
+ const persist = () => {
35
+ pendingPersistenceTimer = null;
36
+ if (disposed)
37
+ return;
38
+ persistWorkspaceShellAndFocus(workspace, shell, shellSignature, focus, focusSignature);
39
+ };
40
+ if (input.persistenceDebounceMs === 0) {
41
+ persist();
42
+ return;
43
+ }
44
+ pendingPersistenceTimer = setTimeout(persist, input.persistenceDebounceMs);
45
+ }
46
+ function flush() {
47
+ if (disposed)
48
+ return;
49
+ clearPendingPersistence();
50
+ if (!input.isPersistenceEnabled()) {
51
+ return;
52
+ }
53
+ const currentState = input.getWorkbenchState();
54
+ persistWorkspaceShellAndFocus(currentState.workspace, currentState.shell, input.shellPersistence.serialize(currentState.shell), currentState.focus, input.focusPersistence.serialize(currentState.focus));
55
+ }
56
+ function persistWorkspaceShellAndFocus(workspace, shell, shellSignature, focus, focusSignature) {
57
+ if (validateWorkspace(workspace).length === 0) {
58
+ try {
59
+ input.workspacePersistence.save(workspace);
60
+ lastPersistedWorkspace = workspace;
61
+ }
62
+ catch {
63
+ // Persistence is best-effort: invalid storage state should not break runtime flows.
64
+ }
65
+ }
66
+ try {
67
+ input.shellPersistence.save(shell);
68
+ lastPersistedShellSignature = shellSignature;
69
+ }
70
+ catch {
71
+ // Persistence is best-effort: invalid storage state should not break runtime flows.
72
+ }
73
+ try {
74
+ input.focusPersistence.save(focus);
75
+ lastPersistedFocusSignature = focusSignature;
76
+ }
77
+ catch {
78
+ // Persistence is best-effort: invalid storage state should not break runtime flows.
79
+ }
80
+ }
81
+ function publishWorkspaceSync(workspace, shell, focus) {
82
+ if (!input.isPersistenceEnabled() || isApplyingRemoteWorkspace || validateWorkspace(workspace).length > 0) {
83
+ return;
84
+ }
85
+ input.workspaceSync.publish({
86
+ workspace,
87
+ shell,
88
+ focus
89
+ });
90
+ }
91
+ function applyRemoteWorkspace(workspace, shell = null, focus = null) {
92
+ if (disposed || !input.isPersistenceEnabled() || validateWorkspace(workspace).length > 0) {
93
+ return;
94
+ }
95
+ const currentState = input.getWorkbenchState();
96
+ const nextShell = shell
97
+ ? input.shellPersistence.normalize(shell, createShellFallback())
98
+ : currentState.shell;
99
+ const nextShellSignature = input.shellPersistence.serialize(nextShell);
100
+ const nextFocus = input.focusPersistence.normalize(focus, workspace, currentState.focus);
101
+ const nextFocusSignature = input.focusPersistence.serialize(nextFocus);
102
+ if (JSON.stringify(currentState.workspace) === JSON.stringify(workspace) &&
103
+ input.shellPersistence.serialize(currentState.shell) === nextShellSignature &&
104
+ input.focusPersistence.serialize(currentState.focus) === nextFocusSignature) {
105
+ return;
106
+ }
107
+ // The admitted remote snapshot supersedes the local snapshot captured by
108
+ // the debounce callback. Do not let that stale callback overwrite storage.
109
+ clearPendingPersistence();
110
+ isApplyingRemoteWorkspace = true;
111
+ lastPersistedWorkspace = workspace;
112
+ lastPersistedShellSignature = nextShellSignature;
113
+ lastPersistedFocusSignature = nextFocusSignature;
114
+ input.setWorkbenchState(input.createWorkbenchState(createWorkspaceSessionState(workspace, nextFocus), nextShell), 'none');
115
+ isApplyingRemoteWorkspace = false;
116
+ }
117
+ function clearPendingPersistence() {
118
+ if (pendingPersistenceTimer !== null) {
119
+ clearTimeout(pendingPersistenceTimer);
120
+ pendingPersistenceTimer = null;
121
+ }
122
+ }
123
+ function handlePersistenceBoundary() {
124
+ flush();
125
+ }
126
+ function handleStorage(event) {
127
+ if (disposed || !input.isPersistenceEnabled()) {
128
+ return;
129
+ }
130
+ if (![input.persistenceKey, input.shellPersistenceKey, input.focusPersistenceKey].includes(event.key ?? '') ||
131
+ !event.newValue) {
132
+ return;
133
+ }
134
+ const loadedWorkspace = event.key === input.persistenceKey
135
+ ? input.workspacePersistence.load()
136
+ : input.getWorkbenchState().workspace;
137
+ if (!loadedWorkspace) {
138
+ return;
139
+ }
140
+ applyRemoteWorkspace(loadedWorkspace, input.shellPersistence.load(createShellFallback()), input.focusPersistence.load(loadedWorkspace, input.getWorkbenchState().focus));
141
+ }
142
+ const unsubscribeHost = input.hostEvents?.subscribe({
143
+ onPersistenceBoundary: handlePersistenceBoundary,
144
+ onStorageChange: handleStorage
145
+ });
146
+ const unsubscribe = input.workspaceSync.subscribe((snapshot) => {
147
+ applyRemoteWorkspace(snapshot.workspace, snapshot.shell ?? null, snapshot.focus ?? null);
148
+ });
149
+ function dispose() {
150
+ if (disposed)
151
+ return;
152
+ disposed = true;
153
+ clearPendingPersistence();
154
+ try {
155
+ unsubscribeHost?.();
156
+ }
157
+ finally {
158
+ if (typeof unsubscribe === 'function')
159
+ unsubscribe();
160
+ }
161
+ // workspaceSync is injected: its owner, not this subscriber, closes it.
162
+ }
163
+ return {
164
+ handleStateChange,
165
+ flush,
166
+ dispose
167
+ };
168
+ }
@@ -0,0 +1,24 @@
1
+ import { type LayoutInteractionState, type ShellRegionId, type ShellRegionAxis, type ShellRegionPresentation, type ShellState, type ShellWidgetPlacement, type Workspace, type WorkspaceFocus } from '@konitif/workbench/workspace-contracts';
2
+ import type { WorkbenchHistoryScope } from "./workspaceHistoryController.js";
3
+ export interface WorkbenchShellActionState {
4
+ workspace: Workspace;
5
+ focus: WorkspaceFocus;
6
+ shell: ShellState;
7
+ layoutInteraction: LayoutInteractionState;
8
+ }
9
+ export interface WorkbenchShellActions {
10
+ addShellWidgetToRegion(regionId: ShellRegionId, widgetId: string, placement?: ShellWidgetPlacement): void;
11
+ activateShellWidget(regionId: ShellRegionId, widgetId: string): void;
12
+ moveShellWidgetToRegion(regionId: ShellRegionId, widgetId: string, placement?: ShellWidgetPlacement): void;
13
+ removeShellWidgetFromRegion(regionId: ShellRegionId, widgetId: string): void;
14
+ setShellRegionArrangement(regionId: ShellRegionId, presentation: ShellRegionPresentation, axis?: ShellRegionAxis): void;
15
+ setShellRegionOpen(regionId: ShellRegionId, isOpen: boolean): void;
16
+ setShellRegionVisible(regionId: ShellRegionId, isVisible: boolean): void;
17
+ setShellRegionWidgetVisible(regionId: ShellRegionId, widgetId: string, isVisible: boolean): void;
18
+ setShellRegionSize(regionId: ShellRegionId, size: number): void;
19
+ }
20
+ export interface CreateWorkbenchShellActionsInput<TState extends WorkbenchShellActionState> {
21
+ updateWorkbenchState(updater: (state: TState) => TState, historyScope?: WorkbenchHistoryScope): void;
22
+ flushPersistence?(): void;
23
+ }
24
+ export declare function createWorkbenchShellActions<TState extends WorkbenchShellActionState>(input: CreateWorkbenchShellActionsInput<TState>): WorkbenchShellActions;
@@ -0,0 +1,63 @@
1
+ import { addShellWidgetToRegion, activateShellWidget, moveShellWidgetToRegion, removeShellWidgetFromRegion, setShellRegionArrangement, setShellRegionOpen, setShellRegionSize, setShellRegionWidgetVisible, setShellRegionVisible } from '@konitif/workbench/workspace-contracts';
2
+ export function createWorkbenchShellActions(input) {
3
+ function commitShellInteraction(updater) {
4
+ input.updateWorkbenchState(updater, 'core');
5
+ input.flushPersistence?.();
6
+ }
7
+ return {
8
+ addShellWidgetToRegion(regionId, widgetId, placement = {}) {
9
+ commitShellInteraction((state) => ({
10
+ ...state,
11
+ shell: addShellWidgetToRegion(state.shell, regionId, widgetId, placement)
12
+ }));
13
+ },
14
+ activateShellWidget(regionId, widgetId) {
15
+ commitShellInteraction((state) => ({
16
+ ...state,
17
+ shell: activateShellWidget(state.shell, regionId, widgetId)
18
+ }));
19
+ },
20
+ moveShellWidgetToRegion(regionId, widgetId, placement = {}) {
21
+ commitShellInteraction((state) => ({
22
+ ...state,
23
+ shell: moveShellWidgetToRegion(state.shell, regionId, widgetId, placement)
24
+ }));
25
+ },
26
+ removeShellWidgetFromRegion(regionId, widgetId) {
27
+ commitShellInteraction((state) => ({
28
+ ...state,
29
+ shell: removeShellWidgetFromRegion(state.shell, regionId, widgetId)
30
+ }));
31
+ },
32
+ setShellRegionArrangement(regionId, presentation, axis) {
33
+ commitShellInteraction((state) => ({
34
+ ...state,
35
+ shell: setShellRegionArrangement(state.shell, regionId, presentation, axis)
36
+ }));
37
+ },
38
+ setShellRegionOpen(regionId, isOpen) {
39
+ commitShellInteraction((state) => ({
40
+ ...state,
41
+ shell: setShellRegionOpen(state.shell, regionId, isOpen)
42
+ }));
43
+ },
44
+ setShellRegionVisible(regionId, isVisible) {
45
+ commitShellInteraction((state) => ({
46
+ ...state,
47
+ shell: setShellRegionVisible(state.shell, regionId, isVisible)
48
+ }));
49
+ },
50
+ setShellRegionWidgetVisible(regionId, widgetId, isVisible) {
51
+ commitShellInteraction((state) => ({
52
+ ...state,
53
+ shell: setShellRegionWidgetVisible(state.shell, regionId, widgetId, isVisible)
54
+ }));
55
+ },
56
+ setShellRegionSize(regionId, size) {
57
+ input.updateWorkbenchState((state) => ({
58
+ ...state,
59
+ shell: setShellRegionSize(state.shell, regionId, size)
60
+ }), 'core');
61
+ }
62
+ };
63
+ }
@@ -0,0 +1,18 @@
1
+ import type { ShellState, ShellWidgetCatalog } from '@konitif/workbench';
2
+ export interface WorkbenchShellPersistencePort {
3
+ load(fallback: ShellState): ShellState;
4
+ save(shell: ShellState): void;
5
+ serialize(shell: ShellState): string;
6
+ normalize(input: unknown, fallback: ShellState): ShellState;
7
+ }
8
+ interface LocalWorkbenchShellPersistenceOptions {
9
+ storageKey: string;
10
+ isEnabled?: () => boolean;
11
+ shellWidgetCatalog?: ShellWidgetCatalog;
12
+ }
13
+ export declare function createLocalWorkbenchShellPersistence(options: LocalWorkbenchShellPersistenceOptions): WorkbenchShellPersistencePort;
14
+ export declare function serializeShellState(shell: ShellState): string;
15
+ export declare function normalizePersistedShellState(input: unknown, fallback: ShellState, options?: {
16
+ shellWidgetCatalog?: ShellWidgetCatalog;
17
+ }): ShellState;
18
+ export {};