@vuu-ui/vuu-shell 3.3.17 → 4.0.0-alpha.10
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/package.json +18 -16
- package/src/app-header/AppHeader.js +36 -26
- package/src/application-provider/ApplicationContext.js +2 -5
- package/src/application-provider/ApplicationProvider.js +8 -59
- package/src/feature/Feature.js +3 -34
- package/src/feature/index.js +1 -0
- package/src/feature-list/FeatureList.css.js +27 -5
- package/src/feature-list/FeatureList.js +117 -96
- package/src/index.js +3 -3
- package/src/left-nav/LeftNav.css.js +2 -4
- package/src/left-nav/LeftNav.js +139 -100
- package/src/module-registry/getRegisteredModules.js +12 -0
- package/src/persistence-manager/LocalWorkspacePersistenceService.js +129 -0
- package/src/persistence-manager/PersistenceProvider.js +10 -3
- package/src/persistence-manager/RemoteWorkspacePersistenceService.js +112 -0
- package/src/persistence-manager/WorkspacePersistenceService.js +9 -0
- package/src/persistence-manager/index.js +3 -0
- package/src/shell-layout-templates/context-panel/ApplicationSettingsContextPanel.js +24 -0
- package/src/shell-layout-templates/context-panel/ContextPanel.css.js +8 -8
- package/src/shell-layout-templates/context-panel/ContextPanel.js +17 -35
- package/src/shell-layout-templates/context-panel/index.js +1 -0
- package/src/shell-layout-templates/full-height-left-panel/useFullHeightLeftPanel.js +54 -40
- package/src/shell-layout-templates/inlay-left-panel/useInlayLeftPanel.js +54 -64
- package/src/shell-layout-templates/left-main-tabs/useLeftMainTabs.js +49 -28
- package/src/shell-layout-templates/side-panel/SidePanel.css.js +3 -1
- package/src/shell-layout-templates/simple-content-pane/useSimpleContentPane.js +43 -35
- package/src/shell.css.js +85 -2
- package/src/shell.js +148 -70
- package/src/workspace-management/LayoutList.js +59 -99
- package/src/workspace-management/SaveLayoutPanel.js +3 -3
- package/src/workspace-management/WorkspaceController.js +73 -0
- package/src/workspace-management/WorkspaceHost.js +194 -0
- package/src/workspace-management/WorkspacePersistentState.js +67 -0
- package/src/workspace-management/WorkspaceProvider.js +699 -178
- package/src/workspace-management/WorkspaceRepository.js +127 -0
- package/src/workspace-management/WorkspaceStartPanel.css.js +44 -0
- package/src/workspace-management/WorkspaceStartPanel.js +34 -0
- package/src/workspace-management/index.js +8 -0
- package/src/workspace-management/shell-workspace-components.js +59 -0
- package/src/workspace-management/useWorkspaceContextMenuItems.js +13 -7
- package/src/workspace-management/workspace-component-registry.js +30 -0
- package/src/workspace-management/workspace-schemas.js +257 -0
- package/types/app-header/AppHeader.d.ts +2 -2
- package/types/application-provider/ApplicationContext.d.ts +1 -6
- package/types/application-provider/ApplicationProvider.d.ts +5 -14
- package/types/feature/Feature.d.ts +2 -10
- package/types/feature/Loader.d.ts +1 -1
- package/types/feature/index.d.ts +2 -1
- package/types/feature-list/FeatureList.d.ts +4 -4
- package/types/index.d.ts +3 -3
- package/types/left-nav/LeftNav.d.ts +2 -1
- package/types/login/LoginPanel.d.ts +1 -1
- package/types/login/VuuLogo.d.ts +1 -1
- package/types/persistence-manager/PersistenceProvider.d.ts +7 -3
- package/types/persistence-manager/index.d.ts +3 -0
- package/types/shell-layout-templates/context-panel/ContextPanel.d.ts +9 -11
- package/types/shell-layout-templates/context-panel/index.d.ts +1 -0
- package/types/shell-layout-templates/full-height-left-panel/useFullHeightLeftPanel.d.ts +1 -1
- package/types/shell-layout-templates/inlay-left-panel/useInlayLeftPanel.d.ts +1 -1
- package/types/shell-layout-templates/left-main-tabs/useLeftMainTabs.d.ts +1 -1
- package/types/shell-layout-templates/side-panel/SidePanel.d.ts +1 -1
- package/types/shell-layout-templates/simple-content-pane/useSimpleContentPane.d.ts +1 -1
- package/types/shell-layout-templates/useShellLayout.d.ts +4 -2
- package/types/shell.d.ts +19 -15
- package/types/theme-switch/ThemeSwitch.d.ts +1 -1
- package/types/workspace-management/LayoutList.d.ts +2 -2
- package/types/workspace-management/LayoutTile.d.ts +1 -1
- package/types/workspace-management/SaveLayoutPanel.d.ts +2 -2
- package/types/workspace-management/WorkspaceProvider.d.ts +61 -50
- package/types/workspace-management/defaultWorkspaceJSON.d.ts +7 -6
- package/types/workspace-management/index.d.ts +8 -0
- package/types/workspace-management/useWorkspaceContextMenuItems.d.ts +1 -1
- package/src/feature/FeatureErrorBoundary.js +0 -33
- package/src/user-settings/SettingsForm.css.js +0 -10
- package/src/user-settings/SettingsForm.js +0 -141
- package/src/user-settings/UserSettingsPanel.css.js +0 -9
- package/src/user-settings/UserSettingsPanel.js +0 -29
- 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 "@vuu-ui/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 };
|