@itrocks/list 0.13.6 → 0.17.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.
- package/README.md +62 -17
- package/build.js +12 -9
- package/cjs/columns.js +45 -2
- package/cjs/columns.js.map +1 -1
- package/cjs/list.js +34 -2
- package/cjs/list.js.map +1 -1
- package/cjs/projection.d.ts +7 -0
- package/cjs/projection.js +36 -1
- package/cjs/projection.js.map +1 -1
- package/list.css +10 -7
- package/package.json +1 -1
- package/property-select.d.ts +12 -3
- package/property-select.js +240 -87
package/README.md
CHANGED
|
@@ -185,16 +185,33 @@ the dependency hook. The configured data source remains responsible for storage
|
|
|
185
185
|
`GET /<business-route>/list?column=<path>&rowIds=<comma-separated-identifiers>` uses the HTML action and returns a JSON
|
|
186
186
|
column descriptor with rendered cells keyed by root id. It intersects visible ids with the current filters and mandatory
|
|
187
187
|
scope, makes one projection and performs no count. Invalid selections return 400. Full entity JSON remains available
|
|
188
|
-
through `/list/json`.
|
|
188
|
+
through `/list/json`. Adding `columns=<ordered-paths>` saves the column configuration in the same request.
|
|
189
|
+
Configuration-only requests return 204. The browser serialises both kinds of request to preserve the latest order.
|
|
189
190
|
|
|
190
191
|
The browser entry `@itrocks/list/property-select.js` exports `ListPropertySelect`, with explicit metadata, table
|
|
191
192
|
addition, column persistence and narrow pane ports. The framework mounts it only on compatible lists and disposes it on
|
|
192
|
-
navigation. It reserves pending paths
|
|
193
|
-
and
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
193
|
+
navigation. It reserves pending paths and confirms accepted additions. The checkbox, its cell and the property name
|
|
194
|
+
toggle terminal and relation columns. The checkbox clears immediately before the red removal flash. Reselecting during that flash cancels removal,
|
|
195
|
+
checks the property again and highlights the retained column green, then saves the restored configuration.
|
|
196
|
+
The removal configuration is sent at the initial click, independently of the animation; deleting the cells sends no
|
|
197
|
+
additional request.
|
|
198
|
+
After a completed removal, a later click returns the column immediately right of its former left neighbour, or left of its former right neighbour if the left one is absent. If neither remains,
|
|
199
|
+
it is appended. Neighbours are kept only in this pane instance's memory and cleared on folding, closure or disposal;
|
|
200
|
+
they never enter session state. Drag placement continues to follow the drop position.
|
|
201
|
+
An explicit addition intention always requests addition: an existing column is revealed and highlighted green without removal.
|
|
202
|
+
Property names and checkbox cells toggle the same selection; chevrons and intermediate path segments navigate.
|
|
203
|
+
The consumer checks row identity before insertion. Failed selection leaves the context intact. It loads only currently
|
|
204
|
+
visible row ids through the current feed position; changing the row view while loading rejects the obsolete response with a
|
|
205
|
+
discreet red background. Clicks commit the translated full-path heading and filter locally, before any server response.
|
|
206
|
+
One asynchronous request
|
|
207
|
+
then saves the column order and loads the values; there is no separate admission request. A new drag calls the same
|
|
208
|
+
addition code as a click with `{ effects: false, hidden: true }`, then moves the committed column through the standard
|
|
209
|
+
table reorder operation with `{ adding: true, removal: true }`. Its combined request loads values immediately while
|
|
210
|
+
cells remain hidden, without width animation, flash or automatic scroll. On first pointer entry, the column appears
|
|
211
|
+
with any values already received and uses a green background and plus indicator. An outside drop deletes it through
|
|
212
|
+
the standard header-origin removal and saves once, even if the pointer never entered. Cancelling a new addition also
|
|
213
|
+
removes it. Dropping inside saves only a changed column order; it never reloads values.
|
|
214
|
+
The optional `request(query, signal)` port shares the list persistence queue.
|
|
198
215
|
Existing-column moves and removals only save the column configuration. The header contains each label and its filter
|
|
199
216
|
together, so it uses the table addition plugin's single-header contract.
|
|
200
217
|
|
|
@@ -227,15 +244,13 @@ The browser displays recoverable persistence errors.
|
|
|
227
244
|
|
|
228
245
|
## External column transport
|
|
229
246
|
|
|
230
|
-
The list consumer supplies
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
Admission is independent of the optional values request. A slow or failed values request leaves valid drops accepted;
|
|
235
|
-
failed cells expose `Retry values` without another addition or recent entry.
|
|
247
|
+
The list consumer supplies cells containing a non-breaking space and loads all currently loaded row ids through
|
|
248
|
+
one combined configuration/projection request after the local commit. A failed values request leaves the column
|
|
249
|
+
in place, with a discreet red background on its data cells. The spaces remain; no error text, retry button or automatic
|
|
250
|
+
retry is added. The translated full path comes from the selector's displayed row and available branch metadata.
|
|
236
251
|
|
|
237
|
-
Pending paths and the active drag are reserved before asynchronous work. Used paths
|
|
238
|
-
|
|
252
|
+
Pending paths and the active drag are reserved before asynchronous work. Used paths include the new column immediately
|
|
253
|
+
and retain their committed order throughout the drag. New accepted drops feed recents; moves and
|
|
239
254
|
cancellations do not. Row-view changes and view disposal cancel transport. The desktop pane stays open, and capture is
|
|
240
255
|
owned by the selector's surviving source controller. The consumer imports no home implementation, SQL or hydration code.
|
|
241
256
|
|
|
@@ -245,6 +260,36 @@ cancellation/rejection; it never focuses the search field. View disposal removes
|
|
|
245
260
|
restoration. Custom pane adapters may implement the same optional suspension argument without importing `home`.
|
|
246
261
|
|
|
247
262
|
`node test/browser-requests.mjs` checks request counts and real mouse gestures in Chromium with synthetic HTTP data.
|
|
248
|
-
It covers initial mount, click placeholders, hidden drag
|
|
249
|
-
|
|
263
|
+
It covers initial mount, click placeholders, hidden drag additions, values for 80 loaded rows before entry or drop,
|
|
264
|
+
green addition indicators, outside removal before and after entry, terminal toggles and reordering.
|
|
265
|
+
`node test/browser-removal-cancel.mjs` checks immediate unchecking and cancellation during the red flash through
|
|
266
|
+
checkbox, cell and name clicks, for terminal and relation columns, with an immediate removal save followed by a restored configuration save.
|
|
267
|
+
`node test/browser-column-positions.mjs` checks neighbour-based reinsertion in the real table, both fallbacks,
|
|
268
|
+
and memory expiry on closure, including closure during a pending removal. It starts its own synthetic HTTP fixture.
|
|
250
269
|
Set `PS_PLAYWRIGHT_MODULE` to an installed Playwright module when using a different runtime.
|
|
270
|
+
|
|
271
|
+
`node test/browser-toggle.mjs` exercises the real framework list against the synthetic recipe database on port 13316
|
|
272
|
+
(`PS_DATABASE_PORT` overrides it). It starts and stops its own application instance and checks desktop keyboard, touch,
|
|
273
|
+
reduced motion, horizontal reveal, removal and temporary-row transitions. `PS_TOGGLE_URL` reuses an existing instance
|
|
274
|
+
of that recipe instead. The synthetic database must already be running; this test never resets it.
|
|
275
|
+
|
|
276
|
+
## Component column batches
|
|
277
|
+
|
|
278
|
+
Selecting a component group sends one request with `loadColumns` listing missing paths, `columns` containing the
|
|
279
|
+
complete final configuration and `rowIds` identifying visible roots. The response contains `columns`, each with
|
|
280
|
+
`path`, `label`, `sortable` and rendered `rows`. Empty `loadColumns` saves group removals without querying records.
|
|
281
|
+
Failed batch projections preserve the saved configuration. Checkboxes update optimistically before metadata or HTTP
|
|
282
|
+
requests, for both individual selections and groups. The host commits placeholders and starts the shared green flash
|
|
283
|
+
before the values request; early values remain masked until the flash finishes, while late values appear immediately.
|
|
284
|
+
Removal starts one red flash after publishing the unchecked state, then sends the configuration request. Front-end
|
|
285
|
+
columns disappear at the animation deadline even if that request is still pending. Group actions use one HTTP request.
|
|
286
|
+
|
|
287
|
+
Sub-properties reached through one collection marked `@Component` are projected together when its element type
|
|
288
|
+
has one matching `@Composite` back-reference. The list reads visible root identities once, then all requested fields
|
|
289
|
+
in one query per component collection, restricted to those identities. Singular ancestors and related object labels
|
|
290
|
+
share these queries. Values retain component order and duplicates; these columns are not sortable.
|
|
291
|
+
The same projection is used when reloading the list or adding an individual component sub-property.
|
|
292
|
+
|
|
293
|
+
`test/browser-component-batch.mjs` verifies real browser interactions, HTTP request counts, MySQL query counts,
|
|
294
|
+
rendered values, mandatory row scope, partial selection, removal and session restoration. It creates and cleans up
|
|
295
|
+
its own temporary database and server using the existing selector integration recipe.
|
package/build.js
CHANGED
|
@@ -11,24 +11,27 @@ import { tableByElement } from '@itrocks/table/table.js';
|
|
|
11
11
|
import { xTargetCall } from '@itrocks/xtarget/xtarget.js';
|
|
12
12
|
build('article[data-action="list"] > form > table.objects', table => {
|
|
13
13
|
let saving = Promise.resolve();
|
|
14
|
-
const
|
|
14
|
+
const request = (query, signal) => {
|
|
15
|
+
const form = table.closest('form');
|
|
16
|
+
const result = saving.then(() => fetch(`${form.action}?${query}`, { keepalive: true, signal }));
|
|
17
|
+
saving = result.then(() => { }, () => { });
|
|
18
|
+
return result;
|
|
19
|
+
};
|
|
20
|
+
const saveColumns = (paths) => {
|
|
15
21
|
const columns = table.tHead?.rows[0].querySelectorAll(':scope > th[data-property]:not(.column-add-provisional)');
|
|
16
22
|
const form = table.closest('form');
|
|
17
23
|
if (!columns || !form)
|
|
18
24
|
return Promise.resolve();
|
|
19
|
-
const properties = Array.from(columns, column =>
|
|
20
|
-
|
|
21
|
-
saving = saving.catch(() => { }).then(async () => {
|
|
22
|
-
const response = await fetch(url, { keepalive: true });
|
|
25
|
+
const properties = paths ?? Array.from(columns, column => column.dataset.property ?? '');
|
|
26
|
+
return request(new URLSearchParams({ columns: properties.join(',') })).then(response => {
|
|
23
27
|
if (!response.ok)
|
|
24
28
|
throw new Error('Unable to save columns');
|
|
25
29
|
});
|
|
26
|
-
return saving;
|
|
27
30
|
};
|
|
28
31
|
const tableColumnReorder = new TableColumnReorder({
|
|
29
32
|
isReorderable: header => header.dataset.property !== undefined,
|
|
30
|
-
onRemove: saveColumns,
|
|
31
|
-
onReorder: saveColumns
|
|
33
|
+
onRemove: () => saveColumns(),
|
|
34
|
+
onReorder: () => saveColumns()
|
|
32
35
|
});
|
|
33
36
|
const tableLink = new TableLink({
|
|
34
37
|
call: url => xTargetCall(url, 'main'),
|
|
@@ -46,7 +49,7 @@ build('article[data-action="list"] > form > table.objects', table => {
|
|
|
46
49
|
]
|
|
47
50
|
});
|
|
48
51
|
table.dispatchEvent(new CustomEvent('itrocks:list-ready', {
|
|
49
|
-
bubbles: true, detail: { addition, instance, saveColumns }
|
|
52
|
+
bubbles: true, detail: { addition, instance, request, saveColumns }
|
|
50
53
|
}));
|
|
51
54
|
table.addEventListener('click', event => {
|
|
52
55
|
const element = event.target;
|
package/cjs/columns.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.projectColumns = projectColumns;
|
|
|
5
5
|
const list_properties_1 = require("@itrocks/list-properties");
|
|
6
6
|
const storage_1 = require("@itrocks/storage");
|
|
7
7
|
const storage_2 = require("@itrocks/storage");
|
|
8
|
+
const storage_3 = require("@itrocks/storage");
|
|
8
9
|
const parameter_1 = require("./parameter");
|
|
9
10
|
const projection_1 = require("./projection");
|
|
10
11
|
class Columns extends parameter_1.Parameter {
|
|
@@ -74,12 +75,51 @@ class Columns extends parameter_1.Parameter {
|
|
|
74
75
|
exports.Columns = Columns;
|
|
75
76
|
async function projectColumns(type, columns, search, options) {
|
|
76
77
|
const singular = columns.filter(column => !column.multiple);
|
|
77
|
-
const
|
|
78
|
+
const selections = singular.map(column => column.selection());
|
|
79
|
+
const parents = [...new Set(columns.flatMap(column => column.component?.parent ? [column.component.parent] : []))]
|
|
80
|
+
.filter(path => !singular.some(column => column.name === path));
|
|
81
|
+
selections.push(...parents.map(path => ({ path, objectFields: [] })));
|
|
82
|
+
const result = await (0, storage_1.dataSource)().select(type, selections, search, options);
|
|
78
83
|
if (!columns.some(column => column.multiple) || !result.rows.length)
|
|
79
84
|
return result;
|
|
80
85
|
const ids = result.rows.map(row => row.rootId);
|
|
81
|
-
const
|
|
86
|
+
const direct = columns.filter(column => column.multiple && !column.component);
|
|
87
|
+
const objects = direct.length ? await (0, storage_1.dataSource)().search(type, search, [new storage_2.Identifiers(ids)]) : [];
|
|
82
88
|
const byId = new Map(objects.map(object => [String(object.id), object]));
|
|
89
|
+
const components = new Map();
|
|
90
|
+
for (const path of new Set(columns.flatMap(column => column.component ? [column.component.path] : []))) {
|
|
91
|
+
const grouped = columns.filter(column => column.component?.path === path);
|
|
92
|
+
const component = grouped[0].component;
|
|
93
|
+
const parentIds = result.rows.map(row => {
|
|
94
|
+
if (!component.parent)
|
|
95
|
+
return row.rootId;
|
|
96
|
+
const index = singular.findIndex(column => column.name === component.parent);
|
|
97
|
+
const cell = row.cells[index < 0 ? singular.length + parents.indexOf(component.parent) : index];
|
|
98
|
+
return cell.kind === 'value' ? cell.value?.id : undefined;
|
|
99
|
+
});
|
|
100
|
+
const selectedIds = [...new Set(parentIds.filter((id) => id !== undefined))];
|
|
101
|
+
const selections = new Map([
|
|
102
|
+
[component.inverse, { path: component.inverse, objectFields: [] }]
|
|
103
|
+
]);
|
|
104
|
+
for (const column of grouped)
|
|
105
|
+
selections.set(column.component.column.name, column.component.column.selection());
|
|
106
|
+
const names = [...selections.keys()];
|
|
107
|
+
const values = selectedIds.length ? await (0, storage_1.dataSource)().select(component.type, [...selections.values()], { [component.inverse]: selectedIds }, [new storage_3.Sort([])]) : undefined;
|
|
108
|
+
for (const column of grouped) {
|
|
109
|
+
const byParent = new Map();
|
|
110
|
+
for (const row of values?.rows ?? []) {
|
|
111
|
+
const owner = row.cells[0];
|
|
112
|
+
if (owner.kind !== 'value' || !owner.value)
|
|
113
|
+
continue;
|
|
114
|
+
const id = String(owner.value.id);
|
|
115
|
+
let cells = byParent.get(id);
|
|
116
|
+
if (!cells)
|
|
117
|
+
byParent.set(id, cells = []);
|
|
118
|
+
cells.push(row.cells[names.indexOf(column.component.column.name)]);
|
|
119
|
+
}
|
|
120
|
+
components.set(column.name, new Map(result.rows.map((row, index) => [String(row.rootId), byParent.get(String(parentIds[index])) ?? []])));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
83
123
|
return {
|
|
84
124
|
rootType: type,
|
|
85
125
|
columns: columns.map(column => ({ path: column.name, kind: column.multiple ? 'scalar' :
|
|
@@ -89,6 +129,9 @@ async function projectColumns(type, columns, search, options) {
|
|
|
89
129
|
const cells = await Promise.all(columns.map(async (column) => {
|
|
90
130
|
if (!column.multiple)
|
|
91
131
|
return row.cells[singular.indexOf(column)];
|
|
132
|
+
if (column.component)
|
|
133
|
+
return { kind: 'value',
|
|
134
|
+
value: components.get(column.name).get(String(row.rootId)) ?? [] };
|
|
92
135
|
const value = object ? await object[column.name] : [];
|
|
93
136
|
return { kind: 'value', value: value ?? [] };
|
|
94
137
|
}));
|
package/cjs/columns.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"columns.js","sourceRoot":"","sources":["../src/columns.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"columns.js","sourceRoot":"","sources":["../src/columns.ts"],"names":[],"mappings":"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"}
|
package/cjs/list.js
CHANGED
|
@@ -89,7 +89,8 @@ let List = (() => {
|
|
|
89
89
|
for (const field of column.fields ?? [])
|
|
90
90
|
await access.authorise(`${column.name}.${field}`);
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
const batch = typeof request.request.data.loadColumns === 'string';
|
|
93
|
+
if (request.request.data.columns !== undefined && typeof request.request.data.column !== 'string' && !batch) {
|
|
93
94
|
(0, persist_2.save)(this, request.request, type);
|
|
94
95
|
return this.htmlResponse('', 204);
|
|
95
96
|
}
|
|
@@ -98,13 +99,44 @@ let List = (() => {
|
|
|
98
99
|
for (const column of columns)
|
|
99
100
|
column.search = filter.search[column.name] ?? '';
|
|
100
101
|
sort.getParams(request.request, type);
|
|
101
|
-
|
|
102
|
+
if (!batch)
|
|
103
|
+
(0, persist_2.save)(this, request.request, type);
|
|
102
104
|
const options = feed.readOptions(filter.readOptions(sort.readOptions()));
|
|
103
105
|
const search = Object.assign(filter.searchObject(type), scope);
|
|
104
106
|
for (const path of Object.keys(search))
|
|
105
107
|
await access.authorise(path);
|
|
106
108
|
for (const path of Object.keys(sort.sorted ?? {}))
|
|
107
109
|
await access.authorise(path);
|
|
110
|
+
if (batch) {
|
|
111
|
+
try {
|
|
112
|
+
const names = request.request.data.loadColumns.split(',').filter(Boolean);
|
|
113
|
+
if (new Set(names).size !== names.length)
|
|
114
|
+
throw new Error('Duplicate column paths');
|
|
115
|
+
const requested = names.map(path => new projection_1.ListColumn(type, path));
|
|
116
|
+
for (const column of requested) {
|
|
117
|
+
if (!columns.serialize().includes(column.name))
|
|
118
|
+
throw new Error('Column absent from configuration');
|
|
119
|
+
await access.authorise(column.name);
|
|
120
|
+
for (const field of column.fields ?? [])
|
|
121
|
+
await access.authorise(`${column.name}.${field}`);
|
|
122
|
+
}
|
|
123
|
+
const ids = request.request.data.rowIds;
|
|
124
|
+
if (typeof ids !== 'string')
|
|
125
|
+
throw new Error('Visible row identities are required');
|
|
126
|
+
const result = requested.length ? await (0, columns_2.projectColumns)(type, requested, search, [new storage_1.Identifiers(ids ? ids.split(',') : [])]) : undefined;
|
|
127
|
+
const response = await Promise.all(requested.map(async (column, index) => ({
|
|
128
|
+
label: column.label, path: column.name, sortable: column.sortable,
|
|
129
|
+
rows: await Promise.all(result.rows.map(async (row) => ({
|
|
130
|
+
id: row.rootId, html: await column.output(row.cells[index])
|
|
131
|
+
})))
|
|
132
|
+
})));
|
|
133
|
+
(0, persist_2.save)(this, request.request, type);
|
|
134
|
+
return this.jsonResponse({ columns: response });
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
return this.jsonResponse({ error: error instanceof Error ? error.message : 'Columns unavailable' }, 400);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
108
140
|
if (typeof request.request.data.column === 'string') {
|
|
109
141
|
try {
|
|
110
142
|
const path = request.request.data.column;
|
package/cjs/list.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","sourceRoot":"","sources":["../src/list.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4CAAwD;AAExD,4CAAwD;AACxD,4CAAwD;AAExD,0CAAuD;AACvD,0CAAuD;AACvD,8CAAyD;AAEzD,8CAAyD;AACzD,8CAAyD;AACzD,qCAAiD;AACjD,uCAAkD;AAClD,uCAAkD;AAClD,iCAA+C;AAC/C,qCAAiD;AACjD,uCAAkD;AAClD,uCAAkD;AAClD,6CAAqD;AACrD,qDAAyD;AACzD,qDAAyD;AACzD,iCAA+C;AAE/C,mCAAwC;AAA/B,uGAAA,aAAa,OAAA;IAKT,IAAI;4BAFhB,IAAA,aAAI,EAAC,OAAO,EAAE,KAAK,CAAC,EACpB,IAAA,aAAK,EAAC,OAAO,CAAC;;;;sBACsC,eAAM;oBAAd,SAAQ,WAAS;;;;YAA9D,
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../src/list.ts"],"names":[],"mappings":"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"}
|
package/cjs/projection.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ export declare function representationFields(type: Type, ancestors?: readonly Ty
|
|
|
11
11
|
export declare class ListColumn {
|
|
12
12
|
readonly root: Type;
|
|
13
13
|
readonly name: string;
|
|
14
|
+
readonly component?: {
|
|
15
|
+
column: ListColumn;
|
|
16
|
+
inverse: string;
|
|
17
|
+
parent: string;
|
|
18
|
+
path: string;
|
|
19
|
+
type: Type;
|
|
20
|
+
};
|
|
14
21
|
readonly field: string;
|
|
15
22
|
readonly fields?: readonly string[];
|
|
16
23
|
readonly label: string;
|
package/cjs/projection.js
CHANGED
|
@@ -3,13 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ListColumn = void 0;
|
|
4
4
|
exports.listRepresentationDependsOn = listRepresentationDependsOn;
|
|
5
5
|
exports.representationFields = representationFields;
|
|
6
|
+
const class_type_1 = require("@itrocks/class-type");
|
|
6
7
|
const class_view_1 = require("@itrocks/class-view");
|
|
7
8
|
const class_view_2 = require("@itrocks/class-view");
|
|
8
9
|
const class_view_3 = require("@itrocks/class-view");
|
|
9
10
|
const composition_1 = require("@itrocks/composition");
|
|
11
|
+
const composition_2 = require("@itrocks/composition");
|
|
10
12
|
const core_transformers_1 = require("@itrocks/core-transformers");
|
|
11
13
|
const core_transformers_2 = require("@itrocks/core-transformers");
|
|
12
14
|
const property_view_1 = require("@itrocks/property-view");
|
|
15
|
+
const reflect_1 = require("@itrocks/reflect");
|
|
16
|
+
const reflect_2 = require("@itrocks/reflect");
|
|
13
17
|
const reflect_path_1 = require("@itrocks/reflect-path");
|
|
14
18
|
const reflect_path_2 = require("@itrocks/reflect-path");
|
|
15
19
|
const storage_1 = require("@itrocks/storage");
|
|
@@ -41,6 +45,7 @@ function representationFields(type, ancestors = [], property) {
|
|
|
41
45
|
class ListColumn {
|
|
42
46
|
root;
|
|
43
47
|
name;
|
|
48
|
+
component;
|
|
44
49
|
field;
|
|
45
50
|
fields;
|
|
46
51
|
label;
|
|
@@ -58,12 +63,36 @@ class ListColumn {
|
|
|
58
63
|
const resolved = (0, reflect_path_2.resolvePropertyPath)(root, name);
|
|
59
64
|
this.multiple = resolved.targetMultiplicity === 'many';
|
|
60
65
|
this.sortable = !this.multiple;
|
|
61
|
-
if (
|
|
66
|
+
if (this.multiple && resolved.segments.length > 1) {
|
|
67
|
+
const index = resolved.segments.findIndex(property => property.relation?.targetMultiplicity === 'many');
|
|
68
|
+
const property = resolved.segments[index];
|
|
69
|
+
const target = property?.relation?.target;
|
|
70
|
+
if (target && (0, composition_1.componentOf)(property.class.type, property.name) && index < resolved.segments.length - 1) {
|
|
71
|
+
const inverses = Object.keys(new reflect_1.ReflectClass(target).propertyTypes).filter(name => {
|
|
72
|
+
if (!(0, composition_2.compositeOf)(target, name))
|
|
73
|
+
return false;
|
|
74
|
+
const relation = new reflect_2.ReflectProperty(target, name).relation;
|
|
75
|
+
return relation?.target && (0, class_type_1.inherits)(property.class.type, relation.target);
|
|
76
|
+
});
|
|
77
|
+
if (inverses.length === 1) {
|
|
78
|
+
const column = new ListColumn(target, name.split('.').slice(index + 1).join('.'));
|
|
79
|
+
if (!column.multiple)
|
|
80
|
+
this.component = {
|
|
81
|
+
column, inverse: inverses[0], parent: name.split('.').slice(0, index).join('.'),
|
|
82
|
+
path: name.split('.').slice(0, index + 1).join('.'), type: target
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (resolved.targetMultiplicity !== 'one' && !(this.multiple && resolved.segments.length === 1)
|
|
88
|
+
&& !this.component) {
|
|
62
89
|
throw new storage_1.SelectionError(resolved.targetMultiplicity === 'many' ? 'multiple-values' : 'unsupported-selection', name);
|
|
63
90
|
}
|
|
64
91
|
this.property = new reflect_path_1.ReflectPropertyPath(root, name);
|
|
65
92
|
this.target = resolved.target;
|
|
66
93
|
this.label = resolved.segments.map(property => (0, translate_1.tr)((0, property_view_1.displayOf)(property.class.type, property.name))).join('.');
|
|
94
|
+
if (this.component)
|
|
95
|
+
this.fields = this.component.column.fields;
|
|
67
96
|
if (resolved.target && !this.multiple)
|
|
68
97
|
this.fields = representationFields(resolved.target, [], resolved.property);
|
|
69
98
|
}
|
|
@@ -72,6 +101,12 @@ class ListColumn {
|
|
|
72
101
|
return '';
|
|
73
102
|
if (cell.kind === 'unloaded')
|
|
74
103
|
return '<span class="unavailable">Unavailable</span>';
|
|
104
|
+
if (this.component) {
|
|
105
|
+
const cells = cell.value;
|
|
106
|
+
const values = await Promise.all(cells.map(cell => this.component.column.output(cell)));
|
|
107
|
+
return (0, multiple_output_1.multipleOutputOf)(this.property.class.type, this.property.name) === 'lines'
|
|
108
|
+
? '<ul>' + values.map(value => `<li>${value}</li>`).join('') + '</ul>' : values.join(', ');
|
|
109
|
+
}
|
|
75
110
|
if (this.fields && this.fields.some(field => !cell.loadedFields?.includes(field)))
|
|
76
111
|
throw new storage_1.SelectionError('unsupported-selection', this.name);
|
|
77
112
|
if (this.multiple && (0, composition_1.componentOf)(this.property.class.type, this.property.name)) {
|
package/cjs/projection.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":";;;AAgCA,kEAGC;AAED,oDAgBC;AArDD,oDAAiE;AAEjE,oDAAiE;AACjE,oDAAiE;AACjE,oDAAiE;AACjE,sDAAkE;AAClE,sDAAkE;AAClE,kEAAwE;AACxE,kEAAwE;AACxE,0DAAoE;AACpE,8CAA8D;AAC9D,8CAA8D;AAC9D,wDAAmE;AACnE,wDAAmE;AAGnE,8CAA8D;AAC9D,kDAAgE;AAChE,sDAAkE;AAClE,sDAAkE;AAClE,sDAAkE;AAClE,sDAAkE;AAClE,uDAA+D;AAQ/D,MAAM,YAAY,GAA+B,EAAE,CAAA;AAEnD,SAAgB,2BAA2B,CAAC,SAA8C;IAEzF,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;AACvC,CAAC;AAED,SAAgB,oBAAoB,CACnC,IAAU,EAAE,YAA6B,EAAE,EAAE,QAAiC;IAG9E,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,wBAAc,CAAC,uBAAuB,EAAE,8BAA8B,CAAC,CAAA;IAC/G,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,EAAE,IAAI,CAAC,CAAA;IACnC,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC5D,IAAI,CAAC,MAAM,IAAI,QAAQ,IAAI,IAAA,gCAAkB,EAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,kBAAI,EAAE,oBAAM,CAAC,KAAK,+BAAW;QAC9G,MAAM,IAAI,wBAAc,CAAC,uBAAuB,EAAE,yCAAyC,CAAC,CAAA;IAC7F,OAAO,CAAC,MAAM,IAAI,IAAA,6BAAgB,EAAC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACpE,MAAM,QAAQ,GAAG,IAAA,kCAAmB,EAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAChD,IAAI,QAAQ,CAAC,kBAAkB,KAAK,KAAK;YACxC,MAAM,IAAI,wBAAc,CAAC,uBAAuB,EAAE,IAAI,CAAC,CAAA;QACxD,OAAO,QAAQ,CAAC,MAAM;YACrB,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IAC9G,CAAC,CAAC,CAAA;AACH,CAAC;AAED,MAAa,UAAU;IAeD;IAAqB;IAZjC,SAAS,CAAoF;IAC7F,KAAK,CAAQ;IACb,MAAM,CAAoB;IAC1B,KAAK,CAAQ;IACb,QAAQ,CAAS;IACjB,QAAQ,CAAoD;IAC5D,QAAQ,CAAS;IACjB,MAAM,CAAO;IACtB,OAAO,GAAG,KAAK,CAAA;IACf,MAAM,GAAG,EAAE,CAAA;IACX,IAAI,CAAS;IAEb,YAAqB,IAAU,EAAW,IAAY;QAAjC,SAAI,GAAJ,IAAI,CAAM;QAAW,SAAI,GAAJ,IAAI,CAAQ;QAErD,IAAI,CAAC,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAC5D,MAAM,QAAQ,GAAG,IAAA,kCAAmB,EAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAChD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,kBAAkB,KAAK,MAAM,CAAA;QACtD,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAA;QAC9B,IAAI,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnD,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,KAAK,MAAM,CAAC,CAAA;YACvG,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YACzC,MAAM,MAAM,GAAG,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAA;YACzC,IAAI,MAAM,IAAI,IAAA,yBAAW,EAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvG,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,sBAAY,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;oBAClF,IAAI,CAAC,IAAA,yBAAW,EAAC,MAAM,EAAE,IAAa,CAAC;wBAAE,OAAO,KAAK,CAAA;oBACrD,MAAM,QAAQ,GAAG,IAAI,yBAAe,CAAC,MAAM,EAAE,IAAa,CAAC,CAAC,QAAQ,CAAA;oBACpE,OAAO,QAAQ,EAAE,MAAM,IAAI,IAAA,qBAAQ,EAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;gBAC1E,CAAC,CAAC,CAAA;gBACF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC3B,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;oBACjF,IAAI,CAAC,MAAM,CAAC,QAAQ;wBAAE,IAAI,CAAC,SAAS,GAAG;4BACtC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;4BAC/E,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM;yBACjE,CAAA;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QACD,IAAI,QAAQ,CAAC,kBAAkB,KAAK,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;eAC3F,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACrB,MAAM,IAAI,wBAAc,CACvB,QAAQ,CAAC,kBAAkB,KAAK,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,uBAAuB,EAAE,IAAI,CAC1F,CAAA;QACF,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,kCAAmB,CAAC,IAAI,EAAE,IAAa,CAAC,CAAA;QAC5D,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAA;QAC7B,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAA,cAAE,EAAC,IAAA,yBAAS,EAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC3G,IAAI,IAAI,CAAC,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAA;QAC9D,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ;YACpC,IAAI,CAAC,MAAM,GAAG,oBAAoB,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAC5E,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAoB;QAEhC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAA;QACrC,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU;YAAE,OAAO,8CAA8C,CAAA;QACnF,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAyB,CAAA;YAC5C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,SAAU,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACxF,OAAO,IAAA,kCAAgB,EAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,OAAO;gBAChF,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC5F,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;YAChF,MAAM,IAAI,wBAAc,CAAC,uBAAuB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAC7D,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAA,yBAAW,EAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAChF,MAAM,MAAM,GAAG,IAAI,CAAC,KAAiB,CAAA;YACrC,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;gBACxB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM;oBACvB,CAAC,CAAC,IAAA,sBAAc,EAAC,IAAI,CAAC,MAAM,CAAC;oBAC7B,CAAC,CAAC,IAAA,yBAAS,EAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;gBAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAA;gBAC1E,OAAO,GAAG,MAAM,CAAC,MAAM,IAAI,IAAA,cAAE,EAAC,MAAM,CAAC,EAAE,CAAA;YACxC,CAAC;YACD,OAAO,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,kCAAqB,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChF,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAA,kCAAgB,EAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACvG,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAA;QACjD,OAAO,MAAM,IAAA,8BAAgB,EAC5B,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,kBAAI,EAAE,oBAAM,EACtE,IAAI,iCAAa,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,CACjD,CAAA;IACF,CAAC;IAED,SAAS;QAER,IAAI,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,wBAAc,CAAC,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QACzE,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;IAChF,CAAC;IAED,SAAS;QAER,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrG,CAAC;CAED;AAhGD,gCAgGC"}
|
package/list.css
CHANGED
|
@@ -33,7 +33,7 @@ table.objects {
|
|
|
33
33
|
overflow-y: auto;
|
|
34
34
|
}
|
|
35
35
|
table.objects th {
|
|
36
|
-
background: #
|
|
36
|
+
background: #eee;
|
|
37
37
|
}
|
|
38
38
|
table.objects tr td, table.objects tr th {
|
|
39
39
|
border-bottom: 1px solid #d8d8d8;
|
|
@@ -51,7 +51,7 @@ table.objects > * > tr > .column-reorder {
|
|
|
51
51
|
--column-reorder-tint: inset 0 0 0 100vmax rgb(0 0 0 / 2%);
|
|
52
52
|
box-shadow: var(--column-reorder-tint);
|
|
53
53
|
}
|
|
54
|
-
table.objects > * > tr > .column-reorder.
|
|
54
|
+
table.objects > * > tr > .column-reorder.adding {
|
|
55
55
|
--column-reorder-tint: inset 0 0 0 100vmax rgb(47 137 67 / 5%);
|
|
56
56
|
}
|
|
57
57
|
table.objects > * > tr > .column-reorder.moving {
|
|
@@ -84,10 +84,10 @@ table.objects > tbody:first-child > tr:nth-child(odd) > td {
|
|
|
84
84
|
table.objects > thead > tr.properties > th {
|
|
85
85
|
user-select: none;
|
|
86
86
|
}
|
|
87
|
-
table.objects > thead > tr.properties > th.moving, table.objects > thead > tr.properties > th.removing {
|
|
87
|
+
table.objects > thead > tr.properties > th.adding, table.objects > thead > tr.properties > th.moving, table.objects > thead > tr.properties > th.removing {
|
|
88
88
|
z-index: 2;
|
|
89
89
|
}
|
|
90
|
-
table.objects > thead > tr.properties > th.
|
|
90
|
+
table.objects > thead > tr.properties > th.adding::before, table.objects > thead > tr.properties > th.moving::before, table.objects > thead > tr.properties > th.removing::before {
|
|
91
91
|
content: "";
|
|
92
92
|
display: inline-block;
|
|
93
93
|
height: 1em;
|
|
@@ -108,7 +108,7 @@ table.objects > thead > tr.properties > th.removing::before {
|
|
|
108
108
|
background: #d51d2c;
|
|
109
109
|
mask-image: url("column-remove.svg");
|
|
110
110
|
}
|
|
111
|
-
table.objects > thead > tr.properties > th.
|
|
111
|
+
table.objects > thead > tr.properties > th.adding::before {
|
|
112
112
|
background: #2f8943;
|
|
113
113
|
mask-image: url("column-add.svg");
|
|
114
114
|
}
|
|
@@ -164,12 +164,15 @@ table.objects > thead > tr.search > th > input:focus {
|
|
|
164
164
|
outline: none;
|
|
165
165
|
box-shadow: inset 0 0 0 2px Highlight, var(--column-reorder-tint, 0 0 transparent);
|
|
166
166
|
}
|
|
167
|
-
table.objects > thead + tbody > tr:nth-child(even)
|
|
167
|
+
table.objects > thead + tbody > tr:nth-child(even) {
|
|
168
168
|
background: #f8f8f8;
|
|
169
169
|
}
|
|
170
|
-
table.objects > thead + tbody > tr:nth-child(odd)
|
|
170
|
+
table.objects > thead + tbody > tr:nth-child(odd) {
|
|
171
171
|
background: white;
|
|
172
172
|
}
|
|
173
|
+
table.objects > thead + tbody > tr:hover {
|
|
174
|
+
background: #f4f4f4;
|
|
175
|
+
}
|
|
173
176
|
|
|
174
177
|
:not(article, article > form) > table.objects td, :not(article, article > form) > table.objects th {
|
|
175
178
|
border-top: 1px solid #d8d8d8;
|
package/package.json
CHANGED
package/property-select.d.ts
CHANGED
|
@@ -25,32 +25,41 @@ export type ListSelectorOptions = {
|
|
|
25
25
|
addition: TableColumnAdd;
|
|
26
26
|
metadata: MetadataProvider;
|
|
27
27
|
mountSelector(content: HTMLElement, title: string): PaneHandle;
|
|
28
|
-
|
|
28
|
+
request?(query: URLSearchParams, signal: AbortSignal): Promise<Response>;
|
|
29
|
+
saveColumns(paths?: readonly string[]): Promise<void>;
|
|
29
30
|
templateUrl?: string;
|
|
30
31
|
};
|
|
31
32
|
export declare class ListPropertySelect implements ConsumerPort {
|
|
32
33
|
private readonly article;
|
|
33
34
|
private readonly options;
|
|
34
35
|
private readonly abort;
|
|
36
|
+
private batch;
|
|
35
37
|
private readonly button;
|
|
38
|
+
private readonly content;
|
|
36
39
|
private disposed;
|
|
37
40
|
private drag?;
|
|
38
41
|
private readonly observer;
|
|
39
42
|
private readonly pane;
|
|
40
43
|
private readonly pending;
|
|
44
|
+
private readonly removals;
|
|
45
|
+
private removedColumns;
|
|
46
|
+
private readonly unsubscribePane;
|
|
41
47
|
readonly view: PropertySelectView;
|
|
42
48
|
constructor(article: HTMLElement, options: ListSelectorOptions);
|
|
43
49
|
private loadColumn;
|
|
50
|
+
private loadColumns;
|
|
51
|
+
private columnContent;
|
|
44
52
|
private add;
|
|
45
53
|
begin(intent: SelectionIntent, sample: PointerSample): void;
|
|
46
54
|
private columnSource;
|
|
47
|
-
private validateColumn;
|
|
48
55
|
cancel(id: string): void;
|
|
49
|
-
private
|
|
56
|
+
private displayedPaths;
|
|
50
57
|
dispose(): void;
|
|
51
58
|
end(id: string, sample: PointerSample): Promise<Outcome>;
|
|
52
59
|
mount(): Promise<boolean>;
|
|
53
60
|
move(id: string, sample: PointerSample): void;
|
|
54
61
|
paths(): string[];
|
|
62
|
+
private remove;
|
|
55
63
|
select(intent: SelectionIntent, signal: AbortSignal): Promise<Outcome>;
|
|
64
|
+
selectMany(intents: readonly SelectionIntent[], signal: AbortSignal): Promise<readonly Outcome[]>;
|
|
56
65
|
}
|
package/property-select.js
CHANGED
|
@@ -5,27 +5,37 @@ export class ListPropertySelect {
|
|
|
5
5
|
article;
|
|
6
6
|
options;
|
|
7
7
|
abort = new AbortController();
|
|
8
|
+
batch = false;
|
|
8
9
|
button;
|
|
10
|
+
content = document.createElement('div');
|
|
9
11
|
disposed = false;
|
|
10
12
|
drag;
|
|
11
13
|
observer;
|
|
12
14
|
pane;
|
|
13
15
|
pending = new Map();
|
|
16
|
+
removals = new Map();
|
|
17
|
+
removedColumns = new Map();
|
|
18
|
+
unsubscribePane;
|
|
14
19
|
view;
|
|
15
20
|
constructor(article, options) {
|
|
16
21
|
this.article = article;
|
|
17
22
|
this.options = options;
|
|
18
|
-
const content =
|
|
23
|
+
const content = this.content;
|
|
19
24
|
this.button = article.querySelector('.add-column');
|
|
20
|
-
this.pane = options.mountSelector(content, 'Add columns');
|
|
21
25
|
const initial = article.dataset.selectorBootstrap
|
|
22
26
|
? JSON.parse(decodeURIComponent(article.dataset.selectorBootstrap)) : undefined;
|
|
27
|
+
const translate = (text) => initial?.translations?.[text] ?? text;
|
|
28
|
+
this.pane = options.mountSelector(content, translate('Add columns'));
|
|
29
|
+
this.unsubscribePane = this.pane.subscribe(state => {
|
|
30
|
+
if (!state.open || !state.present)
|
|
31
|
+
this.removedColumns = new Map();
|
|
32
|
+
});
|
|
23
33
|
this.view = new PropertySelectView(content, {
|
|
24
34
|
consumer: this, metadata: options.metadata, root: article.dataset.propertyRoot,
|
|
25
35
|
advice: options.advice, stateKey: article.dataset.selectorKey, stateMode: 'session',
|
|
26
36
|
stateStore: new HttpStateStore(undefined, undefined, initial && {
|
|
27
37
|
...initial, key: article.dataset.selectorKey
|
|
28
|
-
}), usedPaths: this.paths()
|
|
38
|
+
}), translate, usedPaths: this.paths()
|
|
29
39
|
}, { template: initial?.template, templateUrl: options.templateUrl });
|
|
30
40
|
this.observer = new MutationObserver(records => {
|
|
31
41
|
if (this.drag && records.some(record => record.target.tagName === 'TBODY'))
|
|
@@ -37,23 +47,36 @@ export class ListPropertySelect {
|
|
|
37
47
|
this.button.addEventListener('click', () => this.pane.open(), { signal: this.abort.signal });
|
|
38
48
|
}
|
|
39
49
|
async loadColumn(path, signal) {
|
|
50
|
+
return (await this.loadColumns([path], this.paths(), signal, true)).get(path);
|
|
51
|
+
}
|
|
52
|
+
async loadColumns(paths, columns, signal, single = false) {
|
|
40
53
|
const form = this.article.querySelector('form');
|
|
41
54
|
const table = this.article.querySelector('table.objects');
|
|
42
55
|
const rows = [...table.tBodies[0].rows];
|
|
43
56
|
const ids = rows.map(row => row.querySelector('input[name=id]').value);
|
|
44
|
-
const query = new URLSearchParams({
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
const query = new URLSearchParams({ columns: columns.join(','), rowIds: ids.join(',') });
|
|
58
|
+
query.set(single ? 'column' : 'loadColumns', paths.join(','));
|
|
59
|
+
const combined = AbortSignal.any([signal, this.abort.signal]);
|
|
60
|
+
const response = this.options.request
|
|
61
|
+
? await this.options.request(query, combined)
|
|
62
|
+
: await fetch(`${form.action}?${query}`, { signal: combined });
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const result = await response.json();
|
|
50
65
|
throw new Error(result.error ?? 'Column unavailable');
|
|
66
|
+
}
|
|
67
|
+
const result = single ? [await response.json()]
|
|
68
|
+
: (await response.json()).columns;
|
|
51
69
|
signal.throwIfAborted();
|
|
52
70
|
if (this.disposed || !this.article.isConnected || rows.some((row, index) => table.tBodies[0].rows[index] !== row)
|
|
53
71
|
|| rows.length !== table.tBodies[0].rows.length)
|
|
54
72
|
throw new Error('The row view changed; try again');
|
|
55
|
-
if (result.
|
|
73
|
+
if (!Array.isArray(result) || result.length !== paths.length
|
|
74
|
+
|| paths.some(path => result.filter(column => column.path === path).length !== 1))
|
|
56
75
|
throw new Error('Mismatched column response');
|
|
76
|
+
return new Map(result.map(column => [column.path, this.columnContent(column, ids, table)]));
|
|
77
|
+
}
|
|
78
|
+
columnContent(result, ids, table) {
|
|
79
|
+
const path = result.path;
|
|
57
80
|
const values = new Map(result.rows.map(row => [String(row.id), row.html]));
|
|
58
81
|
if (ids.some(id => !values.has(id)))
|
|
59
82
|
throw new Error('The row scope changed; reload the list');
|
|
@@ -64,6 +87,16 @@ export class ListPropertySelect {
|
|
|
64
87
|
input.name = `search.${encodeURIComponent(path).replaceAll('.', '%2E')}`;
|
|
65
88
|
input.dataset.propertyFilter = path;
|
|
66
89
|
input.setAttribute('aria-label', `Filter ${result.label}`);
|
|
90
|
+
const previous = [...table.tHead.rows[0].cells].find(header => header.dataset.property === path);
|
|
91
|
+
const previousInput = previous?.querySelector('input[data-property-filter]');
|
|
92
|
+
input.value = previousInput?.value ?? '';
|
|
93
|
+
if (previousInput && document.activeElement === previousInput)
|
|
94
|
+
requestAnimationFrame(() => {
|
|
95
|
+
if (!input.isConnected || document.activeElement !== document.body)
|
|
96
|
+
return;
|
|
97
|
+
input.focus();
|
|
98
|
+
input.setSelectionRange(previousInput.selectionStart, previousInput.selectionEnd);
|
|
99
|
+
});
|
|
67
100
|
header.append(result.label, input);
|
|
68
101
|
const cells = ids.map(id => {
|
|
69
102
|
const cell = document.createElement('td');
|
|
@@ -72,17 +105,32 @@ export class ListPropertySelect {
|
|
|
72
105
|
});
|
|
73
106
|
return { cells, header };
|
|
74
107
|
}
|
|
75
|
-
|
|
108
|
+
add(path, signal, options = {}, load) {
|
|
76
109
|
const source = this.columnSource(path);
|
|
77
|
-
|
|
110
|
+
if (load)
|
|
111
|
+
source.load = load;
|
|
112
|
+
const operation = this.options.addition.begin(source, options);
|
|
78
113
|
try {
|
|
79
|
-
await this.validateColumn(path, signal);
|
|
80
114
|
signal.throwIfAborted();
|
|
81
115
|
if (this.disposed)
|
|
82
116
|
throw new Error('List disposed');
|
|
117
|
+
const position = this.removedColumns.get(path);
|
|
118
|
+
if (position) {
|
|
119
|
+
const headers = [...this.article.querySelectorAll('thead [data-property]:not(.column-add-provisional)')];
|
|
120
|
+
const left = headers.find(header => header.dataset.property === position.left);
|
|
121
|
+
const right = headers.find(header => header.dataset.property === position.right);
|
|
122
|
+
const anchor = left ?? right;
|
|
123
|
+
if (anchor) {
|
|
124
|
+
let index = anchor.cellIndex + (left ? 1 : 0);
|
|
125
|
+
if (operation.header.cellIndex < index)
|
|
126
|
+
index--;
|
|
127
|
+
operation.moveTo(index);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
83
130
|
const outcome = operation.commit();
|
|
84
|
-
|
|
85
|
-
|
|
131
|
+
this.removedColumns.delete(path);
|
|
132
|
+
if (!this.batch)
|
|
133
|
+
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
86
134
|
return outcome;
|
|
87
135
|
}
|
|
88
136
|
catch (error) {
|
|
@@ -91,24 +139,17 @@ export class ListPropertySelect {
|
|
|
91
139
|
}
|
|
92
140
|
}
|
|
93
141
|
begin(intent, sample) {
|
|
94
|
-
if (this.disposed || this.drag || this.pending.size)
|
|
142
|
+
if (this.disposed || this.batch || this.drag || this.pending.size)
|
|
95
143
|
throw new Error('A column operation is pending');
|
|
96
144
|
const path = normalisePath(intent.path);
|
|
97
|
-
const
|
|
145
|
+
const added = !this.paths().includes(path);
|
|
98
146
|
const suspension = this.pane.suspend({ mobileOnly: true });
|
|
99
147
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
source.accept = async (signal) => {
|
|
106
|
-
await dropped;
|
|
107
|
-
signal.throwIfAborted();
|
|
108
|
-
await this.validateColumn(path, signal);
|
|
109
|
-
};
|
|
110
|
-
const operation = this.options.addition.beginExternal(source, sample);
|
|
111
|
-
this.drag = { drop, intent, operation, paths, restore: () => suspension.restore() };
|
|
148
|
+
if (added)
|
|
149
|
+
this.add(path, this.abort.signal, { effects: false, hidden: true });
|
|
150
|
+
const paths = this.paths();
|
|
151
|
+
const operation = this.options.addition.beginExternal(this.columnSource(path), sample, { adding: added, removal: true });
|
|
152
|
+
this.drag = { added, intent, operation, paths, restore: () => suspension.restore() };
|
|
112
153
|
}
|
|
113
154
|
catch (error) {
|
|
114
155
|
suspension.restore();
|
|
@@ -116,10 +157,17 @@ export class ListPropertySelect {
|
|
|
116
157
|
}
|
|
117
158
|
}
|
|
118
159
|
columnSource(path) {
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
160
|
+
const selector = this.view.selector;
|
|
161
|
+
const row = this.content.querySelector(`.ps-property[data-path="${CSS.escape(path)}"]`);
|
|
162
|
+
const result = selector.search.results.find(result => result.property.path === path);
|
|
163
|
+
const properties = selector.branchContexts.flatMap(context => context.properties);
|
|
164
|
+
const label = row?.title || selector.usedProperties.find(property => property.path === path)?.label
|
|
165
|
+
|| path.split('.').map((name, index, names) => {
|
|
166
|
+
const prefix = names.slice(0, index + 1).join('.');
|
|
167
|
+
return properties.find(property => property.path === prefix)?.label
|
|
168
|
+
|| result?.segments.find(segment => segment.path === prefix)?.label
|
|
169
|
+
|| (prefix === path ? result?.property.label || row?.querySelector('.ps-main')?.textContent : '') || name;
|
|
170
|
+
}).join('.');
|
|
123
171
|
return {
|
|
124
172
|
id: path,
|
|
125
173
|
create: () => {
|
|
@@ -127,33 +175,27 @@ export class ListPropertySelect {
|
|
|
127
175
|
header.dataset.property = path;
|
|
128
176
|
header.dataset.sortable = 'false';
|
|
129
177
|
header.textContent = label;
|
|
178
|
+
const input = document.createElement('input');
|
|
179
|
+
input.name = `search.${encodeURIComponent(path).replaceAll('.', '%2E')}`;
|
|
180
|
+
input.dataset.propertyFilter = path;
|
|
181
|
+
input.setAttribute('aria-label', `Filter ${label}`);
|
|
182
|
+
header.append(input);
|
|
130
183
|
const cells = [...this.article.querySelectorAll('table.objects tbody tr')]
|
|
131
|
-
.map(() =>
|
|
184
|
+
.map(() => {
|
|
185
|
+
const cell = document.createElement('td');
|
|
186
|
+
cell.textContent = '\u00a0';
|
|
187
|
+
return cell;
|
|
188
|
+
});
|
|
132
189
|
return { cells, header };
|
|
133
190
|
},
|
|
134
191
|
load: async (signal) => {
|
|
192
|
+
// Commit the placeholder before reading the column configuration.
|
|
193
|
+
await Promise.resolve();
|
|
135
194
|
signal.throwIfAborted();
|
|
136
|
-
|
|
137
|
-
return await this.loadColumn(path, signal);
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
if (!this.disposed && !signal.aborted)
|
|
141
|
-
this.previewError(path);
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
195
|
+
return this.loadColumn(path, signal);
|
|
144
196
|
}
|
|
145
197
|
};
|
|
146
198
|
}
|
|
147
|
-
async validateColumn(path, signal) {
|
|
148
|
-
const form = this.article.querySelector('form');
|
|
149
|
-
const query = new URLSearchParams({ column: path, validate: '1' });
|
|
150
|
-
const response = await fetch(`${form.action}?${query}`, {
|
|
151
|
-
signal: AbortSignal.any([signal, this.abort.signal])
|
|
152
|
-
});
|
|
153
|
-
const result = await response.json();
|
|
154
|
-
if (!response.ok || result.path !== path)
|
|
155
|
-
throw new Error(result.error ?? 'Column unavailable');
|
|
156
|
-
}
|
|
157
199
|
cancel(id) {
|
|
158
200
|
if (this.drag?.intent.id !== id)
|
|
159
201
|
return;
|
|
@@ -163,24 +205,10 @@ export class ListPropertySelect {
|
|
|
163
205
|
drag.restore();
|
|
164
206
|
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
165
207
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.
|
|
169
|
-
|
|
170
|
-
return;
|
|
171
|
-
for (const row of this.article.querySelectorAll('tbody tr'))
|
|
172
|
-
row.cells[header.cellIndex].textContent = 'Unable to load values';
|
|
173
|
-
if (header.querySelector('.column-retry'))
|
|
174
|
-
return;
|
|
175
|
-
const retry = document.createElement('button');
|
|
176
|
-
retry.type = 'button';
|
|
177
|
-
retry.className = 'column-retry';
|
|
178
|
-
retry.textContent = 'Retry values';
|
|
179
|
-
retry.addEventListener('click', event => {
|
|
180
|
-
event.stopPropagation();
|
|
181
|
-
void this.options.addition.retry(path);
|
|
182
|
-
}, { signal: this.abort.signal });
|
|
183
|
-
header.append(retry);
|
|
208
|
+
displayedPaths() {
|
|
209
|
+
return [...this.article.querySelectorAll('thead [data-property]:not(.column-add-provisional)')]
|
|
210
|
+
.map(header => header.dataset.property)
|
|
211
|
+
.filter(path => !this.removals.has(path) || this.removals.get(path).signal.aborted);
|
|
184
212
|
}
|
|
185
213
|
dispose() {
|
|
186
214
|
if (this.disposed)
|
|
@@ -190,22 +218,30 @@ export class ListPropertySelect {
|
|
|
190
218
|
this.disposed = true;
|
|
191
219
|
this.abort.abort();
|
|
192
220
|
this.observer.disconnect();
|
|
221
|
+
this.unsubscribePane();
|
|
222
|
+
this.removedColumns.clear();
|
|
193
223
|
this.view.dispose();
|
|
194
224
|
this.pane.remove();
|
|
195
225
|
this.options.addition.dispose();
|
|
196
226
|
this.button.hidden = true;
|
|
197
227
|
this.pending.clear();
|
|
228
|
+
for (const removal of this.removals.values())
|
|
229
|
+
removal.abort();
|
|
230
|
+
this.removals.clear();
|
|
198
231
|
}
|
|
199
232
|
async end(id, sample) {
|
|
200
233
|
const drag = this.drag;
|
|
201
234
|
if (!drag || drag.intent.id !== id)
|
|
202
235
|
throw new Error('Unknown column transport');
|
|
203
236
|
try {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
237
|
+
let status = await drag.operation.finish(sample);
|
|
238
|
+
if (status !== 'cancelled') {
|
|
239
|
+
this.removedColumns.delete(drag.intent.path);
|
|
240
|
+
if (status === 'moved' && this.displayedPaths().join(',') !== drag.paths.join(','))
|
|
241
|
+
await this.options.saveColumns();
|
|
242
|
+
if (drag.added && status === 'moved')
|
|
243
|
+
status = 'added';
|
|
244
|
+
}
|
|
209
245
|
if (this.disposed)
|
|
210
246
|
return { ...drag.intent, status: 'cancelled' };
|
|
211
247
|
if (this.drag === drag)
|
|
@@ -234,26 +270,77 @@ export class ListPropertySelect {
|
|
|
234
270
|
this.drag.operation.move(sample);
|
|
235
271
|
}
|
|
236
272
|
paths() {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
273
|
+
return this.drag ? [...this.drag.paths] : this.displayedPaths();
|
|
274
|
+
}
|
|
275
|
+
async remove(path, signal) {
|
|
276
|
+
const positions = this.removedColumns;
|
|
277
|
+
const paths = this.paths();
|
|
278
|
+
const index = paths.indexOf(path);
|
|
279
|
+
const position = { left: paths[index - 1], right: paths[index + 1] };
|
|
280
|
+
const removal = new AbortController();
|
|
281
|
+
this.removals.set(path, removal);
|
|
282
|
+
// Publish the unchecked state before starting the table's removal flash.
|
|
283
|
+
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
284
|
+
try {
|
|
285
|
+
const animation = this.options.addition.remove(path, AbortSignal.any([signal, removal.signal]));
|
|
286
|
+
const saving = this.options.saveColumns(this.paths());
|
|
287
|
+
const [animationResult, saved] = await Promise.allSettled([animation, saving]);
|
|
288
|
+
if (animationResult.status === 'rejected')
|
|
289
|
+
throw animationResult.reason;
|
|
290
|
+
if (saved.status === 'rejected')
|
|
291
|
+
throw saved.reason;
|
|
292
|
+
const removed = animationResult.value;
|
|
293
|
+
if (!removed) {
|
|
294
|
+
if (removal.signal.aborted && !signal.aborted && !this.disposed) {
|
|
295
|
+
await this.options.saveColumns(this.paths());
|
|
296
|
+
return 'existing';
|
|
297
|
+
}
|
|
298
|
+
throw new Error('Column removal cancelled');
|
|
299
|
+
}
|
|
300
|
+
if (!this.disposed && positions === this.removedColumns && index >= 0)
|
|
301
|
+
positions.set(path, position);
|
|
302
|
+
return 'removed';
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
removal.abort();
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
if (this.removals.get(path) === removal)
|
|
310
|
+
this.removals.delete(path);
|
|
311
|
+
if (!this.disposed)
|
|
312
|
+
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
313
|
+
}
|
|
241
314
|
}
|
|
242
315
|
async select(intent, signal) {
|
|
243
316
|
const path = normalisePath(intent.path);
|
|
244
317
|
try {
|
|
245
|
-
if (this.drag)
|
|
246
|
-
throw new Error('A column
|
|
318
|
+
if (this.drag || this.batch)
|
|
319
|
+
throw new Error('A column operation is pending');
|
|
247
320
|
let status;
|
|
248
321
|
const pending = this.pending.get(path);
|
|
249
322
|
if (pending) {
|
|
250
|
-
|
|
323
|
+
const removal = this.removals.get(path);
|
|
324
|
+
if (removal && !removal.signal.aborted) {
|
|
325
|
+
removal.abort();
|
|
326
|
+
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
327
|
+
}
|
|
328
|
+
else if (removal && intent.action !== 'add') {
|
|
329
|
+
await pending;
|
|
330
|
+
return this.select(intent, signal);
|
|
331
|
+
}
|
|
332
|
+
const outcome = await pending;
|
|
333
|
+
if (removal && outcome === 'removed')
|
|
334
|
+
return this.select({ ...intent, action: 'add' }, signal);
|
|
251
335
|
status = 'existing';
|
|
252
336
|
}
|
|
253
|
-
else if (this.paths().includes(path))
|
|
254
|
-
status = 'existing';
|
|
255
337
|
else {
|
|
256
|
-
const
|
|
338
|
+
const existing = this.paths().includes(path);
|
|
339
|
+
const operation = existing && (intent.action !== 'add')
|
|
340
|
+
? this.remove(path, signal)
|
|
341
|
+
: existing
|
|
342
|
+
? Promise.resolve('existing')
|
|
343
|
+
: Promise.resolve(this.add(path, signal));
|
|
257
344
|
this.pending.set(path, operation);
|
|
258
345
|
try {
|
|
259
346
|
status = await operation;
|
|
@@ -266,12 +353,78 @@ export class ListPropertySelect {
|
|
|
266
353
|
signal.throwIfAborted();
|
|
267
354
|
if (this.disposed)
|
|
268
355
|
throw new Error('List disposed');
|
|
269
|
-
this.
|
|
270
|
-
|
|
356
|
+
if (status !== 'removed' && this.paths().includes(path)) {
|
|
357
|
+
this.options.addition.reveal(path);
|
|
358
|
+
if (status === 'existing')
|
|
359
|
+
this.options.addition.highlight(path);
|
|
360
|
+
}
|
|
271
361
|
return { ...intent, status };
|
|
272
362
|
}
|
|
273
363
|
catch (error) {
|
|
274
364
|
return { ...intent, message: error instanceof Error ? error.message : 'Column unavailable', status: 'rejected' };
|
|
275
365
|
}
|
|
276
366
|
}
|
|
367
|
+
async selectMany(intents, signal) {
|
|
368
|
+
if (!intents.length)
|
|
369
|
+
return [];
|
|
370
|
+
if (this.batch || this.drag || this.pending.size || this.disposed)
|
|
371
|
+
return intents.map(intent => ({ ...intent, status: 'rejected', message: 'A column operation is pending' }));
|
|
372
|
+
this.batch = true;
|
|
373
|
+
const previous = this.paths();
|
|
374
|
+
const added = intents.filter(intent => !previous.includes(intent.path)).map(intent => intent.path);
|
|
375
|
+
const removed = intents.filter(intent => previous.includes(intent.path) && intent.action !== 'add')
|
|
376
|
+
.map(intent => intent.path);
|
|
377
|
+
const paths = [...previous.filter(path => !removed.includes(path)), ...added];
|
|
378
|
+
const abort = new AbortController();
|
|
379
|
+
const combined = AbortSignal.any([signal, this.abort.signal, abort.signal]);
|
|
380
|
+
let receive;
|
|
381
|
+
let fail;
|
|
382
|
+
const content = new Promise((resolve, reject) => { receive = resolve; fail = reject; });
|
|
383
|
+
void content.catch(() => { });
|
|
384
|
+
try {
|
|
385
|
+
for (const path of removed)
|
|
386
|
+
this.removals.set(path, abort);
|
|
387
|
+
for (const path of added) {
|
|
388
|
+
let first = true;
|
|
389
|
+
this.add(path, combined, { effects: 'deferred' }, signal => {
|
|
390
|
+
if (!first)
|
|
391
|
+
return this.loadColumn(path, signal);
|
|
392
|
+
first = false;
|
|
393
|
+
return content.then(content => content.get(path));
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
397
|
+
if (added.length)
|
|
398
|
+
this.options.addition.reveal(added.at(-1));
|
|
399
|
+
this.options.addition.highlightMany(added, true);
|
|
400
|
+
const animation = removed.length ? this.options.addition.removeMany(removed, combined) : Promise.resolve(true);
|
|
401
|
+
const request = this.loadColumns(added, paths, combined).then(receive, error => { fail(error); throw error; });
|
|
402
|
+
const [animated, loaded] = await Promise.allSettled([animation, request]);
|
|
403
|
+
if (animated.status === 'rejected')
|
|
404
|
+
throw animated.reason;
|
|
405
|
+
if (loaded.status === 'rejected')
|
|
406
|
+
throw loaded.reason;
|
|
407
|
+
if (!animated.value)
|
|
408
|
+
throw new Error('Column removal cancelled');
|
|
409
|
+
for (const path of removed) {
|
|
410
|
+
const index = previous.indexOf(path);
|
|
411
|
+
this.removedColumns.set(path, { left: previous[index - 1], right: previous[index + 1] });
|
|
412
|
+
}
|
|
413
|
+
return intents.map(intent => ({ ...intent, status: added.includes(intent.path) ? 'added'
|
|
414
|
+
: removed.includes(intent.path) ? 'removed' : 'existing' }));
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
fail(error);
|
|
418
|
+
return intents.map(intent => ({ ...intent, status: 'rejected',
|
|
419
|
+
message: error instanceof Error ? error.message : 'Columns unavailable' }));
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
for (const path of removed)
|
|
423
|
+
if (this.removals.get(path) === abort)
|
|
424
|
+
this.removals.delete(path);
|
|
425
|
+
this.batch = false;
|
|
426
|
+
if (!this.disposed)
|
|
427
|
+
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
277
430
|
}
|