@vuu-ui/vuu-shell 3.3.10 → 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
package/src/shell.js CHANGED
@@ -1,108 +1,186 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { GridLayout, GridLayoutItem, GridLayoutProvider } from "@heswell/grid-layout";
2
3
  import { useComponentCssInjection } from "@salt-ds/styles";
3
4
  import { useWindow } from "@salt-ds/window";
5
+ import { useAuthenticatedUser } from "@vuu-ui/core";
4
6
  import { ContextMenuProvider } from "@vuu-ui/vuu-context-menu";
5
- import { LayoutProvider, StackLayout } from "@vuu-ui/vuu-layout";
7
+ import { useLostConnection } from "@vuu-ui/vuu-data-react";
6
8
  import { NotificationsProvider } from "@vuu-ui/vuu-notifications";
7
- import { ModalProvider } from "@vuu-ui/vuu-ui-controls";
8
- import { logger, registerComponent } from "@vuu-ui/vuu-utils";
9
- import { useCallback, useMemo } from "react";
9
+ import { ContextPanelProvider, ModalProvider } from "@vuu-ui/vuu-ui-controls";
10
+ import { VuuShellLocation } from "@vuu-ui/vuu-utils";
11
+ import clsx from "clsx";
12
+ import { createContext, isValidElement, useCallback, useContext, useMemo, useRef, useState } from "react";
10
13
  import { AppHeader } from "./app-header/index.js";
11
14
  import { ApplicationProvider } from "./application-provider/index.js";
12
- import { LocalPersistenceManager, PersistenceProvider, usePersistenceManager } from "./persistence-manager/index.js";
13
- import { useShellLayout } from "./shell-layout-templates/index.js";
14
- import { UserSettingsPanel } from "./user-settings/index.js";
15
- import { WorkspaceProvider, useWorkspace, useWorkspaceContextMenuItems } from "./workspace-management/index.js";
16
- import { loadingJSON } from "./workspace-management/defaultWorkspaceJSON.js";
17
- import { useLostConnection } from "@vuu-ui/vuu-data-react";
15
+ import { LeftNav } from "./left-nav/index.js";
16
+ import { ContextPanel, SidePanel } from "./shell-layout-templates/index.js";
17
+ import { WorkspaceHost, WorkspaceProvider, createWorkspaceComponentRegistries, shellWorkspaceComponentRegistrations, useWorkspace, useWorkspaceContextMenuItems } from "./workspace-management/index.js";
18
18
  import shell from "./shell.css";
19
- registerComponent("ApplicationSettings", UserSettingsPanel, "view");
20
- if ("production" === process.env.NODE_ENV) {
21
- if ("function" != typeof StackLayout) console.warn("StackLayout module not loaded, will be unable to deserialize from layout JSON");
22
- }
23
- const { error: error } = logger("Shell");
24
19
  const defaultAppHeader = /*#__PURE__*/ jsx(AppHeader, {});
25
- const getAppHeader = (shellLayoutProps)=>shellLayoutProps?.appHeader ?? defaultAppHeader;
26
- const defaultHTMLAttributes = {
27
- className: "vuuShell"
20
+ const defaultRegistries = createWorkspaceComponentRegistries(shellWorkspaceComponentRegistrations);
21
+ const ShellContextPanelStateContext = /*#__PURE__*/ createContext(void 0);
22
+ const StaticContextPanel = ()=>{
23
+ const contextPanel = useContext(ShellContextPanelStateContext);
24
+ return /*#__PURE__*/ jsx(ContextPanel, {
25
+ content: contextPanel?.content,
26
+ expanded: void 0 !== contextPanel,
27
+ id: VuuShellLocation.ContextPanel,
28
+ overlay: true,
29
+ title: contextPanel?.title
30
+ });
28
31
  };
29
- const getHTMLAttributes = (props)=>{
30
- if (props?.htmlAttributes) return {
31
- ...defaultHTMLAttributes,
32
- ...props.htmlAttributes
33
- };
34
- return defaultHTMLAttributes;
32
+ const StaticShellLayout = ({ appHeader = defaultAppHeader, className, leftNavWidth: initialLeftNavWidth = 200, style, workspaceHost = /*#__PURE__*/ jsx(WorkspaceHost, {}), ...htmlAttributes })=>{
33
+ const { setApplicationSetting } = useWorkspace();
34
+ const [leftNavWidth, setLeftNavWidth] = useState(initialLeftNavWidth);
35
+ const [contextPanel, setContextPanel] = useState();
36
+ const contextPanelTrigger = useRef(null);
37
+ const handleLeftNavWidthChange = useCallback((width)=>{
38
+ setLeftNavWidth(width);
39
+ }, []);
40
+ const showContextPanel = useCallback((content, title)=>{
41
+ if (!/*#__PURE__*/ isValidElement(content)) throw new Error(`Context panel component "${content}" must be provided as a React element`);
42
+ contextPanelTrigger.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
43
+ setContextPanel({
44
+ content,
45
+ title
46
+ });
47
+ }, []);
48
+ const hideContextPanel = useCallback(()=>{
49
+ setContextPanel(void 0);
50
+ setApplicationSetting("applicationSettings.panelOpen", false).catch(()=>void 0);
51
+ requestAnimationFrame(()=>contextPanelTrigger.current?.focus());
52
+ }, [
53
+ setApplicationSetting
54
+ ]);
55
+ return /*#__PURE__*/ jsx(ContextPanelProvider, {
56
+ hideContextPanel: hideContextPanel,
57
+ showContextPanel: showContextPanel,
58
+ children: /*#__PURE__*/ jsx(ShellContextPanelStateContext.Provider, {
59
+ value: contextPanel,
60
+ children: /*#__PURE__*/ jsxs(GridLayout, {
61
+ ...htmlAttributes,
62
+ className: clsx("vuuShell", "vuuShell-staticGrid", className),
63
+ colsAndRows: {
64
+ cols: [
65
+ `${initialLeftNavWidth}px`,
66
+ "1fr",
67
+ "0px"
68
+ ],
69
+ rows: [
70
+ "48px",
71
+ "1fr"
72
+ ]
73
+ },
74
+ "full-page": true,
75
+ id: "vuu-shell-grid",
76
+ style: {
77
+ ...style,
78
+ gridTemplateColumns: `${leftNavWidth}px minmax(0, 1fr) 0px`
79
+ },
80
+ children: [
81
+ /*#__PURE__*/ jsx(GridLayoutItem, {
82
+ id: "vuu-shell-left-nav",
83
+ style: {
84
+ gridArea: "1/1/3/2"
85
+ },
86
+ children: /*#__PURE__*/ jsx(SidePanel, {
87
+ id: "vuu-side-panel",
88
+ sizeOpen: initialLeftNavWidth,
89
+ children: /*#__PURE__*/ jsx(LeftNav, {
90
+ onWidthChange: handleLeftNavWidthChange,
91
+ sizeExpanded: initialLeftNavWidth
92
+ })
93
+ })
94
+ }),
95
+ /*#__PURE__*/ jsx(GridLayoutItem, {
96
+ id: "vuu-shell-header",
97
+ style: {
98
+ gridArea: "1/2/2/3"
99
+ },
100
+ children: appHeader
101
+ }),
102
+ /*#__PURE__*/ jsx(GridLayoutItem, {
103
+ className: "vuuShell-content",
104
+ id: "vuu-shell-workspace-host",
105
+ style: {
106
+ gridArea: "2/2/3/3"
107
+ },
108
+ children: workspaceHost
109
+ }),
110
+ /*#__PURE__*/ jsx(GridLayoutItem, {
111
+ id: "vuu-shell-context-panel-host",
112
+ style: {
113
+ gridArea: "1/3/3/4",
114
+ overflow: "visible",
115
+ position: "relative"
116
+ },
117
+ children: /*#__PURE__*/ jsx(StaticContextPanel, {})
118
+ })
119
+ ]
120
+ })
121
+ })
122
+ });
35
123
  };
36
- const VuuApplication = ({ shellLayoutProps: ShellLayoutProps, children })=>{
124
+ const VuuApplication = ({ appHeader, children, htmlAttributes, leftNavWidth })=>{
37
125
  const targetWindow = useWindow();
38
126
  useComponentCssInjection({
39
127
  testId: "vuu-shell",
40
128
  css: shell,
41
129
  window: targetWindow
42
130
  });
43
- const { workspaceJSON, saveApplicationLayout } = useWorkspace();
44
131
  const { buildMenuOptions, handleMenuAction } = useWorkspaceContextMenuItems();
45
- const handleLayoutChange = useCallback((layout)=>{
46
- try {
47
- saveApplicationLayout(layout);
48
- } catch {
49
- error?.("Failed to save layout");
50
- }
51
- }, [
52
- saveApplicationLayout
53
- ]);
54
- const isLayoutLoading = workspaceJSON === loadingJSON;
55
- const initialLayout = useShellLayout({
56
- ...ShellLayoutProps,
57
- appHeader: getAppHeader(ShellLayoutProps),
58
- htmlAttributes: getHTMLAttributes(ShellLayoutProps)
59
- });
60
132
  useLostConnection();
61
- return isLayoutLoading ? null : /*#__PURE__*/ jsxs(ContextMenuProvider, {
133
+ return /*#__PURE__*/ jsxs(ContextMenuProvider, {
62
134
  menuActionHandler: handleMenuAction,
63
135
  menuBuilder: buildMenuOptions,
64
136
  children: [
65
- /*#__PURE__*/ jsx(LayoutProvider, {
66
- workspaceJSON: workspaceJSON,
67
- onLayoutChange: handleLayoutChange,
68
- children: initialLayout
137
+ /*#__PURE__*/ jsx(GridLayoutProvider, {
138
+ children: /*#__PURE__*/ jsx(StaticShellLayout, {
139
+ ...htmlAttributes,
140
+ appHeader: appHeader,
141
+ leftNavWidth: leftNavWidth
142
+ })
69
143
  }),
70
144
  children
71
145
  ]
72
146
  });
73
147
  };
74
- const Shell = ({ logout, user, userSettingsSchema, workspaceProps, ...props })=>{
75
- const persistenceManager = usePersistenceManager();
76
- const localPersistenceManager = useMemo(()=>{
77
- if (persistenceManager) return;
78
- console.log(`No Persistence Manager, configuration data will be persisted to Local Storage, key: 'vuu/${user.username}'`);
79
- return new LocalPersistenceManager(`vuu/${user.username}`);
80
- }, [
81
- persistenceManager,
82
- user.username
148
+ const Shell = ({ appHeader, children, leftNavWidth, logout, saveUrl: _saveUrl, serverUrl: _serverUrl, shellLayoutProps, workspaceProps, ...htmlAttributes })=>{
149
+ const user = useAuthenticatedUser();
150
+ const resolvedWorkspaceProps = useMemo(()=>({
151
+ ...workspaceProps,
152
+ componentRenderers: workspaceProps?.componentRenderers ?? defaultRegistries.renderers,
153
+ settingsCodecs: workspaceProps?.settingsCodecs ?? defaultRegistries.settingsCodecs
154
+ }), [
155
+ workspaceProps
83
156
  ]);
84
- const shellProviders = /*#__PURE__*/ jsx(ApplicationProvider, {
157
+ const resolvedHtmlAttributes = {
158
+ ...htmlAttributes,
159
+ ...shellLayoutProps?.htmlAttributes,
160
+ className: clsx(htmlAttributes.className, shellLayoutProps?.htmlAttributes?.className),
161
+ style: {
162
+ ...htmlAttributes.style,
163
+ ...shellLayoutProps?.htmlAttributes?.style
164
+ }
165
+ };
166
+ return /*#__PURE__*/ jsx(ApplicationProvider, {
85
167
  density: "high",
86
168
  logout: logout,
87
169
  theme: "vuu-theme",
88
- user: user,
89
- userSettingsSchema: userSettingsSchema,
90
- children: /*#__PURE__*/ jsx(WorkspaceProvider, {
91
- ...workspaceProps,
92
- children: /*#__PURE__*/ jsx(ModalProvider, {
93
- children: /*#__PURE__*/ jsx(NotificationsProvider, {
170
+ children: /*#__PURE__*/ jsx(ModalProvider, {
171
+ children: /*#__PURE__*/ jsx(NotificationsProvider, {
172
+ children: /*#__PURE__*/ jsx(WorkspaceProvider, {
173
+ ...resolvedWorkspaceProps,
174
+ userId: user.userName,
94
175
  children: /*#__PURE__*/ jsx(VuuApplication, {
95
- ...props,
96
- user: user
176
+ appHeader: appHeader ?? shellLayoutProps?.appHeader,
177
+ htmlAttributes: resolvedHtmlAttributes,
178
+ leftNavWidth: leftNavWidth ?? shellLayoutProps?.SidePanelProps?.sizeOpen,
179
+ children: children
97
180
  })
98
181
  })
99
182
  })
100
183
  })
101
184
  });
102
- if (persistenceManager) return shellProviders;
103
- return /*#__PURE__*/ jsx(PersistenceProvider, {
104
- persistenceManager: localPersistenceManager,
105
- children: shellProviders
106
- });
107
185
  };
108
- export { Shell };
186
+ export { Shell, StaticShellLayout };
@@ -1,15 +1,10 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { createElement, useCallback, useMemo } from "react";
3
- import { IconButton } from "@vuu-ui/vuu-ui-controls";
4
2
  import { ListBox, Option } from "@salt-ds/core";
5
- import { VuuShellLocation } from "@vuu-ui/vuu-utils";
6
3
  import { useComponentCssInjection } from "@salt-ds/styles";
7
4
  import { useWindow } from "@salt-ds/window";
5
+ import { IconButton } from "@vuu-ui/vuu-ui-controls";
8
6
  import clsx from "clsx";
9
- import { LayoutTile } from "./LayoutTile.js";
10
7
  import { useWorkspace } from "./WorkspaceProvider.js";
11
- import { useLayouts } from "../feature-and-layout-provider/index.js";
12
- import { layoutFromJson, useLayoutProviderDispatch } from "@vuu-ui/vuu-layout";
13
8
  import LayoutList from "./LayoutList.css";
14
9
  const classBase = "vuuLayoutList";
15
10
  const NO_SELECTION = [];
@@ -20,108 +15,73 @@ const LayoutList_LayoutList = ({ className, title, ...htmlAttributes })=>{
20
15
  css: LayoutList,
21
16
  window: targetWindow
22
17
  });
23
- const { layoutMetadata, loadLayoutById } = useWorkspace();
24
- const { systemLayouts } = useLayouts();
25
- const handleLoadLayout = useCallback((layoutId)=>{
26
- if (layoutId) loadLayoutById(layoutId);
27
- }, [
28
- loadLayoutById
29
- ]);
30
- const dispatch = useLayoutProviderDispatch();
31
- const handleLoadSysLayout = (layoutId, layoutJSON)=>{
32
- if (layoutJSON) dispatch({
33
- type: "add",
34
- path: `#${VuuShellLocation.Workspace}`,
35
- component: layoutFromJson(layoutJSON, "0")
36
- });
37
- else throw Error("layoutJSON is required for system layouts");
38
- };
39
- const layoutsByGroup = layoutMetadata.reduce((acc, cur)=>{
40
- if (acc[cur.group]) return {
41
- ...acc,
42
- [cur.group]: [
43
- ...acc[cur.group],
44
- cur
45
- ]
46
- };
47
- return {
48
- ...acc,
49
- [cur.group]: [
50
- cur
51
- ]
52
- };
53
- }, {});
54
- let sysContent = [];
55
- if (systemLayouts) sysContent = [
56
- /*#__PURE__*/ jsxs("div", {
57
- className: `${classBase}-group`,
58
- children: [
59
- /*#__PURE__*/ jsx("div", {
60
- className: `${classBase}-groupHeader`,
61
- children: "System Layout"
62
- }),
63
- /*#__PURE__*/ jsx(ListBox, {
64
- selected: NO_SELECTION,
65
- children: systemLayouts.map((layout)=>/*#__PURE__*/ jsx(Option, {
66
- value: layout,
67
- children: /*#__PURE__*/ createElement(LayoutTile, {
68
- ...htmlAttributes,
69
- key: layout.id,
70
- metadata: layout,
71
- onLoadLayout: handleLoadSysLayout
72
- })
73
- }, layout.id))
74
- })
75
- ]
76
- }, 0)
77
- ];
78
- const content = useMemo(()=>Object.entries(layoutsByGroup).map(([heading, layoutMetadata], index)=>/*#__PURE__*/ jsxs("div", {
79
- className: `${classBase}-group`,
80
- children: [
81
- /*#__PURE__*/ jsx("div", {
82
- className: `${classBase}-groupHeader`,
83
- children: heading
84
- }),
85
- /*#__PURE__*/ jsx(ListBox, {
86
- selected: NO_SELECTION,
87
- children: layoutMetadata.map((layout)=>/*#__PURE__*/ jsxs(Option, {
88
- value: layout,
89
- children: [
90
- /*#__PURE__*/ createElement(LayoutTile, {
91
- ...htmlAttributes,
92
- key: layout.id,
93
- metadata: layout,
94
- onLoadLayout: handleLoadLayout
95
- }),
96
- /*#__PURE__*/ jsx(IconButton, {
97
- className: `${classBase}-menu`,
98
- "data-embedded": true,
99
- icon: "more-vert",
100
- appearance: "transparent",
101
- sentiment: "neutral"
102
- })
103
- ]
104
- }, layout.id))
105
- })
106
- ]
107
- }, index)), [
108
- handleLoadLayout,
109
- htmlAttributes,
110
- layoutsByGroup
111
- ]);
18
+ const { deleteNamedWorkspace, namedWorkspaces, openNamedWorkspace, openSystemWorkspace, status, systemWorkspaces } = useWorkspace();
112
19
  return /*#__PURE__*/ jsxs("div", {
113
20
  ...htmlAttributes,
114
- className: clsx(classBase, "vuuScrollable"),
21
+ className: clsx(classBase, className, "vuuScrollable"),
115
22
  children: [
116
23
  /*#__PURE__*/ jsx("div", {
117
24
  className: `${classBase}-header`,
118
25
  children: title
119
26
  }),
120
- /*#__PURE__*/ jsx("div", {
27
+ /*#__PURE__*/ jsxs("div", {
121
28
  className: `${classBase}-content`,
122
29
  children: [
123
- sysContent,
124
- ...content
30
+ "loading" === status ? /*#__PURE__*/ jsx("div", {
31
+ role: "status",
32
+ children: "Loading layouts…"
33
+ }) : null,
34
+ systemWorkspaces.length > 0 ? /*#__PURE__*/ jsxs("div", {
35
+ className: `${classBase}-group`,
36
+ children: [
37
+ /*#__PURE__*/ jsx("div", {
38
+ className: `${classBase}-groupHeader`,
39
+ children: "System Layouts"
40
+ }),
41
+ /*#__PURE__*/ jsx(ListBox, {
42
+ selected: NO_SELECTION,
43
+ children: systemWorkspaces.map((definition)=>/*#__PURE__*/ jsx(Option, {
44
+ value: definition.id,
45
+ children: /*#__PURE__*/ jsx("button", {
46
+ onClick: ()=>void openSystemWorkspace(definition),
47
+ type: "button",
48
+ children: definition.name
49
+ })
50
+ }, definition.id))
51
+ })
52
+ ]
53
+ }) : null,
54
+ /*#__PURE__*/ jsxs("div", {
55
+ className: `${classBase}-group`,
56
+ children: [
57
+ /*#__PURE__*/ jsx("div", {
58
+ className: `${classBase}-groupHeader`,
59
+ children: "My Layouts"
60
+ }),
61
+ 0 === namedWorkspaces.length && "loading" !== status ? /*#__PURE__*/ jsx("div", {
62
+ children: "No saved workspaces"
63
+ }) : /*#__PURE__*/ jsx(ListBox, {
64
+ selected: NO_SELECTION,
65
+ children: namedWorkspaces.map((definition)=>/*#__PURE__*/ jsxs(Option, {
66
+ value: definition.id,
67
+ children: [
68
+ /*#__PURE__*/ jsx("button", {
69
+ onClick: ()=>void openNamedWorkspace(definition.id),
70
+ type: "button",
71
+ children: definition.name
72
+ }),
73
+ /*#__PURE__*/ jsx(IconButton, {
74
+ "aria-label": `Delete ${definition.name}`,
75
+ appearance: "transparent",
76
+ icon: "delete",
77
+ onClick: ()=>void deleteNamedWorkspace(definition.id),
78
+ sentiment: "neutral"
79
+ })
80
+ ]
81
+ }, definition.id))
82
+ })
83
+ ]
84
+ })
125
85
  ]
126
86
  })
127
87
  ]
@@ -2,10 +2,10 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Button, ComboBox, FormField, FormFieldLabel, Input, Option, Text } from "@salt-ds/core";
3
3
  import { useComponentCssInjection } from "@salt-ds/styles";
4
4
  import { useWindow } from "@salt-ds/window";
5
+ import { useAuthenticatedUser } from "@vuu-ui/core";
5
6
  import { useCallback, useEffect, useMemo, useState } from "react";
6
7
  import { takeScreenshot } from "./screenshot-utils.js";
7
8
  import SaveLayoutPanel from "./SaveLayoutPanel.css";
8
- import { useApplicationUser } from "../application-provider/index.js";
9
9
  const classBase = "vuuSaveLayoutPanel";
10
10
  const groups = [
11
11
  "Group 1",
@@ -26,7 +26,7 @@ const SaveLayoutPanel_SaveLayoutPanel = (props)=>{
26
26
  const [group, setGroup] = useState("");
27
27
  const [screenshot, setScreenshot] = useState();
28
28
  const [screenshotErrorMessage, setScreenshotErrorMessage] = useState();
29
- const { username } = useApplicationUser();
29
+ const { userName } = useAuthenticatedUser();
30
30
  useEffect(()=>{
31
31
  if (componentId) takeScreenshot(document.getElementById(componentId)).then((screenshot)=>{
32
32
  setScreenshot(screenshot);
@@ -41,7 +41,7 @@ const SaveLayoutPanel_SaveLayoutPanel = (props)=>{
41
41
  name: layoutName,
42
42
  group,
43
43
  screenshot: screenshot ?? "",
44
- user: username
44
+ user: userName
45
45
  });
46
46
  };
47
47
  const screenshotContent = useMemo(()=>{
@@ -0,0 +1,73 @@
1
+ import { GridLayoutDocumentController } from "@heswell/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 };