@vuu-ui/vuu-shell 4.0.0-alpha.3 → 4.0.0-alpha.30
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 +20 -19
- package/src/app-header/AppHeader.css.js +1 -1
- package/src/persistence-manager/PersistenceProvider.js +5 -7
- package/src/persistence-manager/index.js +1 -3
- package/src/shell.css.js +1 -1
- package/src/shell.js +2 -2
- package/src/workspace-management/LayoutList.js +1 -1
- package/src/workspace-management/index.js +1 -8
- package/src/workspace-management/useWorkspaceContextMenuItems.js +1 -2
- package/types/app-header/AppHeader.d.ts +1 -1
- package/types/feature/Feature.d.ts +1 -1
- package/types/feature/Loader.d.ts +1 -1
- package/types/feature-list/FeatureList.d.ts +1 -1
- package/types/left-nav/LeftNav.d.ts +1 -1
- package/types/login/LoginPanel.d.ts +1 -1
- package/types/login/VuuLogo.d.ts +1 -1
- package/types/persistence-manager/PersistenceProvider.d.ts +2 -3
- package/types/persistence-manager/index.d.ts +1 -3
- package/types/shell-layout-templates/context-panel/ApplicationSettingsContextPanel.d.ts +1 -1
- package/types/shell-layout-templates/context-panel/ContextPanel.d.ts +1 -1
- package/types/shell-layout-templates/side-panel/SidePanel.d.ts +1 -1
- package/types/shell.d.ts +2 -2
- package/types/theme-switch/ThemeSwitch.d.ts +1 -1
- package/types/workspace-management/LayoutList.d.ts +1 -1
- package/types/workspace-management/LayoutTile.d.ts +1 -1
- package/types/workspace-management/SaveLayoutPanel.d.ts +1 -1
- package/types/workspace-management/WorkspaceHost.d.ts +1 -1
- package/types/workspace-management/WorkspaceProvider.d.ts +50 -61
- package/types/workspace-management/WorkspaceStartPanel.d.ts +1 -1
- package/types/workspace-management/index.d.ts +5 -8
- package/types/workspace-management/shell-workspace-components.d.ts +1 -1
- package/src/persistence-manager/LocalWorkspacePersistenceService.js +0 -129
- package/src/persistence-manager/RemoteWorkspacePersistenceService.js +0 -112
- package/src/persistence-manager/WorkspacePersistenceService.js +0 -9
- package/src/workspace-management/WorkspaceController.js +0 -73
- package/src/workspace-management/WorkspaceHost.js +0 -194
- package/src/workspace-management/WorkspacePersistentState.js +0 -67
- package/src/workspace-management/WorkspaceProvider.js +0 -730
- package/src/workspace-management/WorkspaceRepository.js +0 -127
- package/src/workspace-management/WorkspaceStartPanel.css.js +0 -44
- package/src/workspace-management/WorkspaceStartPanel.js +0 -34
- package/src/workspace-management/workspace-component-registry.js +0 -30
- package/src/workspace-management/workspace-schemas.js +0 -257
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { cloneWorkspaceValue, validateApplicationSessionV1, validateNamedWorkspaceDefinitionV1, validateNamedWorkspaceMetadataV1, validateWorkspaceSnapshotRecordV1 } from "../workspace-management/workspace-schemas.js";
|
|
2
|
-
import { WorkspacePersistenceError } from "./WorkspacePersistenceService.js";
|
|
3
|
-
const trimSlash = (value)=>value.replace(/\/+$/, "");
|
|
4
|
-
const requireId = (id, operation)=>{
|
|
5
|
-
if (!id) throw new WorkspacePersistenceError("Entity id must not be empty", operation);
|
|
6
|
-
return encodeURIComponent(id);
|
|
7
|
-
};
|
|
8
|
-
class RemoteWorkspacePersistenceService {
|
|
9
|
-
scope;
|
|
10
|
-
#baseUrl;
|
|
11
|
-
#requestInit;
|
|
12
|
-
#requestInitProvider;
|
|
13
|
-
#fetch;
|
|
14
|
-
constructor(scope, { baseUrl, requestInit, requestInitProvider, fetch: fetchImplementation = globalThis.fetch }){
|
|
15
|
-
if (!scope.applicationNamespace || !scope.applicationId || !scope.userId || !baseUrl) throw new WorkspacePersistenceError("Remote persistence scope and base URL must not be empty", "construct");
|
|
16
|
-
this.scope = Object.freeze({
|
|
17
|
-
...scope
|
|
18
|
-
});
|
|
19
|
-
this.#baseUrl = trimSlash(baseUrl);
|
|
20
|
-
this.#requestInit = requestInit;
|
|
21
|
-
this.#requestInitProvider = requestInitProvider;
|
|
22
|
-
this.#fetch = fetchImplementation;
|
|
23
|
-
}
|
|
24
|
-
async loadApplicationSession() {
|
|
25
|
-
return this.#optional("loadApplicationSession", "application-session", validateApplicationSessionV1);
|
|
26
|
-
}
|
|
27
|
-
async saveApplicationSession(session) {
|
|
28
|
-
await this.#void("saveApplicationSession", "application-session", "PUT", validateApplicationSessionV1(session));
|
|
29
|
-
}
|
|
30
|
-
async deleteApplicationSession() {
|
|
31
|
-
await this.#void("deleteApplicationSession", "application-session", "DELETE");
|
|
32
|
-
}
|
|
33
|
-
async listWorkspaceSnapshots() {
|
|
34
|
-
const value = await this.#json("listWorkspaceSnapshots", "snapshots");
|
|
35
|
-
if (!Array.isArray(value)) throw new WorkspacePersistenceError("Remote snapshot list response must be an array", "listWorkspaceSnapshots");
|
|
36
|
-
return value.map(validateWorkspaceSnapshotRecordV1);
|
|
37
|
-
}
|
|
38
|
-
async loadWorkspaceSnapshot(workspaceInstanceId) {
|
|
39
|
-
return this.#optional("loadWorkspaceSnapshot", `snapshots/${requireId(workspaceInstanceId, "loadWorkspaceSnapshot")}`, validateWorkspaceSnapshotRecordV1);
|
|
40
|
-
}
|
|
41
|
-
async saveWorkspaceSnapshot(snapshot) {
|
|
42
|
-
const validated = validateWorkspaceSnapshotRecordV1(snapshot);
|
|
43
|
-
await this.#void("saveWorkspaceSnapshot", `snapshots/${encodeURIComponent(validated.workspaceInstanceId)}`, "PUT", validated);
|
|
44
|
-
}
|
|
45
|
-
async deleteWorkspaceSnapshot(workspaceInstanceId) {
|
|
46
|
-
await this.#void("deleteWorkspaceSnapshot", `snapshots/${requireId(workspaceInstanceId, "deleteWorkspaceSnapshot")}`, "DELETE");
|
|
47
|
-
}
|
|
48
|
-
async listNamedWorkspaceMetadata() {
|
|
49
|
-
const value = await this.#json("listNamedWorkspaceMetadata", "named-workspaces");
|
|
50
|
-
if (!Array.isArray(value)) throw new WorkspacePersistenceError("Remote workspace metadata response must be an array", "listNamedWorkspaceMetadata");
|
|
51
|
-
return value.map(validateNamedWorkspaceMetadataV1);
|
|
52
|
-
}
|
|
53
|
-
async loadNamedWorkspaceDefinition(id) {
|
|
54
|
-
return this.#optional("loadNamedWorkspaceDefinition", `named-workspaces/${requireId(id, "loadNamedWorkspaceDefinition")}`, validateNamedWorkspaceDefinitionV1);
|
|
55
|
-
}
|
|
56
|
-
async saveNamedWorkspaceDefinition(definition) {
|
|
57
|
-
const validated = validateNamedWorkspaceDefinitionV1(definition);
|
|
58
|
-
await this.#void("saveNamedWorkspaceDefinition", `named-workspaces/${encodeURIComponent(validated.id)}`, "PUT", validated);
|
|
59
|
-
}
|
|
60
|
-
async deleteNamedWorkspaceDefinition(id) {
|
|
61
|
-
await this.#void("deleteNamedWorkspaceDefinition", `named-workspaces/${requireId(id, "deleteNamedWorkspaceDefinition")}`, "DELETE");
|
|
62
|
-
}
|
|
63
|
-
async #optional(operation, path, validator) {
|
|
64
|
-
const response = await this.#request(operation, path, "GET");
|
|
65
|
-
if (404 === response.status) return;
|
|
66
|
-
await this.#assertOk(response, operation);
|
|
67
|
-
return cloneWorkspaceValue(validator(await response.json()));
|
|
68
|
-
}
|
|
69
|
-
async #json(operation, path) {
|
|
70
|
-
const response = await this.#request(operation, path, "GET");
|
|
71
|
-
await this.#assertOk(response, operation);
|
|
72
|
-
return response.json();
|
|
73
|
-
}
|
|
74
|
-
async #void(operation, path, method, body) {
|
|
75
|
-
const response = await this.#request(operation, path, method, body);
|
|
76
|
-
await this.#assertOk(response, operation);
|
|
77
|
-
}
|
|
78
|
-
async #request(operation, path, method, body) {
|
|
79
|
-
const provided = this.#requestInitProvider ? await this.#requestInitProvider() : {};
|
|
80
|
-
const configured = this.#requestInit ?? {};
|
|
81
|
-
const headers = new Headers(configured.headers);
|
|
82
|
-
new Headers(provided.headers).forEach((value, key)=>{
|
|
83
|
-
headers.set(key, value);
|
|
84
|
-
});
|
|
85
|
-
headers.set("Accept", "application/json");
|
|
86
|
-
headers.set("X-Vuu-Application-Namespace", this.scope.applicationNamespace);
|
|
87
|
-
headers.set("X-Vuu-Application-Id", this.scope.applicationId);
|
|
88
|
-
headers.set("X-Vuu-User-Id", this.scope.userId);
|
|
89
|
-
if (void 0 !== body) headers.set("Content-Type", "application/json");
|
|
90
|
-
const scopePath = [
|
|
91
|
-
this.scope.applicationNamespace,
|
|
92
|
-
this.scope.applicationId,
|
|
93
|
-
this.scope.userId
|
|
94
|
-
].map(encodeURIComponent).join("/");
|
|
95
|
-
try {
|
|
96
|
-
return await this.#fetch(`${this.#baseUrl}/${scopePath}/${path}`, {
|
|
97
|
-
...configured,
|
|
98
|
-
...provided,
|
|
99
|
-
headers,
|
|
100
|
-
method,
|
|
101
|
-
body: void 0 === body ? void 0 : JSON.stringify(cloneWorkspaceValue(body))
|
|
102
|
-
});
|
|
103
|
-
} catch (error) {
|
|
104
|
-
if (!(error instanceof Error)) throw error;
|
|
105
|
-
throw new WorkspacePersistenceError(error.message, operation);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
async #assertOk(response, operation) {
|
|
109
|
-
if (!response.ok) throw new WorkspacePersistenceError(response.statusText || `Remote persistence request failed (${response.status})`, operation, response.status);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
export { RemoteWorkspacePersistenceService };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
class WorkspacePersistenceError extends Error {
|
|
2
|
-
operation;
|
|
3
|
-
status;
|
|
4
|
-
constructor(message, operation, status){
|
|
5
|
-
super(message), this.operation = operation, this.status = status;
|
|
6
|
-
this.name = "WorkspacePersistenceError";
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
export { WorkspacePersistenceError };
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { GridLayoutDocumentController } from "@vuu-ui/grid-layout";
|
|
2
|
-
import { WorkspacePersistentStateStore } from "./WorkspacePersistentState.js";
|
|
3
|
-
import { WorkspaceRepository } from "./WorkspaceRepository.js";
|
|
4
|
-
import { WORKSPACE_SNAPSHOT_VERSION } from "./workspace-schemas.js";
|
|
5
|
-
class WorkspaceController {
|
|
6
|
-
definitionId;
|
|
7
|
-
documentController;
|
|
8
|
-
instanceId;
|
|
9
|
-
name;
|
|
10
|
-
persistentState;
|
|
11
|
-
renderers;
|
|
12
|
-
repository;
|
|
13
|
-
settingsCodecs;
|
|
14
|
-
#disposed = false;
|
|
15
|
-
#savingStructure = false;
|
|
16
|
-
#onError;
|
|
17
|
-
#unsubscribe;
|
|
18
|
-
constructor(options){
|
|
19
|
-
this.definitionId = options.definitionId;
|
|
20
|
-
this.instanceId = options.instanceId;
|
|
21
|
-
this.name = options.name;
|
|
22
|
-
this.renderers = options.renderers;
|
|
23
|
-
this.settingsCodecs = options.settingsCodecs;
|
|
24
|
-
this.#onError = options.onError;
|
|
25
|
-
this.repository = new WorkspaceRepository(options.service, options.instanceId, void 0 === options.revision ? void 0 : {
|
|
26
|
-
revision: options.revision,
|
|
27
|
-
snapshot: options.snapshot,
|
|
28
|
-
version: 1,
|
|
29
|
-
workspaceInstanceId: options.instanceId
|
|
30
|
-
});
|
|
31
|
-
this.persistentState = new WorkspacePersistentStateStore(options.snapshot.componentState);
|
|
32
|
-
this.documentController = new GridLayoutDocumentController(options.snapshot.layout);
|
|
33
|
-
this.#unsubscribe = this.persistentState.subscribe(()=>{
|
|
34
|
-
if (!this.#savingStructure) this.#save();
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
get snapshot() {
|
|
38
|
-
const layout = this.documentController.getSnapshot();
|
|
39
|
-
if (!layout) throw new Error(`Workspace "${this.instanceId}" has no layout document`);
|
|
40
|
-
return {
|
|
41
|
-
version: WORKSPACE_SNAPSHOT_VERSION,
|
|
42
|
-
layout,
|
|
43
|
-
componentState: this.persistentState.getSnapshot()
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
handleDocumentChange = (document, change)=>{
|
|
47
|
-
if (this.#disposed) return;
|
|
48
|
-
this.documentController.replace(document);
|
|
49
|
-
this.#savingStructure = true;
|
|
50
|
-
try {
|
|
51
|
-
for (const componentId of change.removedComponentInstanceIds)this.persistentState.purge(componentId);
|
|
52
|
-
} finally{
|
|
53
|
-
this.#savingStructure = false;
|
|
54
|
-
}
|
|
55
|
-
this.#save();
|
|
56
|
-
};
|
|
57
|
-
rename(name) {
|
|
58
|
-
this.name = name;
|
|
59
|
-
}
|
|
60
|
-
dispose() {
|
|
61
|
-
if (!this.#disposed) {
|
|
62
|
-
this.#disposed = true;
|
|
63
|
-
this.#unsubscribe();
|
|
64
|
-
}
|
|
65
|
-
return this.repository.dispose();
|
|
66
|
-
}
|
|
67
|
-
#save() {
|
|
68
|
-
if (!this.#disposed) this.repository.save(this.snapshot).catch((cause)=>{
|
|
69
|
-
if (!this.#disposed) this.#onError(cause instanceof Error ? cause : new Error("Workspace snapshot persistence failed"));
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
export { WorkspaceController };
|
|
@@ -1,194 +0,0 @@
|
|
|
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 };
|
|
@@ -1,67 +0,0 @@
|
|
|
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 };
|