@lavalogic/scoria 0.38.1 → 0.38.3

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.
@@ -14,7 +14,7 @@
14
14
  import { Size } from '../../../Types/Internal/Size.js';
15
15
  import type { TabOption } from '../../../Types/Internal/TabOption.js';
16
16
  import { Variant } from '../../../Types/Internal/Variant.js';
17
- import { getContext } from 'svelte';
17
+ import { getContext, onMount } from 'svelte';
18
18
  import { TableContext } from '../Types/Context/TableContext.svelte.js';
19
19
  import type { Primitive } from '../Types/Context/TableInitOptions.js';
20
20
  import type { DatatableView } from '../Types/Persistence/DatatableView.js';
@@ -48,6 +48,10 @@
48
48
  // errors inline in its own body.
49
49
  let errorMessage = $state<string | null>(null);
50
50
 
51
+ // True while the open-time fetch of saved views is in flight, so the
52
+ // My-tab shows "Loading…" rather than the "nothing saved yet" message.
53
+ let loadingViews = $state(false);
54
+
51
55
  // Whether remote saved views are available at all. A table created
52
56
  // without `datatableUuid` / `remoteLayouts` opts out of remote views;
53
57
  // the modal then renders a graceful "unavailable" empty state and
@@ -93,6 +97,24 @@
93
97
  }
94
98
  }
95
99
 
100
+ // Fetch the saved views fresh every time the modal opens. The boot-time
101
+ // fetch can miss (it races table construction / auth readiness), so the
102
+ // modal cannot rely on `_viewState.savedViews` already being populated -
103
+ // it pulls `GET /datatable-state` itself on open. A failure surfaces in
104
+ // the inline error banner via `guard` rather than being swallowed.
105
+ onMount(() => {
106
+ if (!remoteAvailable) {
107
+ return;
108
+ }
109
+ loadingViews = true;
110
+ void guard(
111
+ () => tableContext.refreshSavedViews(),
112
+ 'Could not load saved layouts and filters. Please try again.'
113
+ ).finally(() => {
114
+ loadingViews = false;
115
+ });
116
+ });
117
+
96
118
  /** Apply a saved view to the live table. Pure local call - no await. */
97
119
  function applyView(view: DatatableView): void {
98
120
  errorMessage = null;
@@ -111,7 +133,14 @@
111
133
  }, `Could not delete "${view.name}". Please try again.`);
112
134
  }
113
135
 
114
- /** Save the live state of the active section as a brand-new view. */
136
+ /**
137
+ * Save the live state of the active section as a brand-new view.
138
+ * `saveCurrentAsNewView` re-fetches the saved views and makes the new
139
+ * one active; the `My …` list and the panel dropdowns are all driven
140
+ * off the reactive `_viewState.savedViews(section)` so they update
141
+ * without a reopen. On success we also switch to the `My …` tab so
142
+ * the user immediately sees the view they just saved.
143
+ */
115
144
  function saveNewView(): void {
116
145
  const trimmed = newViewName.trim();
117
146
  if (!trimmed) {
@@ -120,6 +149,7 @@
120
149
  void guard(async () => {
121
150
  await tableContext.saveCurrentAsNewView(trimmed, section);
122
151
  newViewName = '';
152
+ selectTab('mine');
123
153
  }, `Could not save the new ${sectionNoun.toLowerCase()}. Please try again.`);
124
154
  }
125
155
 
@@ -293,7 +323,9 @@
293
323
  {/if}
294
324
 
295
325
  {#if args.tab === 'mine'}
296
- {#if myViews.length === 0}
326
+ {#if loadingViews}
327
+ <p class="muted">Loading saved {sectionNounPlural.toLowerCase()}…</p>
328
+ {:else if myViews.length === 0}
297
329
  <p class="muted">
298
330
  You have not saved any {sectionNounPlural.toLowerCase()} yet. Use the "Save a new {sectionNoun}"
299
331
  tab to create one.
@@ -450,10 +482,6 @@
450
482
  }}
451
483
  />
452
484
  <Button
453
- type="submit"
454
- variant={Variant.Primary}
455
- size={Size.MediumSmall}
456
- nowrap
457
485
  disabled={!newViewName.trim()}
458
486
  onclick={saveNewView}>Save Current</Button
459
487
  >
@@ -537,14 +565,17 @@
537
565
  color: #aa1414;
538
566
  }
539
567
 
540
- /* The body of a single top tab. Scrolls within the fixed modal
541
- frame rather than growing the dialog. */
568
+ /* The body of a single top tab. A `min-height` floor keeps the modal
569
+ frame visually stable across every section/tab combination (short
570
+ tabs no longer shrink the dialog); the `max-height` + `overflow-y`
571
+ keep taller content scrolling within that fixed frame rather than
572
+ growing the dialog. */
542
573
  .tab-body {
543
574
  display: flex;
544
575
  flex-flow: column nowrap;
545
576
  gap: 0.75rem;
546
577
  padding: 1rem;
547
- min-height: 0;
578
+ min-height: 30vh;
548
579
  max-height: 60vh;
549
580
  overflow-y: auto;
550
581
  }
@@ -892,10 +892,18 @@ export declare class TableContext<T extends object, RowIdType extends Primitive>
892
892
  * `{ kind:'filter', filter: _captureFilter() }`, plus the
893
893
  * host-supplied `datatableUuid` and the owning user;
894
894
  * 2. `await remoteLayouts.createView(...)`;
895
- * 3. push the returned `DatatableView` into the matching kind's
896
- * saved-views list;
897
- * 4. apply it via `applyDatatableView`, which re-points that kind's
898
- * baseline and clears that kind's dirty flag.
895
+ * 3. `await refreshSavedViews()` so the matching kind's saved-views
896
+ * list is re-fetched from the backend - the new view then appears
897
+ * in the Table Configuration modal's My-Layouts / My-Filters list
898
+ * (and the panel quick-switch dropdowns) immediately, without a
899
+ * reopen, because both read reactively off `_viewState`;
900
+ * 4. set the newly-saved view as that kind's active view via
901
+ * `applyDatatableView`, which re-points that kind's baseline and
902
+ * clears that kind's dirty flag - so the quick-switch dropdown
903
+ * shows the new view's name straight away rather than
904
+ * `'Custom (unsaved)'`. The authoritative refreshed copy (matched
905
+ * by id) is preferred over the raw `createView` return so the
906
+ * baseline reflects exactly what the backend persisted.
899
907
  *
900
908
  * Only the named kind's state is touched. No-op (dev-logged via
901
909
  * `devCatch`) when remote saved views are disabled (`datatableUuid` /
@@ -932,8 +940,10 @@ export declare class TableContext<T extends object, RowIdType extends Primitive>
932
940
  * against the now-known body of any persisted saved-view ref.
933
941
  *
934
942
  * No-op when remote saved views are disabled (`datatableUuid` /
935
- * `remoteLayouts` absent). An adapter rejection is routed to
936
- * `devCatch` and never breaks the table.
943
+ * `remoteLayouts` absent). An adapter rejection is propagated to the
944
+ * caller: the boot-time call site catches it with `devCatch`, while
945
+ * the Table Configuration modal routes it to its inline error banner
946
+ * so a failed fetch is visible rather than silently swallowed.
937
947
  */
938
948
  readonly refreshSavedViews: () => Promise<void>;
939
949
  /**
@@ -2309,10 +2309,18 @@ export class TableContext {
2309
2309
  * `{ kind:'filter', filter: _captureFilter() }`, plus the
2310
2310
  * host-supplied `datatableUuid` and the owning user;
2311
2311
  * 2. `await remoteLayouts.createView(...)`;
2312
- * 3. push the returned `DatatableView` into the matching kind's
2313
- * saved-views list;
2314
- * 4. apply it via `applyDatatableView`, which re-points that kind's
2315
- * baseline and clears that kind's dirty flag.
2312
+ * 3. `await refreshSavedViews()` so the matching kind's saved-views
2313
+ * list is re-fetched from the backend - the new view then appears
2314
+ * in the Table Configuration modal's My-Layouts / My-Filters list
2315
+ * (and the panel quick-switch dropdowns) immediately, without a
2316
+ * reopen, because both read reactively off `_viewState`;
2317
+ * 4. set the newly-saved view as that kind's active view via
2318
+ * `applyDatatableView`, which re-points that kind's baseline and
2319
+ * clears that kind's dirty flag - so the quick-switch dropdown
2320
+ * shows the new view's name straight away rather than
2321
+ * `'Custom (unsaved)'`. The authoritative refreshed copy (matched
2322
+ * by id) is preferred over the raw `createView` return so the
2323
+ * baseline reflects exactly what the backend persisted.
2316
2324
  *
2317
2325
  * Only the named kind's state is touched. No-op (dev-logged via
2318
2326
  * `devCatch`) when remote saved views are disabled (`datatableUuid` /
@@ -2342,8 +2350,15 @@ export class TableContext {
2342
2350
  ownerUserId,
2343
2351
  };
2344
2352
  const created = await adapter.createView(request);
2345
- this._viewState.setSavedViews(kind, [...this._viewState.savedViews(kind), created]);
2346
- this.applyDatatableView(created);
2353
+ // Re-fetch the authoritative saved-views lists so the new view
2354
+ // shows up in the modal and the panel dropdowns immediately.
2355
+ await this.refreshSavedViews();
2356
+ // Prefer the refreshed copy (matched by id); fall back to the
2357
+ // raw create return if the refresh did not surface it.
2358
+ const authoritative = this._viewState.savedViews(kind).find((view) => view.id === created.id) ?? created;
2359
+ // Make the just-saved view active so the quick-switch dropdown
2360
+ // shows its name (not "Custom (unsaved)") and dirty is cleared.
2361
+ this.applyDatatableView(authoritative);
2347
2362
  }
2348
2363
  catch (e) {
2349
2364
  devCatch(e);
@@ -2424,8 +2439,10 @@ export class TableContext {
2424
2439
  * against the now-known body of any persisted saved-view ref.
2425
2440
  *
2426
2441
  * No-op when remote saved views are disabled (`datatableUuid` /
2427
- * `remoteLayouts` absent). An adapter rejection is routed to
2428
- * `devCatch` and never breaks the table.
2442
+ * `remoteLayouts` absent). An adapter rejection is propagated to the
2443
+ * caller: the boot-time call site catches it with `devCatch`, while
2444
+ * the Table Configuration modal routes it to its inline error banner
2445
+ * so a failed fetch is visible rather than silently swallowed.
2429
2446
  */
2430
2447
  refreshSavedViews = async () => {
2431
2448
  const adapter = this.remoteLayouts;
@@ -2433,18 +2450,13 @@ export class TableContext {
2433
2450
  if (!adapter || !datatableUuid) {
2434
2451
  return;
2435
2452
  }
2436
- try {
2437
- const views = await adapter.listViews(datatableUuid);
2438
- this._viewState.setSavedViews('layout', views.filter((view) => view.kind === 'layout'));
2439
- this._viewState.setSavedViews('filter', views.filter((view) => view.kind === 'filter'));
2440
- // A persisted saved-view ref restored at boot may have had an
2441
- // unknown body until now; reconcile both baselines against it.
2442
- this._reconcileBaselineForActiveView();
2443
- this._reconcileBaselineForActiveFilterView();
2444
- }
2445
- catch (e) {
2446
- devCatch(e);
2447
- }
2453
+ const views = await adapter.listViews(datatableUuid);
2454
+ this._viewState.setSavedViews('layout', views.filter((view) => view.kind === 'layout'));
2455
+ this._viewState.setSavedViews('filter', views.filter((view) => view.kind === 'filter'));
2456
+ // A persisted saved-view ref restored at boot may have had an
2457
+ // unknown body until now; reconcile both baselines against it.
2458
+ this._reconcileBaselineForActiveView();
2459
+ this._reconcileBaselineForActiveFilterView();
2448
2460
  };
2449
2461
  /**
2450
2462
  * Drop the persisted live-layout snapshot for this table. The next
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lavalogic/scoria",
3
3
  "description": "Svelte components used for the FloWMS Web Frontend",
4
- "version": "0.38.1",
4
+ "version": "0.38.3",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },