@vuu-ui/vuu-shell 3.3.11 → 4.0.0-alpha.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 (78) hide show
  1. package/package.json +18 -16
  2. package/src/app-header/AppHeader.js +36 -26
  3. package/src/application-provider/ApplicationContext.js +2 -5
  4. package/src/application-provider/ApplicationProvider.js +8 -59
  5. package/src/feature/Feature.js +3 -34
  6. package/src/feature/index.js +1 -0
  7. package/src/feature-list/FeatureList.css.js +27 -5
  8. package/src/feature-list/FeatureList.js +117 -96
  9. package/src/index.js +3 -3
  10. package/src/left-nav/LeftNav.css.js +2 -4
  11. package/src/left-nav/LeftNav.js +139 -100
  12. package/src/module-registry/getRegisteredModules.js +12 -0
  13. package/src/persistence-manager/LocalWorkspacePersistenceService.js +129 -0
  14. package/src/persistence-manager/PersistenceProvider.js +10 -3
  15. package/src/persistence-manager/RemoteWorkspacePersistenceService.js +112 -0
  16. package/src/persistence-manager/WorkspacePersistenceService.js +9 -0
  17. package/src/persistence-manager/index.js +3 -0
  18. package/src/shell-layout-templates/context-panel/ApplicationSettingsContextPanel.js +24 -0
  19. package/src/shell-layout-templates/context-panel/ContextPanel.css.js +8 -8
  20. package/src/shell-layout-templates/context-panel/ContextPanel.js +17 -35
  21. package/src/shell-layout-templates/context-panel/index.js +1 -0
  22. package/src/shell-layout-templates/full-height-left-panel/useFullHeightLeftPanel.js +54 -40
  23. package/src/shell-layout-templates/inlay-left-panel/useInlayLeftPanel.js +54 -64
  24. package/src/shell-layout-templates/left-main-tabs/useLeftMainTabs.js +49 -28
  25. package/src/shell-layout-templates/side-panel/SidePanel.css.js +3 -1
  26. package/src/shell-layout-templates/simple-content-pane/useSimpleContentPane.js +43 -35
  27. package/src/shell.css.js +85 -2
  28. package/src/shell.js +148 -70
  29. package/src/workspace-management/LayoutList.js +59 -99
  30. package/src/workspace-management/SaveLayoutPanel.js +3 -3
  31. package/src/workspace-management/WorkspaceController.js +73 -0
  32. package/src/workspace-management/WorkspaceHost.js +194 -0
  33. package/src/workspace-management/WorkspacePersistentState.js +67 -0
  34. package/src/workspace-management/WorkspaceProvider.js +699 -178
  35. package/src/workspace-management/WorkspaceRepository.js +127 -0
  36. package/src/workspace-management/WorkspaceStartPanel.css.js +44 -0
  37. package/src/workspace-management/WorkspaceStartPanel.js +34 -0
  38. package/src/workspace-management/index.js +8 -0
  39. package/src/workspace-management/shell-workspace-components.js +59 -0
  40. package/src/workspace-management/useWorkspaceContextMenuItems.js +12 -6
  41. package/src/workspace-management/workspace-component-registry.js +30 -0
  42. package/src/workspace-management/workspace-schemas.js +257 -0
  43. package/types/app-header/AppHeader.d.ts +2 -2
  44. package/types/application-provider/ApplicationContext.d.ts +1 -6
  45. package/types/application-provider/ApplicationProvider.d.ts +5 -14
  46. package/types/feature/Feature.d.ts +2 -10
  47. package/types/feature/Loader.d.ts +1 -1
  48. package/types/feature/index.d.ts +2 -1
  49. package/types/feature-list/FeatureList.d.ts +4 -4
  50. package/types/index.d.ts +3 -3
  51. package/types/left-nav/LeftNav.d.ts +2 -1
  52. package/types/login/LoginPanel.d.ts +1 -1
  53. package/types/login/VuuLogo.d.ts +1 -1
  54. package/types/persistence-manager/PersistenceProvider.d.ts +7 -3
  55. package/types/persistence-manager/index.d.ts +3 -0
  56. package/types/shell-layout-templates/context-panel/ContextPanel.d.ts +9 -11
  57. package/types/shell-layout-templates/context-panel/index.d.ts +1 -0
  58. package/types/shell-layout-templates/full-height-left-panel/useFullHeightLeftPanel.d.ts +1 -1
  59. package/types/shell-layout-templates/inlay-left-panel/useInlayLeftPanel.d.ts +1 -1
  60. package/types/shell-layout-templates/left-main-tabs/useLeftMainTabs.d.ts +1 -1
  61. package/types/shell-layout-templates/side-panel/SidePanel.d.ts +1 -1
  62. package/types/shell-layout-templates/simple-content-pane/useSimpleContentPane.d.ts +1 -1
  63. package/types/shell-layout-templates/useShellLayout.d.ts +4 -2
  64. package/types/shell.d.ts +19 -15
  65. package/types/theme-switch/ThemeSwitch.d.ts +1 -1
  66. package/types/workspace-management/LayoutList.d.ts +2 -2
  67. package/types/workspace-management/LayoutTile.d.ts +1 -1
  68. package/types/workspace-management/SaveLayoutPanel.d.ts +2 -2
  69. package/types/workspace-management/WorkspaceProvider.d.ts +61 -50
  70. package/types/workspace-management/defaultWorkspaceJSON.d.ts +7 -6
  71. package/types/workspace-management/index.d.ts +8 -0
  72. package/types/workspace-management/useWorkspaceContextMenuItems.d.ts +1 -1
  73. package/src/feature/FeatureErrorBoundary.js +0 -33
  74. package/src/user-settings/SettingsForm.css.js +0 -10
  75. package/src/user-settings/SettingsForm.js +0 -141
  76. package/src/user-settings/UserSettingsPanel.css.js +0 -9
  77. package/src/user-settings/UserSettingsPanel.js +0 -29
  78. package/src/user-settings/index.js +0 -2
@@ -0,0 +1,194 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { GridLayout, GridLayoutProvider } from "@heswell/grid-layout";
3
+ import { Tab, Tabstrip } from "@vuu-ui/vuu-ui-controls";
4
+ import { useCallback, useRef, useState } from "react";
5
+ import { WorkspacePersistentStateProvider, WorkspacePersistentStateStore } from "./WorkspacePersistentState.js";
6
+ import { emptyWorkspaceSnapshot, useWorkspace } from "./WorkspaceProvider.js";
7
+ import { WorkspaceStartPanel } from "./WorkspaceStartPanel.js";
8
+ import { WORKSPACE_SNAPSHOT_VERSION } from "./workspace-schemas.js";
9
+ const WorkspaceGrid = ({ active, controller })=>{
10
+ const [isEmpty, setIsEmpty] = useState(0 === controller.snapshot.layout.components.length);
11
+ const handleDocumentError = useCallback((error)=>{
12
+ throw new Error(`Invalid workspace document at ${error.path}: ${error.message}`);
13
+ }, []);
14
+ const handleDocumentChange = useCallback((document, change)=>{
15
+ setIsEmpty(0 === document.components.length);
16
+ controller.handleDocumentChange(document, change);
17
+ }, [
18
+ controller
19
+ ]);
20
+ return /*#__PURE__*/ jsx("section", {
21
+ "aria-hidden": !active,
22
+ "aria-labelledby": `${controller.instanceId}-tab`,
23
+ className: "vuuWorkspaceHost-workspace",
24
+ "data-workspace-instance-id": controller.instanceId,
25
+ hidden: !active,
26
+ id: controller.instanceId,
27
+ role: "tabpanel",
28
+ children: /*#__PURE__*/ jsx(WorkspacePersistentStateProvider, {
29
+ store: controller.persistentState,
30
+ children: /*#__PURE__*/ jsx(GridLayoutProvider, {
31
+ componentRenderers: controller.renderers,
32
+ document: controller.snapshot.layout,
33
+ documentController: controller.documentController,
34
+ onDocumentChange: handleDocumentChange,
35
+ onDocumentError: handleDocumentError,
36
+ settingsCodecs: controller.settingsCodecs,
37
+ children: /*#__PURE__*/ jsx(GridLayout, {
38
+ id: controller.snapshot.layout.layout.id,
39
+ placeholderContent: isEmpty ? /*#__PURE__*/ jsx(WorkspaceStartPanel, {}) : void 0
40
+ })
41
+ })
42
+ })
43
+ });
44
+ };
45
+ const EmptyWorkspaceDropTarget = ()=>{
46
+ const { componentRenderers, createWorkspaceFromSnapshot, settingsCodecs } = useWorkspace();
47
+ const creating = useRef(false);
48
+ const [creationPending, setCreationPending] = useState(false);
49
+ const [generation, setGeneration] = useState(0);
50
+ const [persistentState, setPersistentState] = useState(()=>new WorkspacePersistentStateStore());
51
+ const handleDocumentError = useCallback((error)=>{
52
+ throw new Error(`Invalid empty workspace document at ${error.path}: ${error.message}`);
53
+ }, []);
54
+ const handleDocumentChange = useCallback((document)=>{
55
+ if (creating.current || 0 === document.components.length) return;
56
+ creating.current = true;
57
+ setCreationPending(true);
58
+ createWorkspaceFromSnapshot({
59
+ componentState: persistentState.getSnapshot(),
60
+ layout: document,
61
+ version: WORKSPACE_SNAPSHOT_VERSION
62
+ }, "Untitled").catch(()=>{
63
+ creating.current = false;
64
+ setCreationPending(false);
65
+ setPersistentState(new WorkspacePersistentStateStore());
66
+ setGeneration((current)=>current + 1);
67
+ });
68
+ }, [
69
+ createWorkspaceFromSnapshot,
70
+ persistentState
71
+ ]);
72
+ if (!componentRenderers || !settingsCodecs) return /*#__PURE__*/ jsx("div", {
73
+ className: "vuuWorkspaceHost-state",
74
+ role: "alert",
75
+ children: "Workspace component registries are not configured."
76
+ });
77
+ if (creationPending) return /*#__PURE__*/ jsx("div", {
78
+ className: "vuuWorkspaceHost-empty",
79
+ children: /*#__PURE__*/ jsx("div", {
80
+ className: "vuuWorkspaceHost-state",
81
+ role: "status",
82
+ children: "Creating workspace…"
83
+ })
84
+ });
85
+ return /*#__PURE__*/ jsx("div", {
86
+ className: "vuuWorkspaceHost-empty",
87
+ children: /*#__PURE__*/ jsx(WorkspacePersistentStateProvider, {
88
+ store: persistentState,
89
+ children: /*#__PURE__*/ jsx(GridLayoutProvider, {
90
+ componentRenderers: componentRenderers,
91
+ onDocumentChange: handleDocumentChange,
92
+ onDocumentError: handleDocumentError,
93
+ settingsCodecs: settingsCodecs,
94
+ children: /*#__PURE__*/ jsx(GridLayout, {
95
+ className: "vuuWorkspaceHost-emptyGrid",
96
+ colsAndRows: {
97
+ cols: [
98
+ "1fr"
99
+ ],
100
+ rows: [
101
+ "1fr"
102
+ ]
103
+ },
104
+ id: emptyWorkspaceSnapshot.layout.layout.id,
105
+ placeholderContent: /*#__PURE__*/ jsx(WorkspaceStartPanel, {})
106
+ })
107
+ }, generation)
108
+ })
109
+ });
110
+ };
111
+ const WorkspaceHost = ()=>{
112
+ const { activeWorkspaceInstanceId, closeWorkspace, controllers, createWorkspaceFromSnapshot, error, renameWorkspace, selectWorkspace, status } = useWorkspace();
113
+ const [tabRevision, setTabRevision] = useState(0);
114
+ const activeWorkspaceIndex = controllers.findIndex(({ instanceId })=>instanceId === activeWorkspaceInstanceId);
115
+ const handleActiveChange = useCallback((tabIndex)=>{
116
+ const controller = controllers[tabIndex];
117
+ if (controller) selectWorkspace(controller.instanceId);
118
+ }, [
119
+ controllers,
120
+ selectWorkspace
121
+ ]);
122
+ const handleCloseTab = useCallback((tabIndex)=>{
123
+ const controller = controllers[tabIndex];
124
+ if (controller) closeWorkspace(controller.instanceId);
125
+ }, [
126
+ closeWorkspace,
127
+ controllers
128
+ ]);
129
+ const handleAddTab = useCallback(()=>{
130
+ const titles = new Set(controllers.map(({ name })=>name));
131
+ let suffix = 1;
132
+ let title = "Untitled";
133
+ while(titles.has(title)){
134
+ suffix += 1;
135
+ title = `Untitled ${suffix}`;
136
+ }
137
+ createWorkspaceFromSnapshot(emptyWorkspaceSnapshot, title).catch(()=>void 0);
138
+ }, [
139
+ controllers,
140
+ createWorkspaceFromSnapshot
141
+ ]);
142
+ const handleExitEditMode = useCallback((originalTitle, title, _allowDeactivation, tabIndex)=>{
143
+ const controller = controllers[tabIndex];
144
+ if (controller && title !== originalTitle) renameWorkspace(controller.instanceId, title).catch(()=>{
145
+ setTabRevision((revision)=>revision + 1);
146
+ });
147
+ }, [
148
+ controllers,
149
+ renameWorkspace
150
+ ]);
151
+ if ("loading" === status) return /*#__PURE__*/ jsx("div", {
152
+ className: "vuuWorkspaceHost-state",
153
+ role: "status",
154
+ children: "Loading workspaces…"
155
+ });
156
+ if ("error" === status) return /*#__PURE__*/ jsx("div", {
157
+ className: "vuuWorkspaceHost-state",
158
+ role: "alert",
159
+ children: error?.message ?? "Unable to load workspaces"
160
+ });
161
+ return /*#__PURE__*/ jsxs("div", {
162
+ className: "vuuWorkspaceHost vuu-workspace-tabs",
163
+ children: [
164
+ /*#__PURE__*/ jsx(Tabstrip, {
165
+ activeTabIndex: activeWorkspaceIndex,
166
+ allowAddTab: true,
167
+ allowCloseTab: true,
168
+ allowRenameTab: true,
169
+ animateSelectionThumb: false,
170
+ "aria-label": "Workspace Tabs",
171
+ className: "vuuTabHeader",
172
+ onActiveChange: handleActiveChange,
173
+ onAddTab: handleAddTab,
174
+ onCloseTab: handleCloseTab,
175
+ onExitEditMode: handleExitEditMode,
176
+ variant: "primary",
177
+ children: controllers.map((controller)=>/*#__PURE__*/ jsx(Tab, {
178
+ ariaControls: controller.instanceId,
179
+ id: `${controller.instanceId}-tab`,
180
+ label: controller.name,
181
+ onClose: ()=>void closeWorkspace(controller.instanceId)
182
+ }, `${controller.instanceId}-${tabRevision}`))
183
+ }),
184
+ /*#__PURE__*/ jsx("div", {
185
+ className: "vuuWorkspaceHost-content",
186
+ children: "empty" === status ? /*#__PURE__*/ jsx(EmptyWorkspaceDropTarget, {}) : controllers.map((controller)=>/*#__PURE__*/ jsx(WorkspaceGrid, {
187
+ active: controller.instanceId === activeWorkspaceInstanceId,
188
+ controller: controller
189
+ }, controller.instanceId))
190
+ })
191
+ ]
192
+ });
193
+ };
194
+ export { WorkspaceHost };
@@ -0,0 +1,67 @@
1
+ import { createContext, createElement, useContext, useSyncExternalStore } from "react";
2
+ import { cloneWorkspaceValue } from "./workspace-schemas.js";
3
+ class WorkspacePersistentStateStore {
4
+ #state;
5
+ #listeners = new Set();
6
+ constructor(initialState = {}){
7
+ this.#state = Object.freeze(cloneWorkspaceValue(initialState));
8
+ }
9
+ getSnapshot = ()=>this.#state;
10
+ subscribe = (listener)=>{
11
+ this.#listeners.add(listener);
12
+ return ()=>this.#listeners.delete(listener);
13
+ };
14
+ get(componentInstanceId) {
15
+ const value = this.#state[componentInstanceId];
16
+ return void 0 === value ? void 0 : cloneWorkspaceValue(value);
17
+ }
18
+ update(componentInstanceId, update) {
19
+ if (!componentInstanceId) throw new Error("componentInstanceId must not be empty");
20
+ const next = "function" == typeof update ? update(this.get(componentInstanceId)) : update;
21
+ const captured = cloneWorkspaceValue(next);
22
+ if (!captured.componentType || !Number.isInteger(captured.schemaVersion) || captured.schemaVersion < 1) throw new Error("Component persistent state metadata is invalid");
23
+ this.#state = Object.freeze({
24
+ ...this.#state,
25
+ [componentInstanceId]: captured
26
+ });
27
+ this.#emit();
28
+ }
29
+ purge(componentInstanceId) {
30
+ if (void 0 === componentInstanceId) {
31
+ if (Object.keys(this.#state).length > 0) {
32
+ this.#state = Object.freeze({});
33
+ this.#emit();
34
+ }
35
+ return;
36
+ }
37
+ if (void 0 !== this.#state[componentInstanceId]) {
38
+ const { [componentInstanceId]: removed, ...remaining } = this.#state;
39
+ this.#state = Object.freeze(remaining);
40
+ this.#emit();
41
+ }
42
+ }
43
+ #emit() {
44
+ this.#listeners.forEach((listener)=>{
45
+ listener();
46
+ });
47
+ }
48
+ }
49
+ const WorkspacePersistentStateContext = /*#__PURE__*/ createContext(null);
50
+ const WorkspacePersistentStateProvider = ({ children, store })=>/*#__PURE__*/ createElement(WorkspacePersistentStateContext.Provider, {
51
+ value: store
52
+ }, children);
53
+ const useWorkspacePersistentStateStore = ()=>{
54
+ const store = useContext(WorkspacePersistentStateContext);
55
+ if (!store) throw new Error("useWorkspacePersistentStateStore requires WorkspacePersistentStateProvider");
56
+ return store;
57
+ };
58
+ const useComponentPersistentState = (componentInstanceId)=>{
59
+ const store = useWorkspacePersistentStateStore();
60
+ const state = useSyncExternalStore(store.subscribe, ()=>store.getSnapshot()[componentInstanceId], ()=>store.getSnapshot()[componentInstanceId]);
61
+ return [
62
+ state,
63
+ (update)=>store.update(componentInstanceId, update),
64
+ ()=>store.purge(componentInstanceId)
65
+ ];
66
+ };
67
+ export { WorkspacePersistentStateProvider, WorkspacePersistentStateStore, useComponentPersistentState, useWorkspacePersistentStateStore };