@vuu-ui/vuu-shell 0.7.0-debug → 0.7.1-debug

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/cjs/index.js CHANGED
@@ -37,6 +37,7 @@ __export(src_exports, {
37
37
  DensitySwitch: () => DensitySwitch,
38
38
  Feature: () => Feature,
39
39
  LoginPanel: () => LoginPanel,
40
+ SessionEditingForm: () => SessionEditingForm,
40
41
  Shell: () => Shell,
41
42
  ShellContextProvider: () => ShellContextProvider,
42
43
  ThemeContext: () => ThemeContext,
@@ -54,7 +55,7 @@ var import_react = __toESM(require("react"));
54
55
  var import_classnames = __toESM(require("classnames"));
55
56
  var import_jsx_runtime = require("react/jsx-runtime");
56
57
  var ConnectionStatusIcon = ({ connectionStatus, className, element = "span", ...props }) => {
57
- const [classBase5, setClassBase] = (0, import_react.useState)("vuuConnectingStatus");
58
+ const [classBase6, setClassBase] = (0, import_react.useState)("vuuConnectingStatus");
58
59
  (0, import_react.useEffect)(() => {
59
60
  switch (connectionStatus) {
60
61
  case "connected":
@@ -75,7 +76,7 @@ var ConnectionStatusIcon = ({ connectionStatus, className, element = "span", ...
75
76
  element,
76
77
  {
77
78
  ...props,
78
- className: (0, import_classnames.default)("vuuStatus vuuIcon", classBase5, className)
79
+ className: (0, import_classnames.default)("vuuStatus vuuIcon", classBase6, className)
79
80
  }
80
81
  );
81
82
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vuuStatus-container salt-theme", children: [
@@ -127,11 +128,11 @@ var ErrorBoundary = class extends import_react3.default.Component {
127
128
  super(props);
128
129
  this.state = { errorMessage: null };
129
130
  }
130
- static getDerivedStateFromError(error) {
131
- return { errorMessage: error.message };
131
+ static getDerivedStateFromError(error2) {
132
+ return { errorMessage: error2.message };
132
133
  }
133
- componentDidCatch(error, errorInfo) {
134
- console.log(error, errorInfo);
134
+ componentDidCatch(error2, errorInfo) {
135
+ console.log(error2, errorInfo);
135
136
  }
136
137
  render() {
137
138
  if (this.state.errorMessage) {
@@ -150,18 +151,45 @@ var Loader = () => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { classNa
150
151
 
151
152
  // src/feature/Feature.tsx
152
153
  var import_jsx_runtime5 = require("react/jsx-runtime");
154
+ var componentsMap = /* @__PURE__ */ new Map();
155
+ var useCachedFeature = (url) => {
156
+ (0, import_react4.useEffect)(
157
+ () => () => {
158
+ componentsMap.delete(url);
159
+ },
160
+ [url]
161
+ );
162
+ if (!componentsMap.has(url)) {
163
+ componentsMap.set(
164
+ url,
165
+ import_react4.default.lazy(() => import(
166
+ /* @vite-ignore */
167
+ url
168
+ ))
169
+ );
170
+ }
171
+ return componentsMap.get(url);
172
+ };
153
173
  function RawFeature({
154
174
  url,
155
175
  css,
156
176
  params,
157
177
  ...props
158
178
  }) {
179
+ console.log("Feature render", { css, url, props });
180
+ (0, import_react4.useEffect)(() => {
181
+ console.log("%cFeature mount", "color: green;");
182
+ return () => {
183
+ console.log("%cFeature unmount", "color:red;");
184
+ };
185
+ }, []);
159
186
  if (css) {
160
187
  import(
161
188
  /* @vite-ignore */
162
189
  css
163
190
  ).then(
164
191
  (cssModule) => {
192
+ console.log("%cInject Styles", "color: blue;font-weight: bold");
165
193
  document.adoptedStyleSheets = [
166
194
  ...document.adoptedStyleSheets,
167
195
  cssModule.default
@@ -169,10 +197,7 @@ function RawFeature({
169
197
  }
170
198
  );
171
199
  }
172
- const LazyFeature = import_react4.default.lazy(() => import(
173
- /* @vite-ignore */
174
- url
175
- ));
200
+ const LazyFeature = useCachedFeature(url);
176
201
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react4.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Loader, {}), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LazyFeature, { ...props, ...params }) }) });
177
202
  }
178
203
  var Feature = import_react4.default.memo(RawFeature);
@@ -238,16 +263,273 @@ var logout = (loginUrl) => {
238
263
  redirectToLogin(loginUrl);
239
264
  };
240
265
 
241
- // src/shell.tsx
266
+ // src/session-editing-form/SessionEditingForm.tsx
267
+ var import_react6 = require("react");
268
+ var import_classnames3 = __toESM(require("classnames"));
269
+ var import_core2 = require("@salt-ds/core");
270
+ var import_core3 = require("@salt-ds/core");
242
271
  var import_vuu_data = require("@vuu-ui/vuu-data");
243
- var import_classnames5 = __toESM(require("classnames"));
244
- var import_react11 = require("react");
272
+ var import_vuu_utils2 = require("@vuu-ui/vuu-utils");
273
+ var import_jsx_runtime7 = require("react/jsx-runtime");
274
+ var classBase3 = "vuuSessionEditingForm";
275
+ var getField = (fields, name) => {
276
+ const field = fields.find((f) => f.name === name);
277
+ if (field) {
278
+ return field;
279
+ } else {
280
+ throw Error(`SessionEditingForm, no field '${name}' found`);
281
+ }
282
+ };
283
+ var getFieldNameAndValue = (evt) => {
284
+ const {
285
+ dataset: { field },
286
+ value
287
+ } = evt.target;
288
+ if (field === void 0) {
289
+ throw Error("SessionEditingForm, form field has no field name");
290
+ }
291
+ return [field, value];
292
+ };
293
+ var Status = {
294
+ uninitialised: 0,
295
+ unchanged: 1,
296
+ changed: 2,
297
+ invalid: 3
298
+ };
299
+ function getTypedValue(value, type, throwIfUndefined = false) {
300
+ switch (type) {
301
+ case "int":
302
+ case "long": {
303
+ const typedValue = parseInt(value, 10);
304
+ if ((0, import_vuu_utils2.isValidNumber)(typedValue)) {
305
+ return typedValue;
306
+ } else if (throwIfUndefined) {
307
+ throw Error("SessionEditingForm getTypedValue");
308
+ } else {
309
+ return void 0;
310
+ }
311
+ }
312
+ case "double": {
313
+ const typedValue = parseFloat(value);
314
+ if ((0, import_vuu_utils2.isValidNumber)(typedValue)) {
315
+ return typedValue;
316
+ }
317
+ return void 0;
318
+ }
319
+ case "boolean":
320
+ return value === "true" ? true : false;
321
+ default:
322
+ return value;
323
+ }
324
+ }
325
+ var getDataSource = (dataSource, schema) => {
326
+ if (dataSource) {
327
+ return dataSource;
328
+ } else if (schema) {
329
+ return new import_vuu_data.RemoteDataSource({
330
+ bufferSize: 0,
331
+ table: schema.table,
332
+ columns: schema.columns.map((col) => col.name)
333
+ });
334
+ } else {
335
+ throw Error(
336
+ "SessionEditingForm: either a DataSource or a TableSchema must be provided"
337
+ );
338
+ }
339
+ };
340
+ var SessionEditingForm = ({
341
+ className,
342
+ config: { fields, key: keyField },
343
+ dataSource: dataSourceProp,
344
+ id: idProp,
345
+ onClose,
346
+ schema,
347
+ ...htmlAttributes
348
+ }) => {
349
+ const [values, setValues] = (0, import_react6.useState)();
350
+ const [errorMessage, setErrorMessage] = (0, import_react6.useState)("");
351
+ const formContentRef = (0, import_react6.useRef)(null);
352
+ const initialDataRef = (0, import_react6.useRef)();
353
+ const dataStatusRef = (0, import_react6.useRef)(Status.uninitialised);
354
+ const dataSource = (0, import_react6.useMemo)(() => {
355
+ const applyServerData = (data) => {
356
+ if (columnMap) {
357
+ const values2 = {};
358
+ for (const column of dataSource.columns) {
359
+ values2[column] = data[columnMap[column]];
360
+ }
361
+ if (dataStatusRef.current === Status.uninitialised) {
362
+ dataStatusRef.current = Status.unchanged;
363
+ initialDataRef.current = values2;
364
+ }
365
+ setValues(values2);
366
+ }
367
+ };
368
+ const ds = getDataSource(dataSourceProp, schema);
369
+ const columnMap = (0, import_vuu_utils2.buildColumnMap)(ds.columns);
370
+ ds.subscribe({ range: { from: 0, to: 5 } }, (message) => {
371
+ if (message.type === "viewport-update" && message.rows) {
372
+ if (dataStatusRef.current === Status.uninitialised) {
373
+ applyServerData(message.rows[0]);
374
+ } else {
375
+ console.log("what do we do with server updates");
376
+ }
377
+ }
378
+ });
379
+ return ds;
380
+ }, [dataSourceProp, schema]);
381
+ const id = (0, import_core2.useIdMemo)(idProp);
382
+ const handleChange = (0, import_react6.useCallback)(
383
+ (evt) => {
384
+ const [field, value] = getFieldNameAndValue(evt);
385
+ const { type } = getField(fields, field);
386
+ const typedValue = getTypedValue(value, type);
387
+ setValues((values2 = {}) => {
388
+ const newValues = {
389
+ ...values2,
390
+ [field]: typedValue
391
+ };
392
+ const notUpdated = (0, import_vuu_utils2.shallowEquals)(newValues, initialDataRef.current);
393
+ dataStatusRef.current = notUpdated ? Status.unchanged : typedValue !== void 0 ? Status.changed : Status.invalid;
394
+ return newValues;
395
+ });
396
+ },
397
+ [fields]
398
+ );
399
+ const handleBlur = (0, import_react6.useCallback)(
400
+ (evt) => {
401
+ const [field, value] = getFieldNameAndValue(evt);
402
+ const { type } = getField(fields, field);
403
+ console.log("BLUR", {
404
+ keyField
405
+ });
406
+ const rowKey = values == null ? void 0 : values[keyField];
407
+ const typedValue = getTypedValue(value, type, true);
408
+ if (typeof rowKey === "string") {
409
+ dataSource.menuRpcCall({
410
+ rowKey,
411
+ field,
412
+ value: typedValue,
413
+ type: "VP_EDIT_CELL_RPC"
414
+ });
415
+ }
416
+ },
417
+ [dataSource, fields, keyField, values]
418
+ );
419
+ const handleSubmit = (0, import_react6.useCallback)(async () => {
420
+ const response = await dataSource.menuRpcCall({
421
+ type: "VP_EDIT_SUBMIT_FORM_RPC"
422
+ });
423
+ if ((0, import_vuu_data.isErrorResponse)(response)) {
424
+ setErrorMessage(response.error);
425
+ }
426
+ }, [dataSource]);
427
+ const handleKeyDown = (0, import_react6.useCallback)(
428
+ (evt) => {
429
+ if (evt.key === "Enter" && dataStatusRef.current === Status.changed) {
430
+ handleSubmit();
431
+ }
432
+ },
433
+ [handleSubmit]
434
+ );
435
+ const handleCancel = (0, import_react6.useCallback)(() => {
436
+ onClose();
437
+ }, [onClose]);
438
+ const getFormControl = (field) => {
439
+ var _a;
440
+ const value = String((_a = values == null ? void 0 : values[field.name]) != null ? _a : "");
441
+ if (field.readonly || field.name === keyField) {
442
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: `${classBase3}-fieldValue vuuReadOnly`, children: value });
443
+ } else {
444
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
445
+ "input",
446
+ {
447
+ className: `${classBase3}-fieldValue`,
448
+ "data-field": field.name,
449
+ onBlur: handleBlur,
450
+ onChange: handleChange,
451
+ type: "text",
452
+ value,
453
+ id: `${id}-input-${field.name}`
454
+ }
455
+ );
456
+ }
457
+ };
458
+ (0, import_react6.useEffect)(() => {
459
+ if (formContentRef.current) {
460
+ const firstInput = formContentRef.current.querySelector(
461
+ "input"
462
+ );
463
+ if (firstInput) {
464
+ setTimeout(() => {
465
+ firstInput.focus();
466
+ console.log("select item");
467
+ firstInput.select();
468
+ }, 100);
469
+ }
470
+ }
471
+ }, []);
472
+ const isDirty = dataStatusRef.current === Status.changed;
473
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { ...htmlAttributes, className: (0, import_classnames3.default)(classBase3, className), children: [
474
+ errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
475
+ "div",
476
+ {
477
+ className: `${classBase3}-errorBanner`,
478
+ "data-icon": "error",
479
+ title: errorMessage,
480
+ children: "Error, edit(s) not saved"
481
+ }
482
+ ) : void 0,
483
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
484
+ "div",
485
+ {
486
+ className: `${classBase3}-content`,
487
+ ref: formContentRef,
488
+ onKeyDown: handleKeyDown,
489
+ children: fields.map((field) => {
490
+ var _a;
491
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `${classBase3}-field`, children: [
492
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
493
+ "label",
494
+ {
495
+ className: (0, import_classnames3.default)(`${classBase3}-fieldLabel`, {
496
+ [`${classBase3}-required`]: field.required
497
+ }),
498
+ htmlFor: `${id}-input-${field.name}`,
499
+ children: (_a = field == null ? void 0 : field.label) != null ? _a : field.description
500
+ }
501
+ ),
502
+ getFormControl(field)
503
+ ] }, field.name);
504
+ })
505
+ }
506
+ ),
507
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `${classBase3}-buttonbar salt-theme salt-density-high`, children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
509
+ import_core3.Button,
510
+ {
511
+ type: "submit",
512
+ variant: "cta",
513
+ disabled: !isDirty,
514
+ onClick: handleSubmit,
515
+ children: "Submit"
516
+ }
517
+ ),
518
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_core3.Button, { variant: "secondary", onClick: handleCancel, children: "Cancel" })
519
+ ] })
520
+ ] });
521
+ };
522
+
523
+ // src/shell.tsx
524
+ var import_vuu_data2 = require("@vuu-ui/vuu-data");
525
+ var import_classnames6 = __toESM(require("classnames"));
526
+ var import_react12 = require("react");
245
527
 
246
528
  // src/ShellContextProvider.tsx
247
- var import_react6 = require("react");
248
- var import_jsx_runtime7 = require("react/jsx-runtime");
529
+ var import_react7 = require("react");
530
+ var import_jsx_runtime8 = require("react/jsx-runtime");
249
531
  var defaultConfig = {};
250
- var ShellContext = (0, import_react6.createContext)(defaultConfig);
532
+ var ShellContext = (0, import_react7.createContext)(defaultConfig);
251
533
  var Provider = ({
252
534
  children,
253
535
  context,
@@ -257,53 +539,106 @@ var Provider = ({
257
539
  ...inheritedContext,
258
540
  ...context
259
541
  };
260
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ShellContext.Provider, { value: mergedContext, children });
542
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ShellContext.Provider, { value: mergedContext, children });
261
543
  };
262
544
  var ShellContextProvider = ({
263
545
  children,
264
546
  value
265
547
  }) => {
266
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ShellContext.Consumer, { children: (context) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Provider, { context: value, inheritedContext: context, children }) });
548
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ShellContext.Consumer, { children: (context) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Provider, { context: value, inheritedContext: context, children }) });
267
549
  };
268
550
  var useShellContext = () => {
269
- return (0, import_react6.useContext)(ShellContext);
551
+ return (0, import_react7.useContext)(ShellContext);
270
552
  };
271
553
 
272
- // src/use-layout-config.js
273
- var import_react7 = require("react");
274
- var useLayoutConfig = (user, defaultLayout) => {
275
- const [layout, _setLayout] = (0, import_react7.useState)(defaultLayout);
554
+ // src/layout-config/use-layout-config.ts
555
+ var import_react8 = require("react");
556
+
557
+ // src/layout-config/local-config.ts
558
+ var loadLocalConfig = (saveUrl, user, id = "latest") => new Promise((resolve, reject) => {
559
+ console.log(
560
+ `load local config at ${saveUrl} for user ${user.username}, id ${id}`
561
+ );
562
+ const data = localStorage.getItem(saveUrl);
563
+ if (data) {
564
+ const layout = JSON.parse(data);
565
+ resolve(layout);
566
+ } else {
567
+ reject();
568
+ }
569
+ });
570
+ var saveLocalConfig = (saveUrl, user, data) => new Promise((resolve, reject) => {
571
+ try {
572
+ localStorage.setItem(saveUrl, JSON.stringify(data));
573
+ resolve(void 0);
574
+ } catch {
575
+ reject();
576
+ }
577
+ });
578
+
579
+ // src/layout-config/remote-config.ts
580
+ var loadRemoteConfig = (saveUrl, user, id = "latest") => new Promise((resolve, reject) => {
581
+ fetch(`${saveUrl}/${user.username}/${id}`, {}).then((response) => {
582
+ if (response.ok) {
583
+ resolve(response.json());
584
+ } else {
585
+ reject(void 0);
586
+ }
587
+ }).catch(() => {
588
+ reject(void 0);
589
+ });
590
+ });
591
+ var saveRemoteConfig = (saveUrl, user, data) => new Promise((resolve, reject) => {
592
+ fetch(`${saveUrl}/${user.username}`, {
593
+ method: "POST",
594
+ headers: {
595
+ "Content-Type": "application/json"
596
+ },
597
+ body: JSON.stringify(data)
598
+ }).then((response) => {
599
+ if (response.ok) {
600
+ resolve(void 0);
601
+ } else {
602
+ reject();
603
+ }
604
+ });
605
+ });
606
+
607
+ // src/layout-config/use-layout-config.ts
608
+ var useLayoutConfig = ({
609
+ saveLocation,
610
+ saveUrl = "api/vui",
611
+ user,
612
+ defaultLayout
613
+ }) => {
614
+ const [layout, _setLayout] = (0, import_react8.useState)(defaultLayout);
615
+ const usingRemote = saveLocation === "remote";
616
+ const loadConfig = usingRemote ? loadRemoteConfig : loadLocalConfig;
617
+ const saveConfig = usingRemote ? saveRemoteConfig : saveLocalConfig;
276
618
  const setLayout = (layout2) => {
277
619
  _setLayout(layout2);
278
620
  };
279
- const load = (0, import_react7.useCallback)(
621
+ const load = (0, import_react8.useCallback)(
280
622
  async (id = "latest") => {
281
- fetch(`api/vui/${user.username}/${id}`, {}).then((response) => {
282
- return response.ok ? response.json() : defaultLayout;
283
- }).then(setLayout).catch(() => {
623
+ try {
624
+ const layout2 = await loadConfig(saveUrl, user, id);
625
+ setLayout(layout2);
626
+ } catch {
284
627
  setLayout(defaultLayout);
285
- });
628
+ }
286
629
  },
287
- [defaultLayout, user.username]
630
+ [defaultLayout, loadConfig, saveUrl, user]
288
631
  );
289
- (0, import_react7.useEffect)(() => {
632
+ (0, import_react8.useEffect)(() => {
290
633
  load();
291
634
  }, [load]);
292
- const saveData = (0, import_react7.useCallback)(
635
+ const saveData = (0, import_react8.useCallback)(
293
636
  (data) => {
294
- fetch(`api/vui/${user.username}`, {
295
- method: "POST",
296
- headers: {
297
- "Content-Type": "application/json"
298
- },
299
- body: JSON.stringify(data)
300
- }).then((response) => {
301
- return response.ok ? response.json() : defaultLayout;
302
- });
637
+ saveConfig(saveUrl, user, data);
303
638
  },
304
- [defaultLayout, user]
639
+ [saveConfig, saveUrl, user]
305
640
  );
306
- const loadLayoutById = (0, import_react7.useCallback)(
641
+ const loadLayoutById = (0, import_react8.useCallback)(
307
642
  (id) => {
308
643
  load(id);
309
644
  },
@@ -311,25 +646,24 @@ var useLayoutConfig = (user, defaultLayout) => {
311
646
  );
312
647
  return [layout, saveData, loadLayoutById];
313
648
  };
314
- var use_layout_config_default = useLayoutConfig;
315
649
 
316
650
  // src/shell.tsx
317
651
  var import_vuu_layout2 = require("@vuu-ui/vuu-layout");
318
652
 
319
653
  // src/app-header/AppHeader.tsx
320
- var import_react10 = require("react");
654
+ var import_react11 = require("react");
321
655
 
322
656
  // src/user-profile/UserProfile.tsx
323
- var import_core3 = require("@salt-ds/core");
657
+ var import_core5 = require("@salt-ds/core");
324
658
  var import_salt_lab4 = require("@heswell/salt-lab");
325
659
  var import_icons2 = require("@salt-ds/icons");
326
660
 
327
661
  // src/user-profile/UserPanel.tsx
328
- var import_vuu_utils2 = require("@vuu-ui/vuu-utils");
662
+ var import_vuu_utils3 = require("@vuu-ui/vuu-utils");
329
663
  var import_salt_lab3 = require("@heswell/salt-lab");
330
- var import_core2 = require("@salt-ds/core");
664
+ var import_core4 = require("@salt-ds/core");
331
665
  var import_icons = require("@salt-ds/icons");
332
- var import_react8 = require("react");
666
+ var import_react9 = require("react");
333
667
 
334
668
  // src/get-layout-history.ts
335
669
  var getLayoutHistory = async (user) => {
@@ -342,29 +676,29 @@ var getLayoutHistory = async (user) => {
342
676
  };
343
677
 
344
678
  // src/user-profile/UserPanel.tsx
345
- var import_jsx_runtime8 = require("react/jsx-runtime");
679
+ var import_jsx_runtime9 = require("react/jsx-runtime");
346
680
  var byLastUpdate = ({ lastUpdate: l1 }, { lastUpdate: l2 }) => {
347
681
  return l2 === l1 ? 0 : l2 < l1 ? -1 : 1;
348
682
  };
349
683
  var HistoryListItem = (props) => {
350
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_salt_lab3.ListItem, { ...props });
684
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_salt_lab3.ListItem, { ...props });
351
685
  };
352
- var UserPanel = (0, import_react8.forwardRef)(function UserPanel2({ loginUrl, onNavigate, user, layoutId = "latest" }, forwardedRef) {
353
- const [history, setHistory] = (0, import_react8.useState)([]);
354
- (0, import_react8.useEffect)(() => {
686
+ var UserPanel = (0, import_react9.forwardRef)(function UserPanel2({ loginUrl, onNavigate, user, layoutId = "latest" }, forwardedRef) {
687
+ const [history, setHistory] = (0, import_react9.useState)([]);
688
+ (0, import_react9.useEffect)(() => {
355
689
  async function getHistory() {
356
690
  const history2 = await getLayoutHistory(user);
357
691
  const sortedHistory = history2.filter((item) => item.id !== "latest").sort(byLastUpdate).map(({ id, lastUpdate }) => ({
358
692
  lastUpdate,
359
693
  id,
360
- label: `Saved at ${(0, import_vuu_utils2.formatDate)(new Date(lastUpdate), "kk:mm:ss")}`
694
+ label: `Saved at ${(0, import_vuu_utils3.formatDate)(new Date(lastUpdate), "kk:mm:ss")}`
361
695
  }));
362
696
  console.log({ sortedHistory });
363
697
  setHistory(sortedHistory);
364
698
  }
365
699
  getHistory();
366
700
  }, [user]);
367
- const handleHisorySelected = (0, import_react8.useCallback)(
701
+ const handleHisorySelected = (0, import_react9.useCallback)(
368
702
  (evt, selected2) => {
369
703
  if (selected2) {
370
704
  onNavigate(selected2.id);
@@ -372,12 +706,12 @@ var UserPanel = (0, import_react8.forwardRef)(function UserPanel2({ loginUrl, on
372
706
  },
373
707
  [onNavigate]
374
708
  );
375
- const handleLogout = (0, import_react8.useCallback)(() => {
709
+ const handleLogout = (0, import_react9.useCallback)(() => {
376
710
  logout(loginUrl);
377
711
  }, [loginUrl]);
378
712
  const selected = history.length === 0 ? null : layoutId === "latest" ? history[0] : history.find((i) => i.id === layoutId);
379
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "vuuUserPanel", ref: forwardedRef, children: [
380
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
713
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "vuuUserPanel", ref: forwardedRef, children: [
714
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
381
715
  import_salt_lab3.List,
382
716
  {
383
717
  ListItem: HistoryListItem,
@@ -387,15 +721,15 @@ var UserPanel = (0, import_react8.forwardRef)(function UserPanel2({ loginUrl, on
387
721
  source: history
388
722
  }
389
723
  ),
390
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "vuuUserPanel-buttonBar", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_core2.Button, { "aria-label": "logout", onClick: handleLogout, children: [
391
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons.ExportIcon, {}),
724
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "vuuUserPanel-buttonBar", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_core4.Button, { "aria-label": "logout", onClick: handleLogout, children: [
725
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons.ExportIcon, {}),
392
726
  " Logout"
393
727
  ] }) })
394
728
  ] });
395
729
  });
396
730
 
397
731
  // src/user-profile/UserProfile.tsx
398
- var import_jsx_runtime9 = require("react/jsx-runtime");
732
+ var import_jsx_runtime10 = require("react/jsx-runtime");
399
733
  var UserProfile = ({
400
734
  layoutId,
401
735
  loginUrl,
@@ -405,9 +739,9 @@ var UserProfile = ({
405
739
  const handleNavigate = (id) => {
406
740
  onNavigate(id);
407
741
  };
408
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_salt_lab4.DropdownBase, { className: "vuuUserProfile", placement: "bottom-end", children: [
409
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_core3.Button, { variant: "secondary", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons2.UserSolidIcon, {}) }),
410
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
742
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_salt_lab4.DropdownBase, { className: "vuuUserProfile", placement: "bottom-end", children: [
743
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_core5.Button, { variant: "secondary", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons2.UserSolidIcon, {}) }),
744
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
411
745
  UserPanel,
412
746
  {
413
747
  layoutId,
@@ -421,11 +755,11 @@ var UserProfile = ({
421
755
 
422
756
  // src/theme-switch/ThemeSwitch.tsx
423
757
  var import_salt_lab5 = require("@heswell/salt-lab");
424
- var import_classnames3 = __toESM(require("classnames"));
425
- var import_core4 = require("@salt-ds/core");
426
- var import_react9 = require("react");
427
- var import_jsx_runtime10 = require("react/jsx-runtime");
428
- var classBase3 = "vuuThemeSwitch";
758
+ var import_classnames4 = __toESM(require("classnames"));
759
+ var import_core6 = require("@salt-ds/core");
760
+ var import_react10 = require("react");
761
+ var import_jsx_runtime11 = require("react/jsx-runtime");
762
+ var classBase4 = "vuuThemeSwitch";
429
763
  var modes = ["light", "dark"];
430
764
  var ThemeSwitch = ({
431
765
  className: classNameProp,
@@ -434,14 +768,14 @@ var ThemeSwitch = ({
434
768
  onChange,
435
769
  ...htmlAttributes
436
770
  }) => {
437
- const [mode, setMode] = (0, import_core4.useControlled)({
771
+ const [mode, setMode] = (0, import_core6.useControlled)({
438
772
  controlled: modeProp,
439
773
  default: defaultModeProp != null ? defaultModeProp : "light",
440
774
  name: "ThemeSwitch",
441
775
  state: "mode"
442
776
  });
443
777
  const selectedIndex = modes.indexOf(mode);
444
- const handleChangeSecondary = (0, import_react9.useCallback)(
778
+ const handleChangeSecondary = (0, import_react10.useCallback)(
445
779
  (_evt, index) => {
446
780
  const mode2 = modes[index];
447
781
  setMode(mode2);
@@ -449,8 +783,8 @@ var ThemeSwitch = ({
449
783
  },
450
784
  [onChange, setMode]
451
785
  );
452
- const className = (0, import_classnames3.default)(classBase3, classNameProp);
453
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
786
+ const className = (0, import_classnames4.default)(classBase4, classNameProp);
787
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
454
788
  import_salt_lab5.ToggleButtonGroup,
455
789
  {
456
790
  className,
@@ -458,7 +792,7 @@ var ThemeSwitch = ({
458
792
  onChange: handleChangeSecondary,
459
793
  selectedIndex,
460
794
  children: [
461
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
795
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
462
796
  import_salt_lab5.ToggleButton,
463
797
  {
464
798
  "aria-label": "alert",
@@ -466,7 +800,7 @@ var ThemeSwitch = ({
466
800
  "data-icon": "light"
467
801
  }
468
802
  ),
469
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
803
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
470
804
  import_salt_lab5.ToggleButton,
471
805
  {
472
806
  "aria-label": "home",
@@ -480,9 +814,9 @@ var ThemeSwitch = ({
480
814
  };
481
815
 
482
816
  // src/app-header/AppHeader.tsx
483
- var import_classnames4 = __toESM(require("classnames"));
484
- var import_jsx_runtime11 = require("react/jsx-runtime");
485
- var classBase4 = "vuuAppHeader";
817
+ var import_classnames5 = __toESM(require("classnames"));
818
+ var import_jsx_runtime12 = require("react/jsx-runtime");
819
+ var classBase5 = "vuuAppHeader";
486
820
  var AppHeader = ({
487
821
  className: classNameProp,
488
822
  layoutId,
@@ -493,14 +827,14 @@ var AppHeader = ({
493
827
  user,
494
828
  ...htmlAttributes
495
829
  }) => {
496
- const className = (0, import_classnames4.default)(classBase4, classNameProp);
497
- const handleSwitchTheme = (0, import_react10.useCallback)(
830
+ const className = (0, import_classnames5.default)(classBase5, classNameProp);
831
+ const handleSwitchTheme = (0, import_react11.useCallback)(
498
832
  (mode) => onSwitchTheme == null ? void 0 : onSwitchTheme(mode),
499
833
  [onSwitchTheme]
500
834
  );
501
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("header", { className, ...htmlAttributes, children: [
502
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ThemeSwitch, { defaultMode: themeMode, onChange: handleSwitchTheme }),
503
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
835
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("header", { className, ...htmlAttributes, children: [
836
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ThemeSwitch, { defaultMode: themeMode, onChange: handleSwitchTheme }),
837
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
504
838
  UserProfile,
505
839
  {
506
840
  layoutId,
@@ -513,7 +847,9 @@ var AppHeader = ({
513
847
  };
514
848
 
515
849
  // src/shell.tsx
516
- var import_jsx_runtime12 = require("react/jsx-runtime");
850
+ var import_vuu_utils4 = require("@vuu-ui/vuu-utils");
851
+ var import_jsx_runtime13 = require("react/jsx-runtime");
852
+ var { error } = (0, import_vuu_utils4.logger)("Shell");
517
853
  var warningLayout = {
518
854
  type: "View",
519
855
  props: {
@@ -534,25 +870,32 @@ var Shell = ({
534
870
  defaultLayout = warningLayout,
535
871
  leftSidePanel,
536
872
  loginUrl,
873
+ saveLocation = "remote",
874
+ saveUrl,
537
875
  serverUrl,
538
876
  user,
539
877
  ...htmlAttributes
540
878
  }) => {
541
- const rootRef = (0, import_react11.useRef)(null);
542
- const paletteView = (0, import_react11.useRef)(null);
543
- const [open, setOpen] = (0, import_react11.useState)(false);
544
- const layoutId = (0, import_react11.useRef)("latest");
545
- const [layout, setLayoutConfig, loadLayoutById] = use_layout_config_default(
546
- user,
547
- defaultLayout
548
- );
549
- const handleLayoutChange = (0, import_react11.useCallback)(
879
+ const rootRef = (0, import_react12.useRef)(null);
880
+ const paletteView = (0, import_react12.useRef)(null);
881
+ const [open, setOpen] = (0, import_react12.useState)(false);
882
+ const layoutId = (0, import_react12.useRef)("latest");
883
+ const [layout, saveLayoutConfig, loadLayoutById] = useLayoutConfig({
884
+ defaultLayout,
885
+ saveLocation,
886
+ user
887
+ });
888
+ const handleLayoutChange = (0, import_react12.useCallback)(
550
889
  (layout2) => {
551
- setLayoutConfig(layout2);
890
+ try {
891
+ saveLayoutConfig(layout2);
892
+ } catch {
893
+ error == null ? void 0 : error("Failed to save layout");
894
+ }
552
895
  },
553
- [setLayoutConfig]
896
+ [saveLayoutConfig]
554
897
  );
555
- const handleSwitchTheme = (0, import_react11.useCallback)((mode) => {
898
+ const handleSwitchTheme = (0, import_react12.useCallback)((mode) => {
556
899
  if (rootRef.current) {
557
900
  rootRef.current.dataset.mode = mode;
558
901
  }
@@ -564,16 +907,16 @@ var Shell = ({
564
907
  setOpen(!open);
565
908
  }
566
909
  };
567
- const handleNavigate = (0, import_react11.useCallback)(
910
+ const handleNavigate = (0, import_react12.useCallback)(
568
911
  (id) => {
569
912
  layoutId.current = id;
570
913
  loadLayoutById(id);
571
914
  },
572
915
  [loadLayoutById]
573
916
  );
574
- (0, import_react11.useEffect)(() => {
917
+ (0, import_react12.useEffect)(() => {
575
918
  if (serverUrl && user.token) {
576
- (0, import_vuu_data.connectToServer)({
919
+ (0, import_vuu_data2.connectToServer)({
577
920
  authToken: user.token,
578
921
  url: serverUrl,
579
922
  username: user.username
@@ -584,7 +927,7 @@ var Shell = ({
584
927
  const drawers = [];
585
928
  if (leftSidePanel) {
586
929
  drawers.push(
587
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
930
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
588
931
  import_vuu_layout2.Drawer,
589
932
  {
590
933
  onClick: handleDrawerClick,
@@ -594,7 +937,7 @@ var Shell = ({
594
937
  peekaboo: true,
595
938
  sizeOpen: 200,
596
939
  toggleButton: "end",
597
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
940
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
598
941
  import_vuu_layout2.View,
599
942
  {
600
943
  className: "vuuShell-palette",
@@ -612,7 +955,7 @@ var Shell = ({
612
955
  }
613
956
  return drawers;
614
957
  };
615
- const className = (0, import_classnames5.default)(
958
+ const className = (0, import_classnames6.default)(
616
959
  "vuuShell",
617
960
  classNameProp,
618
961
  "salt-theme",
@@ -620,21 +963,21 @@ var Shell = ({
620
963
  );
621
964
  return (
622
965
  // ShellContext TBD
623
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(ShellContextProvider, { value: void 0, children: [
624
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_vuu_layout2.LayoutProvider, { layout, onLayoutChange: handleLayoutChange, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
966
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(ShellContextProvider, { value: void 0, children: [
967
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_vuu_layout2.LayoutProvider, { layout, onLayoutChange: handleLayoutChange, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
625
968
  import_vuu_layout2.DraggableLayout,
626
969
  {
627
970
  className,
628
971
  "data-mode": "light",
629
972
  ref: rootRef,
630
973
  ...htmlAttributes,
631
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
974
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
632
975
  import_vuu_layout2.Flexbox,
633
976
  {
634
977
  className: "App",
635
978
  style: { flexDirection: "column", height: "100%", width: "100%" },
636
979
  children: [
637
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
980
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
638
981
  AppHeader,
639
982
  {
640
983
  layoutId: layoutId.current,
@@ -644,8 +987,8 @@ var Shell = ({
644
987
  onSwitchTheme: handleSwitchTheme
645
988
  }
646
989
  ),
647
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_vuu_layout2.DockLayout, { style: { flex: 1 }, children: getDrawers().concat(
648
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
990
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_vuu_layout2.DockLayout, { style: { flex: 1 }, children: getDrawers().concat(
991
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
649
992
  import_vuu_layout2.DraggableLayout,
650
993
  {
651
994
  dropTarget: true,
@@ -665,22 +1008,22 @@ var Shell = ({
665
1008
  };
666
1009
 
667
1010
  // src/theme-provider/ThemeProvider.tsx
668
- var import_react12 = require("react");
669
- var import_classnames6 = __toESM(require("classnames"));
670
- var import_jsx_runtime13 = require("react/jsx-runtime");
1011
+ var import_react13 = require("react");
1012
+ var import_classnames7 = __toESM(require("classnames"));
1013
+ var import_jsx_runtime14 = require("react/jsx-runtime");
671
1014
  var DEFAULT_DENSITY2 = "medium";
672
1015
  var DEFAULT_THEME = "salt-theme";
673
1016
  var DEFAULT_THEME_MODE = "light";
674
- var ThemeContext = (0, import_react12.createContext)({
1017
+ var ThemeContext = (0, import_react13.createContext)({
675
1018
  density: "high",
676
1019
  theme: "salt-theme",
677
1020
  themeMode: "light"
678
1021
  });
679
1022
  var createThemedChildren = (children, theme, themeMode, density) => {
680
1023
  var _a;
681
- if ((0, import_react12.isValidElement)(children)) {
682
- return (0, import_react12.cloneElement)(children, {
683
- className: (0, import_classnames6.default)(
1024
+ if ((0, import_react13.isValidElement)(children)) {
1025
+ return (0, import_react13.cloneElement)(children, {
1026
+ className: (0, import_classnames7.default)(
684
1027
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
685
1028
  (_a = children.props) == null ? void 0 : _a.className,
686
1029
  theme,
@@ -710,7 +1053,7 @@ var ThemeProvider = ({
710
1053
  density: inheritedDensity,
711
1054
  themeMode: inheritedThemeMode,
712
1055
  theme: inheritedTheme
713
- } = (0, import_react12.useContext)(ThemeContext);
1056
+ } = (0, import_react13.useContext)(ThemeContext);
714
1057
  const density = (_a = densityProp != null ? densityProp : inheritedDensity) != null ? _a : DEFAULT_DENSITY2;
715
1058
  const themeMode = (_b = themeModeProp != null ? themeModeProp : inheritedThemeMode) != null ? _b : DEFAULT_THEME_MODE;
716
1059
  const theme = (_c = themeProp != null ? themeProp : inheritedTheme) != null ? _c : DEFAULT_THEME;
@@ -720,7 +1063,7 @@ var ThemeProvider = ({
720
1063
  themeMode,
721
1064
  density
722
1065
  );
723
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ThemeContext.Provider, { value: { themeMode, density, theme }, children: themedChildren });
1066
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ThemeContext.Provider, { value: { themeMode, density, theme }, children: themedChildren });
724
1067
  };
725
1068
  ThemeProvider.displayName = "ThemeProvider";
726
1069
  //# sourceMappingURL=index.js.map