@lavalogic/scoria 0.37.55 → 0.38.0
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/dist/Components/Table/ARCHITECTURE.md +22 -22
- package/dist/Components/Table/Misc/ColumnPanel.svelte +1 -61
- package/dist/Components/Table/Misc/ColumnPanel.svelte.d.ts +2 -24
- package/dist/Components/Table/Misc/ColumnPanelModal.svelte +3 -92
- package/dist/Components/Table/Misc/ColumnPanelModal.svelte.d.ts +2 -24
- package/dist/Components/Table/Misc/ColumnPanelModalProps.d.ts +2 -2
- package/dist/Components/Table/Misc/TableConfigurationModal.svelte +644 -0
- package/dist/Components/Table/Misc/TableConfigurationModal.svelte.d.ts +26 -0
- package/dist/Components/Table/Misc/TableConfigurationModalProps.d.ts +13 -0
- package/dist/Components/Table/Misc/TableHorizontalBar.svelte +27 -0
- package/dist/Components/Table/Misc/TableSidebar.svelte +45 -2
- package/dist/Components/Table/Misc/TableViewDropdown.svelte +175 -0
- package/dist/Components/Table/Misc/TableViewDropdown.svelte.d.ts +25 -0
- package/dist/Components/Table/SubApis.svelte.js +0 -41
- package/dist/Components/Table/Types/Columns/JSONTableLayout.d.ts +5 -5
- package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.d.ts +5 -5
- package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.js +5 -5
- package/dist/Components/Table/Types/Context/PreferencesState.svelte.d.ts +106 -147
- package/dist/Components/Table/Types/Context/PreferencesState.svelte.js +156 -203
- package/dist/Components/Table/Types/Context/TableContext.svelte.d.ts +323 -44
- package/dist/Components/Table/Types/Context/TableContext.svelte.js +687 -187
- package/dist/Components/Table/Types/Context/TableInitOptions.d.ts +9 -0
- package/dist/Components/Table/Types/Context/ViewState.svelte.d.ts +152 -0
- package/dist/Components/Table/Types/Context/ViewState.svelte.js +223 -0
- package/dist/Components/Table/Types/ExpandedPanel.d.ts +9 -5
- package/dist/Components/Table/Types/ExpandedPanel.js +9 -5
- package/dist/Components/Table/Types/Persistence/DatatableView.d.ts +95 -0
- package/dist/Components/Table/Types/Persistence/DatatableViewEnvelope.d.ts +21 -0
- package/dist/Components/Table/Types/Persistence/DatatableViewEnvelope.js +1 -0
- package/dist/Components/Table/Types/Persistence/DatatableViewKind.d.ts +10 -0
- package/dist/Components/Table/Types/Persistence/DatatableViewKind.js +1 -0
- package/dist/Components/Table/Types/Persistence/JSONActiveView.d.ts +58 -0
- package/dist/Components/Table/Types/Persistence/JSONActiveView.js +41 -0
- package/dist/Components/Table/Types/Persistence/JSONTableFilter.d.ts +53 -0
- package/dist/Components/Table/Types/Persistence/JSONTableFilter.js +42 -0
- package/dist/Components/Table/Types/Persistence/RemoteTableLayoutAdapter.d.ts +47 -0
- package/dist/Components/Table/Types/Persistence/RemoteTableLayoutAdapter.js +1 -0
- package/dist/Components/Table/Types/Public/CreateTableOptions.d.ts +10 -0
- package/dist/Components/Table/Types/Public/TablePersistence.d.ts +1 -4
- package/dist/Components/Table/Types/Public/TableSubApis.d.ts +0 -18
- package/dist/Components/Table/Types/Public/index.d.ts +1 -1
- package/dist/Components/Table/createTable.svelte.js +2 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +9 -0
- package/package.json +1 -1
- package/dist/Components/Table/Types/Columns/Definitions/ColumnDefSet.d.ts +0 -20
- package/dist/Components/Table/Types/Columns/Definitions/JSONColumnDefSet.d.ts +0 -25
- /package/dist/Components/Table/{Types/Columns/Definitions/ColumnDefSet.js → Misc/TableConfigurationModalProps.js} +0 -0
- /package/dist/Components/Table/Types/{Columns/Definitions/JSONColumnDefSet.js → Persistence/DatatableView.js} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CellCoordinates } from '../Coordinates/CellCoordinates.js';
|
|
2
|
+
import type { RemoteTableLayoutAdapter } from '../Persistence/RemoteTableLayoutAdapter.js';
|
|
2
3
|
import type { ToolbarPosition } from '../Toolbar/ToolbarPosition.js';
|
|
3
4
|
/** Allowable primary-key types for the table's `selectionExtractor`. */
|
|
4
5
|
export type Primitive = string | number;
|
|
@@ -29,5 +30,13 @@ export interface TableInitOptions<T extends object, Identifier extends Primitive
|
|
|
29
30
|
displayFooter?: boolean;
|
|
30
31
|
onEditCell?: (item: T, coords: CellCoordinates, value: unknown) => void | Promise<void>;
|
|
31
32
|
getUserId: () => string | undefined;
|
|
33
|
+
/** Stable per-table identifier supplied by the host app. Identifies
|
|
34
|
+
* which datatable saved views belong to. Omit to disable remote views
|
|
35
|
+
* (the table then behaves exactly as before). */
|
|
36
|
+
datatableUuid?: string;
|
|
37
|
+
/** Injected backend adapter for saved layouts/filters. Omit to disable
|
|
38
|
+
* remote views. scoria stays backend-agnostic; the host app supplies
|
|
39
|
+
* this. */
|
|
40
|
+
remoteLayouts?: RemoteTableLayoutAdapter;
|
|
32
41
|
debug?: boolean;
|
|
33
42
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { DatatableView } from '../Persistence/DatatableView.js';
|
|
2
|
+
import type { DatatableViewKind } from '../Persistence/DatatableViewKind.js';
|
|
3
|
+
import type { ActiveViewRef } from '../Persistence/JSONActiveView.js';
|
|
4
|
+
/**
|
|
5
|
+
* Sub-context owning the datatable *view selection* state machine for
|
|
6
|
+
* **both** view kinds - column `'layout'` and `'filter'` - as two fully
|
|
7
|
+
* independent slices (see `ViewKindSlice`). For each kind it tracks:
|
|
8
|
+
*
|
|
9
|
+
* - `activeView` - which view the live table belongs to (the code-defined
|
|
10
|
+
* Default, or a named saved `DatatableView`);
|
|
11
|
+
* - `isDirty` - whether the live state has diverged from the active
|
|
12
|
+
* view's saved baseline;
|
|
13
|
+
* - the saved views of that kind fetched through the injected
|
|
14
|
+
* `RemoteTableLayoutAdapter`;
|
|
15
|
+
* - `dropdownLabel` - the `$derived` label the toolbar view-picker
|
|
16
|
+
* renders (`'Default'` / `'Custom (unsaved)'` / `'<name>'` / `'<name>
|
|
17
|
+
* (unsaved)'`).
|
|
18
|
+
*
|
|
19
|
+
* Layouts and filters are *fully independent*: applying or dirtying one
|
|
20
|
+
* kind never touches the other. The two slices share only the
|
|
21
|
+
* `deriveDropdownLabel` formatting logic.
|
|
22
|
+
*
|
|
23
|
+
* It is a deliberately *pure state holder*: it never applies a layout or
|
|
24
|
+
* filter to the live table and never calls the remote adapter. Those side
|
|
25
|
+
* effects live on `TableContext` (which owns `_applyLayout` /
|
|
26
|
+
* `_captureLayout` / `_applyFilter` / `_captureFilter`, the baseline
|
|
27
|
+
* tracking and the adapter wiring), mirroring how `SelectionState` and
|
|
28
|
+
* `PreferencesState` keep their effects on the parent. `TableContext`
|
|
29
|
+
* drives this sub-context through the writer methods below.
|
|
30
|
+
*
|
|
31
|
+
* The sub-context holds no parent reference; it has no dependency thunks
|
|
32
|
+
* because it reads nothing from the parent - the parent pushes state in.
|
|
33
|
+
*
|
|
34
|
+
* ## Public surface
|
|
35
|
+
*
|
|
36
|
+
* The kind-aware methods (`markDirty(kind, ...)`,
|
|
37
|
+
* `selectDefault(kind)`, `selectSavedView(kind, ...)`,
|
|
38
|
+
* `setActiveView(kind, ...)`, `setSavedViews(kind, ...)`) and the
|
|
39
|
+
* per-kind getters (`activeView(kind)`, `isDirty(kind)`,
|
|
40
|
+
* `savedViews(kind)`, `dropdownLabel(kind)`) are the canonical surface.
|
|
41
|
+
* The unsuffixed `*Layout` aliases (`savedLayouts`, plus the
|
|
42
|
+
* single-arg behaviour of the kind methods defaulting to `'layout'`)
|
|
43
|
+
* are retained for the A2 layout call sites.
|
|
44
|
+
*/
|
|
45
|
+
export declare class ViewState {
|
|
46
|
+
/** Layout-kind slice (column order / visibility / pinning / width). */
|
|
47
|
+
private readonly _layout;
|
|
48
|
+
/** Filter-kind slice (per-column filter values and modes). */
|
|
49
|
+
private readonly _filter;
|
|
50
|
+
/**
|
|
51
|
+
* Toolbar label `$derived` for the **layout** kind. Kept as a field
|
|
52
|
+
* (rather than a method) so the existing A2 layout call sites that
|
|
53
|
+
* read `dropdownLabelFor('layout')` and the legacy `layoutDropdownLabel`
|
|
54
|
+
* stay reactive without re-deriving on every read.
|
|
55
|
+
*
|
|
56
|
+
* - `'Default'` - Default active, not dirty;
|
|
57
|
+
* - `'Custom (unsaved)'` - Default active, dirty;
|
|
58
|
+
* - `'<name>'` - a saved view is active and unmodified;
|
|
59
|
+
* - `'<name> (unsaved)'` - a saved view is active but has been edited.
|
|
60
|
+
*/
|
|
61
|
+
readonly layoutDropdownLabel: string;
|
|
62
|
+
/**
|
|
63
|
+
* Toolbar label `$derived` for the **filter** kind. Same four-way
|
|
64
|
+
* matrix as `layoutDropdownLabel`, computed independently from the
|
|
65
|
+
* filter slice.
|
|
66
|
+
*/
|
|
67
|
+
readonly filterDropdownLabel: string;
|
|
68
|
+
/**
|
|
69
|
+
* Shared label-formatting logic for both kinds. The four-way branch is
|
|
70
|
+
* driven purely by the active-view ref and the dirty flag, so it is
|
|
71
|
+
* identical for layouts and filters - hence factored out as a static
|
|
72
|
+
* pure function consumed by both `$derived` labels above.
|
|
73
|
+
*
|
|
74
|
+
* - Default active: `'Default'` (clean) / `'Custom (unsaved)'` (dirty);
|
|
75
|
+
* - saved active: `'<name>'` (clean) / `'<name> (unsaved)'` (dirty).
|
|
76
|
+
*/
|
|
77
|
+
private static deriveDropdownLabel;
|
|
78
|
+
/** Resolve the mutable slice for a `DatatableViewKind`. */
|
|
79
|
+
private slice;
|
|
80
|
+
/**
|
|
81
|
+
* The view the live table currently belongs to for `kind`. Read-only
|
|
82
|
+
* externally; the writers are `selectDefault` / `selectSavedView` /
|
|
83
|
+
* `setActiveView`.
|
|
84
|
+
*/
|
|
85
|
+
activeView(kind: DatatableViewKind): ActiveViewRef;
|
|
86
|
+
/**
|
|
87
|
+
* Whether the live state of `kind` has diverged from the active
|
|
88
|
+
* view's saved baseline. Read-only externally; the writer is
|
|
89
|
+
* `markDirty`.
|
|
90
|
+
*/
|
|
91
|
+
isDirty(kind: DatatableViewKind): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* The saved views of `kind` available in the view-picker. Read-only
|
|
94
|
+
* externally; the writer is `setSavedViews`.
|
|
95
|
+
*/
|
|
96
|
+
savedViews(kind: DatatableViewKind): ReadonlyArray<DatatableView>;
|
|
97
|
+
/**
|
|
98
|
+
* The toolbar label for `kind` (see `layoutDropdownLabel` /
|
|
99
|
+
* `filterDropdownLabel`). A thin kind-keyed accessor over the two
|
|
100
|
+
* `$derived` label fields.
|
|
101
|
+
*/
|
|
102
|
+
dropdownLabel(kind: DatatableViewKind): string;
|
|
103
|
+
/**
|
|
104
|
+
* Set the dirty flag for `kind`. Called by the parent's debounced
|
|
105
|
+
* auto-persist `$effect` after comparing the freshly captured layout /
|
|
106
|
+
* filter against the current baseline. Idempotent - reassigning the
|
|
107
|
+
* same value short-circuits to avoid a redundant `$state` write.
|
|
108
|
+
*/
|
|
109
|
+
markDirty(kind: DatatableViewKind, dirty: boolean): void;
|
|
110
|
+
/**
|
|
111
|
+
* Mark the Default view of `kind` as active and clear that kind's
|
|
112
|
+
* dirty flag. The parent calls this after applying the Default
|
|
113
|
+
* baseline to the live table (`resetToDefault`) or when boot
|
|
114
|
+
* hydration finds no persisted saved-view ref for the kind. Only the
|
|
115
|
+
* named kind's slice is touched.
|
|
116
|
+
*/
|
|
117
|
+
selectDefault(kind: DatatableViewKind): void;
|
|
118
|
+
/**
|
|
119
|
+
* Mark a saved view of `kind` as active and clear that kind's dirty
|
|
120
|
+
* flag. The `ref` carries the server-assigned id and the cached
|
|
121
|
+
* display name. The parent calls this after applying the view to the
|
|
122
|
+
* live table (`applyDatatableView` / `saveCurrentAsNewView`) or when
|
|
123
|
+
* boot hydration restores a persisted saved-view ref. Only the named
|
|
124
|
+
* kind's slice is touched.
|
|
125
|
+
*/
|
|
126
|
+
selectSavedView(kind: DatatableViewKind, ref: {
|
|
127
|
+
id: string;
|
|
128
|
+
name: string;
|
|
129
|
+
}): void;
|
|
130
|
+
/**
|
|
131
|
+
* Set the active view of `kind` directly from a persisted
|
|
132
|
+
* `ActiveViewRef`. Used by boot hydration, which reads the
|
|
133
|
+
* `JSONActiveView` envelope and must restore whatever ref it recorded
|
|
134
|
+
* *without* clearing the dirty flag (the persisted `dirty` value is
|
|
135
|
+
* restored separately via `markDirty`). Prefer `selectDefault` /
|
|
136
|
+
* `selectSavedView` for the normal apply flows, which also reset
|
|
137
|
+
* dirty.
|
|
138
|
+
*/
|
|
139
|
+
setActiveView(kind: DatatableViewKind, ref: ActiveViewRef): void;
|
|
140
|
+
/**
|
|
141
|
+
* Replace the saved-views list for `kind` with a defensive copy.
|
|
142
|
+
* Called by the parent when the boot-time `refreshSavedViews` fetch
|
|
143
|
+
* resolves and after each save flow mutates the list. Only the named
|
|
144
|
+
* kind's slice is touched.
|
|
145
|
+
*/
|
|
146
|
+
setSavedViews(kind: DatatableViewKind, views: ReadonlyArray<DatatableView>): void;
|
|
147
|
+
/**
|
|
148
|
+
* The `kind: 'layout'` saved views. A2 compatibility alias for
|
|
149
|
+
* `savedViews('layout')`.
|
|
150
|
+
*/
|
|
151
|
+
get savedLayouts(): ReadonlyArray<DatatableView>;
|
|
152
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-kind slice of the view-selection state machine - the mutable state
|
|
3
|
+
* for *one* `DatatableViewKind` (`'layout'` or `'filter'`):
|
|
4
|
+
*
|
|
5
|
+
* - `activeView` - which view of this kind the live table belongs to (the
|
|
6
|
+
* code-defined Default, or a named saved `DatatableView`);
|
|
7
|
+
* - `isDirty` - whether the live state of this kind has diverged from the
|
|
8
|
+
* active view's saved baseline;
|
|
9
|
+
* - `savedViews` - the saved views of this kind fetched through the
|
|
10
|
+
* injected `RemoteTableLayoutAdapter`.
|
|
11
|
+
*
|
|
12
|
+
* `ViewState` holds one `ViewKindSlice` per kind so the two are *fully
|
|
13
|
+
* independent*: dirtying or applying a layout view never touches the
|
|
14
|
+
* filter slice and vice versa. The `$state` fields live here (rather than
|
|
15
|
+
* on `ViewState`) so each slice is an independent reactive unit.
|
|
16
|
+
*/
|
|
17
|
+
class ViewKindSlice {
|
|
18
|
+
/**
|
|
19
|
+
* The view of this kind the live table currently belongs to. Defaults
|
|
20
|
+
* to the code-defined Default. Written by the parent through
|
|
21
|
+
* `selectDefault` / `selectSavedView` when a view is applied (or
|
|
22
|
+
* restored at boot).
|
|
23
|
+
*/
|
|
24
|
+
activeView = $state({ kind: 'default' });
|
|
25
|
+
/**
|
|
26
|
+
* `true` when the live state of this kind has diverged from the active
|
|
27
|
+
* view's saved baseline. Written by the parent's debounced
|
|
28
|
+
* auto-persist `$effect` through `ViewState.markDirty`.
|
|
29
|
+
*/
|
|
30
|
+
isDirty = $state(false);
|
|
31
|
+
/**
|
|
32
|
+
* The saved views of this kind fetched through the injected
|
|
33
|
+
* `RemoteTableLayoutAdapter`. Empty when remote saved views are
|
|
34
|
+
* disabled or the fetch has not completed.
|
|
35
|
+
*/
|
|
36
|
+
savedViews = $state([]);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Sub-context owning the datatable *view selection* state machine for
|
|
40
|
+
* **both** view kinds - column `'layout'` and `'filter'` - as two fully
|
|
41
|
+
* independent slices (see `ViewKindSlice`). For each kind it tracks:
|
|
42
|
+
*
|
|
43
|
+
* - `activeView` - which view the live table belongs to (the code-defined
|
|
44
|
+
* Default, or a named saved `DatatableView`);
|
|
45
|
+
* - `isDirty` - whether the live state has diverged from the active
|
|
46
|
+
* view's saved baseline;
|
|
47
|
+
* - the saved views of that kind fetched through the injected
|
|
48
|
+
* `RemoteTableLayoutAdapter`;
|
|
49
|
+
* - `dropdownLabel` - the `$derived` label the toolbar view-picker
|
|
50
|
+
* renders (`'Default'` / `'Custom (unsaved)'` / `'<name>'` / `'<name>
|
|
51
|
+
* (unsaved)'`).
|
|
52
|
+
*
|
|
53
|
+
* Layouts and filters are *fully independent*: applying or dirtying one
|
|
54
|
+
* kind never touches the other. The two slices share only the
|
|
55
|
+
* `deriveDropdownLabel` formatting logic.
|
|
56
|
+
*
|
|
57
|
+
* It is a deliberately *pure state holder*: it never applies a layout or
|
|
58
|
+
* filter to the live table and never calls the remote adapter. Those side
|
|
59
|
+
* effects live on `TableContext` (which owns `_applyLayout` /
|
|
60
|
+
* `_captureLayout` / `_applyFilter` / `_captureFilter`, the baseline
|
|
61
|
+
* tracking and the adapter wiring), mirroring how `SelectionState` and
|
|
62
|
+
* `PreferencesState` keep their effects on the parent. `TableContext`
|
|
63
|
+
* drives this sub-context through the writer methods below.
|
|
64
|
+
*
|
|
65
|
+
* The sub-context holds no parent reference; it has no dependency thunks
|
|
66
|
+
* because it reads nothing from the parent - the parent pushes state in.
|
|
67
|
+
*
|
|
68
|
+
* ## Public surface
|
|
69
|
+
*
|
|
70
|
+
* The kind-aware methods (`markDirty(kind, ...)`,
|
|
71
|
+
* `selectDefault(kind)`, `selectSavedView(kind, ...)`,
|
|
72
|
+
* `setActiveView(kind, ...)`, `setSavedViews(kind, ...)`) and the
|
|
73
|
+
* per-kind getters (`activeView(kind)`, `isDirty(kind)`,
|
|
74
|
+
* `savedViews(kind)`, `dropdownLabel(kind)`) are the canonical surface.
|
|
75
|
+
* The unsuffixed `*Layout` aliases (`savedLayouts`, plus the
|
|
76
|
+
* single-arg behaviour of the kind methods defaulting to `'layout'`)
|
|
77
|
+
* are retained for the A2 layout call sites.
|
|
78
|
+
*/
|
|
79
|
+
export class ViewState {
|
|
80
|
+
/** Layout-kind slice (column order / visibility / pinning / width). */
|
|
81
|
+
_layout = new ViewKindSlice();
|
|
82
|
+
/** Filter-kind slice (per-column filter values and modes). */
|
|
83
|
+
_filter = new ViewKindSlice();
|
|
84
|
+
/**
|
|
85
|
+
* Toolbar label `$derived` for the **layout** kind. Kept as a field
|
|
86
|
+
* (rather than a method) so the existing A2 layout call sites that
|
|
87
|
+
* read `dropdownLabelFor('layout')` and the legacy `layoutDropdownLabel`
|
|
88
|
+
* stay reactive without re-deriving on every read.
|
|
89
|
+
*
|
|
90
|
+
* - `'Default'` - Default active, not dirty;
|
|
91
|
+
* - `'Custom (unsaved)'` - Default active, dirty;
|
|
92
|
+
* - `'<name>'` - a saved view is active and unmodified;
|
|
93
|
+
* - `'<name> (unsaved)'` - a saved view is active but has been edited.
|
|
94
|
+
*/
|
|
95
|
+
layoutDropdownLabel = $derived.by(() => ViewState.deriveDropdownLabel(this._layout.activeView, this._layout.isDirty));
|
|
96
|
+
/**
|
|
97
|
+
* Toolbar label `$derived` for the **filter** kind. Same four-way
|
|
98
|
+
* matrix as `layoutDropdownLabel`, computed independently from the
|
|
99
|
+
* filter slice.
|
|
100
|
+
*/
|
|
101
|
+
filterDropdownLabel = $derived.by(() => ViewState.deriveDropdownLabel(this._filter.activeView, this._filter.isDirty));
|
|
102
|
+
/**
|
|
103
|
+
* Shared label-formatting logic for both kinds. The four-way branch is
|
|
104
|
+
* driven purely by the active-view ref and the dirty flag, so it is
|
|
105
|
+
* identical for layouts and filters - hence factored out as a static
|
|
106
|
+
* pure function consumed by both `$derived` labels above.
|
|
107
|
+
*
|
|
108
|
+
* - Default active: `'Default'` (clean) / `'Custom (unsaved)'` (dirty);
|
|
109
|
+
* - saved active: `'<name>'` (clean) / `'<name> (unsaved)'` (dirty).
|
|
110
|
+
*/
|
|
111
|
+
static deriveDropdownLabel(active, dirty) {
|
|
112
|
+
if (active.kind === 'default') {
|
|
113
|
+
return dirty ? 'Custom (unsaved)' : 'Default';
|
|
114
|
+
}
|
|
115
|
+
return dirty ? `${active.name} (unsaved)` : active.name;
|
|
116
|
+
}
|
|
117
|
+
/** Resolve the mutable slice for a `DatatableViewKind`. */
|
|
118
|
+
slice(kind) {
|
|
119
|
+
return kind === 'filter' ? this._filter : this._layout;
|
|
120
|
+
}
|
|
121
|
+
// #region kind-aware getters
|
|
122
|
+
/**
|
|
123
|
+
* The view the live table currently belongs to for `kind`. Read-only
|
|
124
|
+
* externally; the writers are `selectDefault` / `selectSavedView` /
|
|
125
|
+
* `setActiveView`.
|
|
126
|
+
*/
|
|
127
|
+
activeView(kind) {
|
|
128
|
+
return this.slice(kind).activeView;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Whether the live state of `kind` has diverged from the active
|
|
132
|
+
* view's saved baseline. Read-only externally; the writer is
|
|
133
|
+
* `markDirty`.
|
|
134
|
+
*/
|
|
135
|
+
isDirty(kind) {
|
|
136
|
+
return this.slice(kind).isDirty;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The saved views of `kind` available in the view-picker. Read-only
|
|
140
|
+
* externally; the writer is `setSavedViews`.
|
|
141
|
+
*/
|
|
142
|
+
savedViews(kind) {
|
|
143
|
+
return this.slice(kind).savedViews;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The toolbar label for `kind` (see `layoutDropdownLabel` /
|
|
147
|
+
* `filterDropdownLabel`). A thin kind-keyed accessor over the two
|
|
148
|
+
* `$derived` label fields.
|
|
149
|
+
*/
|
|
150
|
+
dropdownLabel(kind) {
|
|
151
|
+
return kind === 'filter' ? this.filterDropdownLabel : this.layoutDropdownLabel;
|
|
152
|
+
}
|
|
153
|
+
// #endregion
|
|
154
|
+
// #region kind-aware writers
|
|
155
|
+
/**
|
|
156
|
+
* Set the dirty flag for `kind`. Called by the parent's debounced
|
|
157
|
+
* auto-persist `$effect` after comparing the freshly captured layout /
|
|
158
|
+
* filter against the current baseline. Idempotent - reassigning the
|
|
159
|
+
* same value short-circuits to avoid a redundant `$state` write.
|
|
160
|
+
*/
|
|
161
|
+
markDirty(kind, dirty) {
|
|
162
|
+
const slice = this.slice(kind);
|
|
163
|
+
if (dirty === slice.isDirty) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
slice.isDirty = dirty;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Mark the Default view of `kind` as active and clear that kind's
|
|
170
|
+
* dirty flag. The parent calls this after applying the Default
|
|
171
|
+
* baseline to the live table (`resetToDefault`) or when boot
|
|
172
|
+
* hydration finds no persisted saved-view ref for the kind. Only the
|
|
173
|
+
* named kind's slice is touched.
|
|
174
|
+
*/
|
|
175
|
+
selectDefault(kind) {
|
|
176
|
+
const slice = this.slice(kind);
|
|
177
|
+
slice.activeView = { kind: 'default' };
|
|
178
|
+
slice.isDirty = false;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Mark a saved view of `kind` as active and clear that kind's dirty
|
|
182
|
+
* flag. The `ref` carries the server-assigned id and the cached
|
|
183
|
+
* display name. The parent calls this after applying the view to the
|
|
184
|
+
* live table (`applyDatatableView` / `saveCurrentAsNewView`) or when
|
|
185
|
+
* boot hydration restores a persisted saved-view ref. Only the named
|
|
186
|
+
* kind's slice is touched.
|
|
187
|
+
*/
|
|
188
|
+
selectSavedView(kind, ref) {
|
|
189
|
+
const slice = this.slice(kind);
|
|
190
|
+
slice.activeView = { kind: 'saved', id: ref.id, name: ref.name };
|
|
191
|
+
slice.isDirty = false;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Set the active view of `kind` directly from a persisted
|
|
195
|
+
* `ActiveViewRef`. Used by boot hydration, which reads the
|
|
196
|
+
* `JSONActiveView` envelope and must restore whatever ref it recorded
|
|
197
|
+
* *without* clearing the dirty flag (the persisted `dirty` value is
|
|
198
|
+
* restored separately via `markDirty`). Prefer `selectDefault` /
|
|
199
|
+
* `selectSavedView` for the normal apply flows, which also reset
|
|
200
|
+
* dirty.
|
|
201
|
+
*/
|
|
202
|
+
setActiveView(kind, ref) {
|
|
203
|
+
this.slice(kind).activeView = ref;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Replace the saved-views list for `kind` with a defensive copy.
|
|
207
|
+
* Called by the parent when the boot-time `refreshSavedViews` fetch
|
|
208
|
+
* resolves and after each save flow mutates the list. Only the named
|
|
209
|
+
* kind's slice is touched.
|
|
210
|
+
*/
|
|
211
|
+
setSavedViews(kind, views) {
|
|
212
|
+
this.slice(kind).savedViews = [...views];
|
|
213
|
+
}
|
|
214
|
+
// #endregion
|
|
215
|
+
// #region layout-kind aliases (A2 call-site compatibility)
|
|
216
|
+
/**
|
|
217
|
+
* The `kind: 'layout'` saved views. A2 compatibility alias for
|
|
218
|
+
* `savedViews('layout')`.
|
|
219
|
+
*/
|
|
220
|
+
get savedLayouts() {
|
|
221
|
+
return this._layout.savedViews;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Discriminator on the currently-visible toolbar sidebar panel:
|
|
3
3
|
*
|
|
4
|
-
* - `None`
|
|
5
|
-
* `TableSidebar.svelte`; `None` is kept for symmetry but
|
|
6
|
-
*
|
|
7
|
-
* - `
|
|
8
|
-
* - `
|
|
4
|
+
* - `None` - no panel open (currently the `null` state is
|
|
5
|
+
* preferred in `TableSidebar.svelte`; `None` is kept for symmetry but
|
|
6
|
+
* unused).
|
|
7
|
+
* - `Column` - column-picker panel.
|
|
8
|
+
* - `Filter` - advanced filter panel.
|
|
9
|
+
* - `Toolbox` - settings / toolbox panel.
|
|
10
|
+
* - `Configuration` - FPM 403 "Table Configuration" modal: saved
|
|
11
|
+
* datatable layouts and filters (the new view system).
|
|
9
12
|
*/
|
|
10
13
|
export declare const ExpandedPanel: {
|
|
11
14
|
readonly None: "None";
|
|
12
15
|
readonly Column: "Column";
|
|
13
16
|
readonly Filter: "Filter";
|
|
14
17
|
readonly Toolbox: "Toolbox";
|
|
18
|
+
readonly Configuration: "Configuration";
|
|
15
19
|
};
|
|
16
20
|
export type ExpandedPanel = (typeof ExpandedPanel)[keyof typeof ExpandedPanel];
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Discriminator on the currently-visible toolbar sidebar panel:
|
|
3
3
|
*
|
|
4
|
-
* - `None`
|
|
5
|
-
* `TableSidebar.svelte`; `None` is kept for symmetry but
|
|
6
|
-
*
|
|
7
|
-
* - `
|
|
8
|
-
* - `
|
|
4
|
+
* - `None` - no panel open (currently the `null` state is
|
|
5
|
+
* preferred in `TableSidebar.svelte`; `None` is kept for symmetry but
|
|
6
|
+
* unused).
|
|
7
|
+
* - `Column` - column-picker panel.
|
|
8
|
+
* - `Filter` - advanced filter panel.
|
|
9
|
+
* - `Toolbox` - settings / toolbox panel.
|
|
10
|
+
* - `Configuration` - FPM 403 "Table Configuration" modal: saved
|
|
11
|
+
* datatable layouts and filters (the new view system).
|
|
9
12
|
*/
|
|
10
13
|
export const ExpandedPanel = {
|
|
11
14
|
None: 'None',
|
|
12
15
|
Column: 'Column',
|
|
13
16
|
Filter: 'Filter',
|
|
14
17
|
Toolbox: 'Toolbox',
|
|
18
|
+
Configuration: 'Configuration',
|
|
15
19
|
};
|
|
16
20
|
Object.freeze(ExpandedPanel);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { DatatableViewBody } from './DatatableViewEnvelope.js';
|
|
2
|
+
import type { DatatableViewKind } from './DatatableViewKind.js';
|
|
3
|
+
/**
|
|
4
|
+
* A saved datatable view - a named, persisted column layout or filter
|
|
5
|
+
* that round-trips through a `RemoteTableLayoutAdapter`. This is the
|
|
6
|
+
* frozen DTO contract the consuming host app codes against; scoria
|
|
7
|
+
* itself stays backend-agnostic and never constructs the persistence.
|
|
8
|
+
*
|
|
9
|
+
* Ownership and sharing in v1:
|
|
10
|
+
* - The UI always creates exactly one owner: the user who creates the
|
|
11
|
+
* view (`ownerUserId`). scoria never interprets that identifier - it
|
|
12
|
+
* is an opaque token supplied and understood only by the host app.
|
|
13
|
+
* - The backend's `editor` permission and `systemOwned` flag are
|
|
14
|
+
* reserved no-ops in v1: scoria never sets them and never relies on
|
|
15
|
+
* them. Sharing is subscriber-only (see `DatatableViewSubscription`).
|
|
16
|
+
*/
|
|
17
|
+
export interface DatatableView {
|
|
18
|
+
/** Server-assigned, opaque record id. Set by the backend on create;
|
|
19
|
+
* scoria treats it as a meaningless handle for update/delete. */
|
|
20
|
+
id: string;
|
|
21
|
+
/** Stable identifier of the datatable this view belongs to (the
|
|
22
|
+
* `datatableUuid` constant supplied by the host app). */
|
|
23
|
+
datatableUuid: string;
|
|
24
|
+
/** Whether this view holds a column layout or a filter. Matches the
|
|
25
|
+
* `kind` discriminant of `body`. */
|
|
26
|
+
kind: DatatableViewKind;
|
|
27
|
+
/** Human-readable name shown in the saved-views picker. */
|
|
28
|
+
name: string;
|
|
29
|
+
/** The typed payload: a `JSONTableLayout` or a `JSONTableFilter`,
|
|
30
|
+
* discriminated by `kind`. */
|
|
31
|
+
body: DatatableViewBody;
|
|
32
|
+
/** Opaque identifier of the user who owns this view. scoria never
|
|
33
|
+
* interprets it; the UI always sets it to the creating user. */
|
|
34
|
+
ownerUserId: string;
|
|
35
|
+
/**
|
|
36
|
+
* The users this view is currently shared with (v1 sharing is
|
|
37
|
+
* subscriber-only - see `DatatableViewSubscription`). Optional: the
|
|
38
|
+
* backend populates it on a view the *current* user owns so the
|
|
39
|
+
* Table Configuration modal can list and manage subscribers. It is
|
|
40
|
+
* omitted (or empty) on views the current user merely subscribes to
|
|
41
|
+
* - a non-owner has no business seeing the full subscriber roster.
|
|
42
|
+
*
|
|
43
|
+
* Added in A4a (the modal needs to render an owner's subscriber list
|
|
44
|
+
* and an Unsubscribe control). scoria never interprets the entries;
|
|
45
|
+
* `userId` is the same opaque token as `ownerUserId`. `displayName`
|
|
46
|
+
* is an optional human-readable label the host may attach for the
|
|
47
|
+
* UI - scoria falls back to `userId` when it is absent.
|
|
48
|
+
*/
|
|
49
|
+
subscribers?: ReadonlyArray<DatatableViewSubscriber>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One entry in a `DatatableView.subscribers` roster: an opaque user
|
|
53
|
+
* identifier plus an optional human-readable label. scoria never
|
|
54
|
+
* interprets `userId`; `displayName`, when supplied by the host, is
|
|
55
|
+
* shown in the Table Configuration modal in preference to the raw id.
|
|
56
|
+
*/
|
|
57
|
+
export interface DatatableViewSubscriber {
|
|
58
|
+
/** Opaque identifier of the subscribed user (same token space as
|
|
59
|
+
* `DatatableView.ownerUserId`). */
|
|
60
|
+
userId: string;
|
|
61
|
+
/** Optional human-readable label for the subscriber. The modal falls
|
|
62
|
+
* back to `userId` when this is absent. */
|
|
63
|
+
displayName?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Request payload for creating a brand-new `DatatableView`. Identical to
|
|
67
|
+
* `DatatableView` except the server-assigned `id` is omitted - the
|
|
68
|
+
* backend assigns it and returns the complete record.
|
|
69
|
+
*/
|
|
70
|
+
export type CreateDatatableViewRequest = Omit<DatatableView, 'id'>;
|
|
71
|
+
/**
|
|
72
|
+
* Request payload for updating an existing `DatatableView`. `id`
|
|
73
|
+
* identifies the record; `name` and `body` are each optional so a caller
|
|
74
|
+
* can patch either independently (rename without re-sending the body, or
|
|
75
|
+
* replace the body without renaming).
|
|
76
|
+
*/
|
|
77
|
+
export interface UpdateDatatableViewRequest {
|
|
78
|
+
/** Server-assigned id of the view to update. */
|
|
79
|
+
id: string;
|
|
80
|
+
/** New name, if the view is being renamed. */
|
|
81
|
+
name?: string;
|
|
82
|
+
/** New payload, if the layout/filter is being replaced. */
|
|
83
|
+
body?: DatatableViewBody;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A subscriber relationship for a shared `DatatableView`. v1 sharing is
|
|
87
|
+
* subscriber-only: a non-owner is granted read access by being added as
|
|
88
|
+
* a subscriber (there is no `editor` role in v1).
|
|
89
|
+
*/
|
|
90
|
+
export interface DatatableViewSubscription {
|
|
91
|
+
/** Id of the `DatatableView` being shared. */
|
|
92
|
+
viewId: string;
|
|
93
|
+
/** Opaque identifier of the subscribed user. */
|
|
94
|
+
userId: string;
|
|
95
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { JSONTableLayout } from '../Columns/JSONTableLayout.js';
|
|
2
|
+
import type { JSONTableFilter } from './JSONTableFilter.js';
|
|
3
|
+
/**
|
|
4
|
+
* The typed payload body stored inside a saved `DatatableView`, as a
|
|
5
|
+
* discriminated union keyed on `kind`:
|
|
6
|
+
*
|
|
7
|
+
* - `{ kind: 'layout' }` carries a `JSONTableLayout` (column order,
|
|
8
|
+
* visibility, pinning, width).
|
|
9
|
+
* - `{ kind: 'filter' }` carries a `JSONTableFilter` (per-column filter
|
|
10
|
+
* values and modes).
|
|
11
|
+
*
|
|
12
|
+
* The `kind` tag matches the `DatatableView.kind` field, so a caller can
|
|
13
|
+
* narrow the body by switching on a single discriminant.
|
|
14
|
+
*/
|
|
15
|
+
export type DatatableViewBody = {
|
|
16
|
+
kind: 'layout';
|
|
17
|
+
layout: JSONTableLayout;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'filter';
|
|
20
|
+
filter: JSONTableFilter;
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discriminant for a saved `DatatableView`: whether the record holds a
|
|
3
|
+
* column layout (`'layout'` - order / visibility / pinning / width) or a
|
|
4
|
+
* filter (`'filter'` - per-column filter values and modes).
|
|
5
|
+
*
|
|
6
|
+
* The same value appears both on the `DatatableView` DTO and as the
|
|
7
|
+
* `kind` tag of the `DatatableViewBody` discriminated union, so a caller
|
|
8
|
+
* can switch on a single field to know which payload shape it holds.
|
|
9
|
+
*/
|
|
10
|
+
export type DatatableViewKind = 'layout' | 'filter';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference to the datatable view currently driving the table - either the
|
|
3
|
+
* code-defined Default layout, or a named saved view round-tripped through
|
|
4
|
+
* a `RemoteTableLayoutAdapter`.
|
|
5
|
+
*
|
|
6
|
+
* Discriminated on `kind`:
|
|
7
|
+
* - `{ kind: 'default' }` - the live table is tracking the factory column
|
|
8
|
+
* defs (no saved view selected);
|
|
9
|
+
* - `{ kind: 'saved', id, name }` - a saved `DatatableView` is selected.
|
|
10
|
+
* `id` is the server-assigned record id; `name` is cached alongside it
|
|
11
|
+
* so the dropdown label can render before the saved-view list has been
|
|
12
|
+
* fetched (boot-time hydration reads this before the network resolves).
|
|
13
|
+
*/
|
|
14
|
+
export type ActiveViewRef = {
|
|
15
|
+
kind: 'default';
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'saved';
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Persisted-to-localStorage record of which datatable view is active and
|
|
23
|
+
* whether the live table has diverged from that view's saved baseline.
|
|
24
|
+
*
|
|
25
|
+
* This is the view-selection counterpart of `JSONTableLayout` (which
|
|
26
|
+
* persists the *contents* of the live layout): `JSONActiveView` only
|
|
27
|
+
* records *which* view the working copy belongs to and the dirty flag, so
|
|
28
|
+
* after a reload the toolbar dropdown can show e.g. `"Sales (unsaved)"`
|
|
29
|
+
* rather than resetting to `"Default"`.
|
|
30
|
+
*
|
|
31
|
+
* `TableContext` writes this (debounced, alongside the working-copy
|
|
32
|
+
* layout) on every layout change and reads it back during boot hydration.
|
|
33
|
+
*/
|
|
34
|
+
export interface JSONActiveView {
|
|
35
|
+
/** Which view the live table belongs to (Default or a saved view). */
|
|
36
|
+
active: ActiveViewRef;
|
|
37
|
+
/** Whether the live layout has diverged from `active`'s saved
|
|
38
|
+
* baseline. Drives the `"(unsaved)"` suffix in the dropdown label. */
|
|
39
|
+
dirty: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Schema version for the persisted `JSONActiveView` envelope. Bump when
|
|
43
|
+
* the shape or field semantics change in a backwards-incompatible way;
|
|
44
|
+
* `readVersionedJSON` silently drops envelopes written under a different
|
|
45
|
+
* version.
|
|
46
|
+
*/
|
|
47
|
+
export declare const TABLE_ACTIVE_VIEW_SCHEMA_VERSION = 1;
|
|
48
|
+
/**
|
|
49
|
+
* Conservative typeguard for a persisted `JSONActiveView`. localStorage is
|
|
50
|
+
* same-origin-writable so the decoded value is untrusted; this checks the
|
|
51
|
+
* envelope shape, the `active` discriminated union, and every field's
|
|
52
|
+
* primitive type.
|
|
53
|
+
*
|
|
54
|
+
* The `id` referenced by a `kind: 'saved'` slot is *not* verified against
|
|
55
|
+
* the live saved-view list here - `TableContext` reconciles the persisted
|
|
56
|
+
* ref against the fetched views once they load.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isJSONActiveView(raw: unknown): raw is JSONActiveView;
|