@vuu-ui/vuu-data-local 2.1.19-beta.1 → 2.1.19-beta.2
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/package.json +9 -10
- package/src/array-data-source/aggregate-utils.js +121 -0
- package/src/array-data-source/array-data-source.js +671 -0
- package/src/array-data-source/array-data-utils.js +41 -0
- package/src/array-data-source/group-utils.js +138 -0
- package/src/array-data-source/sort-utils.js +56 -0
- package/src/index.js +3 -0
- package/src/json-data-source/JsonDataSource.js +319 -0
- package/src/tree-data-source/IconProvider.js +11 -0
- package/src/tree-data-source/TreeDataSource.js +353 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
import { filterPredicate, parseFilter } from "@vuu-ui/vuu-filter-parser";
|
|
2
|
+
import { EventEmitter, KeySet, NULL_RANGE, Range, buildColumnMap, combineFilters, filterAsQuery, getAddedItems, hasBaseFilter, hasFilter, hasGroupBy, hasSort, isConfigChanged, isGroupByChanged, logger, metadataKeys, rangeNewItems, uuid, vanillaConfig, withConfigDefaults } from "@vuu-ui/vuu-utils";
|
|
3
|
+
import { aggregateData } from "./aggregate-utils.js";
|
|
4
|
+
import { buildDataToClientMap, toClientRow } from "./array-data-utils.js";
|
|
5
|
+
import { collapseGroup, expandGroup, groupRows } from "./group-utils.js";
|
|
6
|
+
import { binarySearch, sortComparator, sortRows } from "./sort-utils.js";
|
|
7
|
+
const { debug: debug, info: info } = logger("ArrayDataSource");
|
|
8
|
+
const { KEY: KEY } = metadataKeys;
|
|
9
|
+
const toDataSourceRow = (indexOfKeyColumn, index)=>(data, idx)=>{
|
|
10
|
+
const key = `${data[indexOfKeyColumn]}`;
|
|
11
|
+
index?.set(key, idx);
|
|
12
|
+
return [
|
|
13
|
+
idx,
|
|
14
|
+
idx,
|
|
15
|
+
true,
|
|
16
|
+
false,
|
|
17
|
+
1,
|
|
18
|
+
0,
|
|
19
|
+
key,
|
|
20
|
+
0,
|
|
21
|
+
0,
|
|
22
|
+
false,
|
|
23
|
+
...data
|
|
24
|
+
];
|
|
25
|
+
};
|
|
26
|
+
const buildTableSchema = (columns, keyColumn)=>{
|
|
27
|
+
const schema = {
|
|
28
|
+
columns: columns.map(({ name, serverDataType = "string" })=>({
|
|
29
|
+
name,
|
|
30
|
+
serverDataType
|
|
31
|
+
})),
|
|
32
|
+
key: keyColumn ?? columns[0].name,
|
|
33
|
+
table: {
|
|
34
|
+
module: "",
|
|
35
|
+
table: "Array"
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return schema;
|
|
39
|
+
};
|
|
40
|
+
class ArrayDataSource extends EventEmitter {
|
|
41
|
+
clientCallback;
|
|
42
|
+
columnDescriptors;
|
|
43
|
+
dataIndices;
|
|
44
|
+
dataMap;
|
|
45
|
+
groupMap;
|
|
46
|
+
key;
|
|
47
|
+
lastRangeServed = {
|
|
48
|
+
from: 0,
|
|
49
|
+
to: 0
|
|
50
|
+
};
|
|
51
|
+
rangeChangeRowset;
|
|
52
|
+
openTreeNodes = [];
|
|
53
|
+
preserveScrollPositionAcrossConfigChange = false;
|
|
54
|
+
#columnMap;
|
|
55
|
+
_config = vanillaConfig;
|
|
56
|
+
#data;
|
|
57
|
+
#freezeTimestamp = void 0;
|
|
58
|
+
#keys = new KeySet(NULL_RANGE);
|
|
59
|
+
#links;
|
|
60
|
+
#range = Range(0, 0);
|
|
61
|
+
#status = "initialising";
|
|
62
|
+
#title;
|
|
63
|
+
_menu;
|
|
64
|
+
selectedRows = new Set();
|
|
65
|
+
index = new Map();
|
|
66
|
+
tableSchema;
|
|
67
|
+
viewport;
|
|
68
|
+
processedData = void 0;
|
|
69
|
+
constructor({ aggregations, baseFilterSpec, columnDescriptors, data, dataMap, filterSpec, groupBy, keyColumn, rangeChangeRowset = "delta", sort, title, viewport }){
|
|
70
|
+
super();
|
|
71
|
+
if (!data || !columnDescriptors) throw Error("ArrayDataSource constructor called without data or without columnDescriptors");
|
|
72
|
+
this.columnDescriptors = columnDescriptors;
|
|
73
|
+
this.dataMap = dataMap;
|
|
74
|
+
this.key = keyColumn ? this.columnDescriptors.findIndex((col)=>col.name === keyColumn) : 0;
|
|
75
|
+
this.rangeChangeRowset = rangeChangeRowset;
|
|
76
|
+
this.tableSchema = buildTableSchema(columnDescriptors, keyColumn);
|
|
77
|
+
this.viewport = viewport || uuid();
|
|
78
|
+
this.#title = title;
|
|
79
|
+
const columns = columnDescriptors.map((col)=>col.name);
|
|
80
|
+
this.#columnMap = buildColumnMap(columns);
|
|
81
|
+
this.dataIndices = buildDataToClientMap(this.#columnMap, this.dataMap);
|
|
82
|
+
this.#data = data.map(toDataSourceRow(this.key, this.index));
|
|
83
|
+
this.config = {
|
|
84
|
+
...this._config,
|
|
85
|
+
aggregations: aggregations || this._config.aggregations,
|
|
86
|
+
baseFilterSpec,
|
|
87
|
+
columns,
|
|
88
|
+
filterSpec: filterSpec || this._config.filterSpec,
|
|
89
|
+
groupBy: groupBy || this._config.groupBy,
|
|
90
|
+
sort: sort || this._config.sort
|
|
91
|
+
};
|
|
92
|
+
debug?.(`columnMap: ${JSON.stringify(this.#columnMap)}`);
|
|
93
|
+
}
|
|
94
|
+
async subscribe({ viewport = this.viewport ?? (this.viewport = uuid()), columns, aggregations, baseFilterSpec, range, sort, groupBy, filterSpec }, callback) {
|
|
95
|
+
this.clientCallback = callback;
|
|
96
|
+
this.viewport = viewport;
|
|
97
|
+
this.#status = "subscribed";
|
|
98
|
+
this.lastRangeServed = {
|
|
99
|
+
from: 0,
|
|
100
|
+
to: 0
|
|
101
|
+
};
|
|
102
|
+
let config = this._config;
|
|
103
|
+
const hasConfigProps = aggregations || columns || filterSpec || groupBy || sort;
|
|
104
|
+
if (hasConfigProps) config = {
|
|
105
|
+
...config,
|
|
106
|
+
aggregations: aggregations || this._config.aggregations,
|
|
107
|
+
baseFilterSpec: baseFilterSpec || this._config.baseFilterSpec,
|
|
108
|
+
columns: columns || this._config.columns,
|
|
109
|
+
filterSpec: filterSpec || this._config.filterSpec,
|
|
110
|
+
groupBy: groupBy || this._config.groupBy,
|
|
111
|
+
sort: sort || this._config.sort
|
|
112
|
+
};
|
|
113
|
+
const subscribedMessage = {
|
|
114
|
+
...config,
|
|
115
|
+
type: "subscribed",
|
|
116
|
+
clientViewportId: this.viewport,
|
|
117
|
+
range: this.#range,
|
|
118
|
+
tableSchema: this.tableSchema
|
|
119
|
+
};
|
|
120
|
+
this.clientCallback?.(subscribedMessage);
|
|
121
|
+
this.emit("subscribed", subscribedMessage);
|
|
122
|
+
if (hasConfigProps) this.config = config;
|
|
123
|
+
else {
|
|
124
|
+
this.sendSizeUpdateToClient();
|
|
125
|
+
this.emit("resize", this.processedData ? this.processedData.length : this.#data.length);
|
|
126
|
+
if (range && !this.#range.equals(range)) this.range = range;
|
|
127
|
+
else if (this.#range !== NULL_RANGE) this.sendRowsToClient();
|
|
128
|
+
if (0 !== this.range.to) {
|
|
129
|
+
const pageCount = Math.ceil(this.size / (this.range.to - this.range.from));
|
|
130
|
+
this.emit("page-count", pageCount);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
unsubscribe() {
|
|
135
|
+
this.#status = "unsubscribed";
|
|
136
|
+
this.emit("unsubscribed", this.viewport);
|
|
137
|
+
this.removeAllListeners();
|
|
138
|
+
this.clientCallback = void 0;
|
|
139
|
+
}
|
|
140
|
+
suspend() {
|
|
141
|
+
console.log("[ArrayDataSource] suspend");
|
|
142
|
+
if ("unsubscribed" !== this.#status) {
|
|
143
|
+
info?.(`suspend #${this.viewport}, current status ${this.#status}`);
|
|
144
|
+
this.#status = "suspended";
|
|
145
|
+
this.emit("suspended", this.viewport);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
resume(callback) {
|
|
149
|
+
const isSuspended = "suspended" === this.#status;
|
|
150
|
+
info?.(`resume #${this.viewport}, current status ${this.#status}`);
|
|
151
|
+
if (callback) this.clientCallback = callback;
|
|
152
|
+
if (isSuspended) this.#status = "subscribed";
|
|
153
|
+
this.emit("resumed", this.viewport);
|
|
154
|
+
if (this.selectedRows.size > 0) if (this.selectedRows.has("*")) this.emit("row-selection", this.size);
|
|
155
|
+
else this.emit("row-selection", this.selectedRows.size);
|
|
156
|
+
this.sendRowsToClient(true);
|
|
157
|
+
}
|
|
158
|
+
disable() {
|
|
159
|
+
this.emit("disabled", this.viewport);
|
|
160
|
+
}
|
|
161
|
+
enable() {
|
|
162
|
+
this.emit("enabled", this.viewport);
|
|
163
|
+
}
|
|
164
|
+
select(selectRequest) {
|
|
165
|
+
switch(selectRequest.type){
|
|
166
|
+
case "SELECT_ROW":
|
|
167
|
+
{
|
|
168
|
+
const { preserveExistingSelection, rowKey } = selectRequest;
|
|
169
|
+
if (!preserveExistingSelection) this.selectedRows.clear();
|
|
170
|
+
this.selectedRows.add(rowKey);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case "DESELECT_ROW":
|
|
174
|
+
{
|
|
175
|
+
const { preserveExistingSelection, rowKey } = selectRequest;
|
|
176
|
+
if (preserveExistingSelection) if (this.selectedRows.has("*")) {
|
|
177
|
+
this.selectedRows.clear();
|
|
178
|
+
for (const key of this.index.keys())if (key !== rowKey) this.selectedRows.add(key);
|
|
179
|
+
} else this.selectedRows.delete(rowKey);
|
|
180
|
+
else this.selectedRows.clear();
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case "SELECT_ROW_RANGE":
|
|
184
|
+
{
|
|
185
|
+
const { preserveExistingSelection, fromRowKey, toRowKey } = selectRequest;
|
|
186
|
+
if (!preserveExistingSelection) this.selectedRows.clear();
|
|
187
|
+
const fromIdx = this.index.get(fromRowKey);
|
|
188
|
+
const toIdx = this.index.get(toRowKey);
|
|
189
|
+
if ("number" == typeof fromIdx && "number" == typeof toIdx) for(let i = fromIdx; i <= toIdx; i++){
|
|
190
|
+
const { [KEY]: rowKey } = this.#data[i];
|
|
191
|
+
this.selectedRows.add(rowKey);
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case "SELECT_ALL":
|
|
196
|
+
this.selectedRows.clear();
|
|
197
|
+
this.selectedRows.add("*");
|
|
198
|
+
break;
|
|
199
|
+
case "DESELECT_ALL":
|
|
200
|
+
this.selectedRows.clear();
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
this.setRange(this.#range, true);
|
|
204
|
+
this.emit("row-selection", "SELECT_ALL" === selectRequest.type ? this.size : this.selectedRows.size);
|
|
205
|
+
}
|
|
206
|
+
getRowKey(keyOrIndex) {
|
|
207
|
+
if ("string" == typeof keyOrIndex) return keyOrIndex;
|
|
208
|
+
const row = this.getRowAtIndex(keyOrIndex);
|
|
209
|
+
if (void 0 === row) throw Error(`row not found at index ${keyOrIndex}`);
|
|
210
|
+
return row?.[KEY];
|
|
211
|
+
}
|
|
212
|
+
openTreeNode(keyOrIndex) {
|
|
213
|
+
const key = this.getRowKey(keyOrIndex);
|
|
214
|
+
this.openTreeNodes.push(key);
|
|
215
|
+
this.processedData = expandGroup(this.openTreeNodes, this.#data, this._config.groupBy, this.#columnMap, this.groupMap, this.processedData);
|
|
216
|
+
this.setRange(this.#range.reset, true);
|
|
217
|
+
}
|
|
218
|
+
closeTreeNode(keyOrIndex) {
|
|
219
|
+
const key = this.getRowKey(keyOrIndex);
|
|
220
|
+
this.openTreeNodes = this.openTreeNodes.filter((value)=>value !== key);
|
|
221
|
+
if (this.processedData) {
|
|
222
|
+
this.processedData = collapseGroup(key, this.processedData);
|
|
223
|
+
this.setRange(this.#range.reset, true);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
get pageSize() {
|
|
227
|
+
return this.#range.to - this.#range.from;
|
|
228
|
+
}
|
|
229
|
+
get links() {
|
|
230
|
+
return this.#links;
|
|
231
|
+
}
|
|
232
|
+
set links(links) {
|
|
233
|
+
this.#links = links;
|
|
234
|
+
if (links) this._clientCallback?.({
|
|
235
|
+
clientViewportId: this.viewport,
|
|
236
|
+
type: "vuu-links",
|
|
237
|
+
links
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
get menu() {
|
|
241
|
+
return this._menu;
|
|
242
|
+
}
|
|
243
|
+
get status() {
|
|
244
|
+
return this.#status;
|
|
245
|
+
}
|
|
246
|
+
get data() {
|
|
247
|
+
return this.#data;
|
|
248
|
+
}
|
|
249
|
+
get currentData() {
|
|
250
|
+
return this.processedData ?? this.#data;
|
|
251
|
+
}
|
|
252
|
+
get table() {
|
|
253
|
+
return this.tableSchema.table;
|
|
254
|
+
}
|
|
255
|
+
get columns() {
|
|
256
|
+
return this._config.columns;
|
|
257
|
+
}
|
|
258
|
+
set columns(columns) {}
|
|
259
|
+
get config() {
|
|
260
|
+
return this._config;
|
|
261
|
+
}
|
|
262
|
+
set config(config) {
|
|
263
|
+
const originalConfig = this._config;
|
|
264
|
+
const configChanges = this.applyConfig(config);
|
|
265
|
+
if (configChanges) {
|
|
266
|
+
if (config) {
|
|
267
|
+
const newConfig = config?.filterSpec?.filter && config?.filterSpec.filterStruct === void 0 ? {
|
|
268
|
+
...config,
|
|
269
|
+
filterSpec: {
|
|
270
|
+
filter: config.filterSpec.filter,
|
|
271
|
+
filterStruct: parseFilter(config.filterSpec.filter)
|
|
272
|
+
}
|
|
273
|
+
} : config;
|
|
274
|
+
this._config = withConfigDefaults(newConfig);
|
|
275
|
+
let processedData;
|
|
276
|
+
if (hasFilter(config) || hasBaseFilter(config)) {
|
|
277
|
+
const fn = this.getFilterPredicate();
|
|
278
|
+
processedData = this.#data.filter(fn);
|
|
279
|
+
}
|
|
280
|
+
if (configChanges.columnsChanged) this.processNewColumns(originalConfig.columns, config.columns);
|
|
281
|
+
if (hasSort(config)) processedData = sortRows(processedData ?? this.#data, config.sort, this.#columnMap);
|
|
282
|
+
if (this.openTreeNodes.length > 0 && isGroupByChanged(originalConfig, config)) {
|
|
283
|
+
if (0 === this._config.groupBy.length) this.openTreeNodes.length = 0;
|
|
284
|
+
}
|
|
285
|
+
if (hasGroupBy(config)) {
|
|
286
|
+
const [groupedData, groupMap] = groupRows(processedData ?? this.#data, config.groupBy, this.#columnMap);
|
|
287
|
+
this.groupMap = groupMap;
|
|
288
|
+
processedData = groupedData;
|
|
289
|
+
if (this.openTreeNodes.length > 0) processedData = expandGroup(this.openTreeNodes, this.#data, this._config.groupBy, this.#columnMap, this.groupMap, processedData);
|
|
290
|
+
}
|
|
291
|
+
if (processedData) this.processedData = this.indexProcessedData(processedData);
|
|
292
|
+
else this.processedData = void 0;
|
|
293
|
+
}
|
|
294
|
+
if (configChanges.filterChanged || configChanges.baseFilterChanged || configChanges.groupByChanged) requestAnimationFrame(()=>{
|
|
295
|
+
this.emit("resize", this.size);
|
|
296
|
+
});
|
|
297
|
+
if ("subscribed" === this.#status) requestAnimationFrame(()=>{
|
|
298
|
+
this.sendSizeUpdateToClient();
|
|
299
|
+
if (this.preserveScrollPositionAcrossConfigChange) this.preserveScrollPositionAcrossConfigChange = false;
|
|
300
|
+
else this.setRange(this.#range.reset, true);
|
|
301
|
+
this.emit("config", this._config, this.range, void 0, configChanges);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
processNewColumns(originalColumns, columns) {
|
|
306
|
+
const addedColumns = getAddedItems(originalColumns, columns);
|
|
307
|
+
addedColumns.length;
|
|
308
|
+
this.#columnMap = buildColumnMap(columns);
|
|
309
|
+
this.dataIndices = buildDataToClientMap(this.#columnMap, this.dataMap);
|
|
310
|
+
}
|
|
311
|
+
indexProcessedData(data) {
|
|
312
|
+
return data?.map((row, i)=>{
|
|
313
|
+
const dolly = row.slice();
|
|
314
|
+
dolly[0] = i;
|
|
315
|
+
dolly[1] = i;
|
|
316
|
+
return dolly;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
getFilterPredicate() {
|
|
320
|
+
const { filterSpec: { filterStruct } } = combineFilters(this._config);
|
|
321
|
+
if (filterStruct) return filterPredicate(this.#columnMap, filterStruct);
|
|
322
|
+
throw Error("filter must include filterStruct");
|
|
323
|
+
}
|
|
324
|
+
applyConfig(config, preserveExistingConfigAttributes = false) {
|
|
325
|
+
const { noChanges, ...otherChanges } = isConfigChanged(this._config, config);
|
|
326
|
+
if (true !== noChanges) {
|
|
327
|
+
if (config) {
|
|
328
|
+
const newConfig = config?.filterSpec?.filter && config?.filterSpec.filterStruct === void 0 ? {
|
|
329
|
+
...config,
|
|
330
|
+
filterSpec: {
|
|
331
|
+
filter: config.filterSpec.filter,
|
|
332
|
+
filterStruct: parseFilter(config.filterSpec.filter)
|
|
333
|
+
}
|
|
334
|
+
} : config;
|
|
335
|
+
if (preserveExistingConfigAttributes) this._config = {
|
|
336
|
+
...this._config,
|
|
337
|
+
...config
|
|
338
|
+
};
|
|
339
|
+
else this._config = withConfigDefaults(newConfig);
|
|
340
|
+
return otherChanges;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
get columnMap() {
|
|
345
|
+
return this.#columnMap;
|
|
346
|
+
}
|
|
347
|
+
get selectedRowsCount() {
|
|
348
|
+
return this.selectedRows.size;
|
|
349
|
+
}
|
|
350
|
+
get size() {
|
|
351
|
+
return this.processedData?.length ?? this.#data.length;
|
|
352
|
+
}
|
|
353
|
+
get range() {
|
|
354
|
+
return this.#range;
|
|
355
|
+
}
|
|
356
|
+
set range(range) {
|
|
357
|
+
this.setRange(range);
|
|
358
|
+
}
|
|
359
|
+
delete(row) {
|
|
360
|
+
console.log(`delete row ${row.join(",")}`);
|
|
361
|
+
}
|
|
362
|
+
insert = (row)=>{
|
|
363
|
+
const dataSourceRow = toDataSourceRow(this.key)(row, this.size);
|
|
364
|
+
this.#data.push(dataSourceRow);
|
|
365
|
+
const { from, to } = this.#range;
|
|
366
|
+
const [rowIdx] = dataSourceRow;
|
|
367
|
+
const isSorted = hasSort(this.config);
|
|
368
|
+
const isFiltered = hasFilter(this.config) || hasBaseFilter(this.config);
|
|
369
|
+
if (isSorted && isFiltered) {
|
|
370
|
+
const meetsFilterCriteria = this.getFilterPredicate();
|
|
371
|
+
if (meetsFilterCriteria(dataSourceRow)) this.insertIntoSortedData(dataSourceRow);
|
|
372
|
+
} else if (isSorted) this.insertIntoSortedData(dataSourceRow);
|
|
373
|
+
else if (isFiltered) {
|
|
374
|
+
const meetsFilterCriteria = this.getFilterPredicate();
|
|
375
|
+
if (meetsFilterCriteria(dataSourceRow)) {
|
|
376
|
+
this.processedData?.push(dataSourceRow);
|
|
377
|
+
this.sendSizeUpdateToClient();
|
|
378
|
+
if (rowIdx >= from && rowIdx < to) this.sendRowsToClient();
|
|
379
|
+
this.emit("resize", this.#data.length);
|
|
380
|
+
}
|
|
381
|
+
} else {
|
|
382
|
+
this.sendSizeUpdateToClient();
|
|
383
|
+
if (rowIdx >= from && rowIdx < to) this.sendRowsToClient();
|
|
384
|
+
this.emit("resize", this.size);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
insertIntoSortedData(row) {
|
|
388
|
+
const indexedSortDefs = this.config.sort.sortDefs.map(({ column, sortType })=>[
|
|
389
|
+
this.columnMap[column],
|
|
390
|
+
sortType
|
|
391
|
+
]);
|
|
392
|
+
const comparator = sortComparator(indexedSortDefs);
|
|
393
|
+
const insertPos = binarySearch(this.processedData, row, comparator);
|
|
394
|
+
this.sendSizeUpdateToClient();
|
|
395
|
+
if (-1 === insertPos) {
|
|
396
|
+
this.processedData?.unshift(row);
|
|
397
|
+
if (this.processedData) this.processedData = this.indexProcessedData(this.processedData);
|
|
398
|
+
if (insertPos <= this.#range.to) this.sendRowsToClient(true);
|
|
399
|
+
if (this.processedData) this.emit("resize", this.processedData.length);
|
|
400
|
+
} else if (this.processedData) this.emit("resize", this.processedData.length);
|
|
401
|
+
}
|
|
402
|
+
validateDataValue(columnName, value) {
|
|
403
|
+
const columnDescriptor = this.columnDescriptors.find((col)=>col.name === columnName);
|
|
404
|
+
if (columnDescriptor) switch(columnDescriptor.serverDataType){
|
|
405
|
+
case "int":
|
|
406
|
+
if ("number" == typeof value) {
|
|
407
|
+
if (Math.floor(value) !== value) throw Error(`${columnName} is int but value = ${value}`);
|
|
408
|
+
} else if ("string" == typeof value) {
|
|
409
|
+
const numericValue = parseFloat(value);
|
|
410
|
+
if (Math.floor(numericValue) !== numericValue) throw Error(`${columnName} is ${value} is not a valid integer`);
|
|
411
|
+
}
|
|
412
|
+
break;
|
|
413
|
+
default:
|
|
414
|
+
}
|
|
415
|
+
else throw Error(`Unknown column ${columnName}`);
|
|
416
|
+
}
|
|
417
|
+
updateDataItem = (keyValue, columnName, value)=>{
|
|
418
|
+
this.validateDataValue(columnName, value);
|
|
419
|
+
const colIndex = this.#columnMap[columnName];
|
|
420
|
+
const dataColIndex = this.dataMap?.[columnName];
|
|
421
|
+
const dataIndex = this.indexOfRowWithKey(keyValue);
|
|
422
|
+
if (-1 !== dataIndex && void 0 !== dataColIndex) {
|
|
423
|
+
const dataSourceRow = this.#data[dataIndex];
|
|
424
|
+
dataSourceRow[colIndex] = value;
|
|
425
|
+
const { from, to } = this.#range;
|
|
426
|
+
const [rowIdx] = dataSourceRow;
|
|
427
|
+
if (rowIdx >= from && rowIdx < to) this.sendRowsToClient(false, dataSourceRow);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
getRowByKey(key) {
|
|
431
|
+
const data = this.processedData ?? this.#data;
|
|
432
|
+
return data.find((row)=>row[KEY] === key);
|
|
433
|
+
}
|
|
434
|
+
getRowAtIndex(rowIndex) {
|
|
435
|
+
return (this.processedData ?? this.#data)[rowIndex];
|
|
436
|
+
}
|
|
437
|
+
indexOfRowWithKey = (key)=>this.#data.findIndex((row)=>row[KEY] === key);
|
|
438
|
+
update = (row, columnName)=>{
|
|
439
|
+
const keyValue = row[this.key];
|
|
440
|
+
const dataColIndex = this.dataMap?.[columnName];
|
|
441
|
+
return this.updateDataItem(keyValue, columnName, row[dataColIndex]);
|
|
442
|
+
};
|
|
443
|
+
updateRow = (row, _columnName)=>{
|
|
444
|
+
const keyValue = row[this.key];
|
|
445
|
+
const dataIndex = this.#data.findIndex((row)=>row[KEY] === keyValue);
|
|
446
|
+
if (-1 !== dataIndex) {
|
|
447
|
+
const dataSourceRow = toDataSourceRow(this.key)(row, dataIndex);
|
|
448
|
+
this.#data[dataIndex] = dataSourceRow;
|
|
449
|
+
const { from, to } = this.#range;
|
|
450
|
+
const isFiltered = hasFilter(this.config) || hasBaseFilter(this.config);
|
|
451
|
+
const isSorted = hasSort(this.config);
|
|
452
|
+
if (isFiltered) {
|
|
453
|
+
const meetsFilterCriteria = this.getFilterPredicate();
|
|
454
|
+
if (meetsFilterCriteria(dataSourceRow) && this.processedData) {
|
|
455
|
+
const dataIndex = this.processedData.findIndex((row)=>row[KEY] === keyValue);
|
|
456
|
+
if (-1 !== dataIndex) {
|
|
457
|
+
const existingRow = this.processedData[dataIndex];
|
|
458
|
+
const newFilteredRow = existingRow.slice(0, 10).concat(dataSourceRow.slice(10));
|
|
459
|
+
this.processedData[dataIndex] = newFilteredRow;
|
|
460
|
+
if (dataIndex >= from && dataIndex < to) this.sendRowsToClient(false, newFilteredRow);
|
|
461
|
+
}
|
|
462
|
+
} else if (this.processedData) {
|
|
463
|
+
const dataIndex = this.processedData.findIndex((row)=>row[KEY] === keyValue);
|
|
464
|
+
if (-1 !== dataIndex) console.log("LAMF dataRow no longer in filter set");
|
|
465
|
+
}
|
|
466
|
+
} else if (isSorted) {
|
|
467
|
+
if (this.processedData) {
|
|
468
|
+
const dataIndex = this.processedData.findIndex((row)=>row[KEY] === keyValue);
|
|
469
|
+
if (-1 !== dataIndex) {
|
|
470
|
+
const existingRow = this.processedData[dataIndex];
|
|
471
|
+
const newSortedRow = existingRow.slice(0, 10).concat(dataSourceRow.slice(10));
|
|
472
|
+
this.processedData[dataIndex] = newSortedRow;
|
|
473
|
+
if (dataIndex >= from && dataIndex < to) this.sendRowsToClient(false, newSortedRow);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} else if (dataIndex >= from && dataIndex < to) this.sendRowsToClient(false, dataSourceRow);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
deleteRow = async (key)=>{
|
|
480
|
+
const dataIndex = this.#data.findIndex((row)=>row[KEY] === key);
|
|
481
|
+
let doomedIndex;
|
|
482
|
+
if (-1 === dataIndex) return "row not found";
|
|
483
|
+
{
|
|
484
|
+
if (this.processedData) {
|
|
485
|
+
for(let i = 0; i < this.processedData.length; i++)if (this.processedData[i][KEY] === key) doomedIndex = i;
|
|
486
|
+
else if (void 0 !== doomedIndex) this.processedData[i][0] -= 1;
|
|
487
|
+
if (void 0 !== doomedIndex) this.processedData.splice(doomedIndex, 1);
|
|
488
|
+
}
|
|
489
|
+
this.#data.splice(dataIndex, 1);
|
|
490
|
+
for(let i = dataIndex; i < this.#data.length; i++)this.#data[i][0] -= 1;
|
|
491
|
+
this.sendSizeUpdateToClient();
|
|
492
|
+
const { from, to } = this.#range;
|
|
493
|
+
const deletedIndex = doomedIndex ?? dataIndex;
|
|
494
|
+
if (deletedIndex >= from && deletedIndex < to) {
|
|
495
|
+
this.#keys.reset(this.range.withBuffer);
|
|
496
|
+
this.sendRowsToClient(true);
|
|
497
|
+
}
|
|
498
|
+
this.emit("resize", this.size);
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
setRange(range, forceFullRefresh = false) {
|
|
503
|
+
if (range.from !== this.#range.from || range.to !== this.#range.to) {
|
|
504
|
+
const currentPageCount = Math.ceil(this.size / (this.#range.to - this.#range.from));
|
|
505
|
+
const newPageCount = Math.ceil(this.size / (range.to - range.from));
|
|
506
|
+
this.#range = range;
|
|
507
|
+
const keysResequenced = this.#keys.reset(range.withBuffer);
|
|
508
|
+
this.sendRowsToClient(forceFullRefresh || keysResequenced);
|
|
509
|
+
requestAnimationFrame(()=>{
|
|
510
|
+
if (newPageCount !== currentPageCount) this.emit("page-count", newPageCount);
|
|
511
|
+
this.emit("range", range);
|
|
512
|
+
});
|
|
513
|
+
} else if (forceFullRefresh) this.sendRowsToClient(forceFullRefresh);
|
|
514
|
+
}
|
|
515
|
+
sendSizeUpdateToClient() {
|
|
516
|
+
this.clientCallback?.({
|
|
517
|
+
clientViewportId: this.viewport,
|
|
518
|
+
mode: "size-only",
|
|
519
|
+
type: "viewport-update",
|
|
520
|
+
size: this.processedData ? this.processedData.length : this.#data.length
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
sendRowsToClient(forceFullRefresh = false, row) {
|
|
524
|
+
if (row) this.clientCallback?.({
|
|
525
|
+
clientViewportId: this.viewport,
|
|
526
|
+
mode: "update",
|
|
527
|
+
rows: [
|
|
528
|
+
toClientRow(row, this.#keys, this.selectedRows, this.dataIndices)
|
|
529
|
+
],
|
|
530
|
+
type: "viewport-update"
|
|
531
|
+
});
|
|
532
|
+
else {
|
|
533
|
+
const rowRange = "delta" !== this.rangeChangeRowset || forceFullRefresh ? this.#range.withBuffer : rangeNewItems(this.lastRangeServed, this.#range.withBuffer);
|
|
534
|
+
const data = this.processedData ?? this.#data;
|
|
535
|
+
const rowsWithinViewport = data.slice(rowRange.from, rowRange.to).map((row)=>toClientRow(row, this.#keys, this.selectedRows, this.dataIndices));
|
|
536
|
+
this.clientCallback?.({
|
|
537
|
+
clientViewportId: this.viewport,
|
|
538
|
+
mode: "batch",
|
|
539
|
+
range: this.#range,
|
|
540
|
+
rows: rowsWithinViewport,
|
|
541
|
+
size: data.length,
|
|
542
|
+
type: "viewport-update"
|
|
543
|
+
});
|
|
544
|
+
this.lastRangeServed = {
|
|
545
|
+
from: this.#range.from,
|
|
546
|
+
to: Math.min(this.#range.to, this.#range.from + rowsWithinViewport.length)
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
get aggregations() {
|
|
551
|
+
return this._config.aggregations;
|
|
552
|
+
}
|
|
553
|
+
set aggregations(aggregations) {
|
|
554
|
+
this._config = {
|
|
555
|
+
...this._config,
|
|
556
|
+
aggregations
|
|
557
|
+
};
|
|
558
|
+
const targetData = this.processedData ?? this.#data;
|
|
559
|
+
const leafData = this.#data;
|
|
560
|
+
aggregateData(aggregations, targetData, this._config.groupBy, leafData, this.#columnMap, this.groupMap);
|
|
561
|
+
this.setRange(this.#range.reset, true);
|
|
562
|
+
this.emit("config", this._config, this.range);
|
|
563
|
+
}
|
|
564
|
+
get sort() {
|
|
565
|
+
return this._config.sort;
|
|
566
|
+
}
|
|
567
|
+
set sort(sort) {
|
|
568
|
+
debug?.(`sort ${JSON.stringify(sort)}`);
|
|
569
|
+
this.config = {
|
|
570
|
+
...this._config,
|
|
571
|
+
sort
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
get baseFilter() {
|
|
575
|
+
return this._config.baseFilterSpec;
|
|
576
|
+
}
|
|
577
|
+
set baseFilter(baseFilter) {
|
|
578
|
+
debug?.(`baseFilter ${JSON.stringify(baseFilter)}`);
|
|
579
|
+
this.config = {
|
|
580
|
+
...this._config,
|
|
581
|
+
baseFilterSpec: baseFilter
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
get filter() {
|
|
585
|
+
return this._config.filterSpec;
|
|
586
|
+
}
|
|
587
|
+
set filter(filter) {
|
|
588
|
+
debug?.(`filter ${JSON.stringify(filter)}`);
|
|
589
|
+
this.config = {
|
|
590
|
+
...this._config,
|
|
591
|
+
filterSpec: filter
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
setFilter(filter) {
|
|
595
|
+
const dataSourceFilter = {
|
|
596
|
+
filter: filterAsQuery(filter),
|
|
597
|
+
filterStruct: filter
|
|
598
|
+
};
|
|
599
|
+
this.filter = dataSourceFilter;
|
|
600
|
+
}
|
|
601
|
+
clearFilter() {
|
|
602
|
+
this.filter = {
|
|
603
|
+
filter: ""
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
get groupBy() {
|
|
607
|
+
return this._config.groupBy;
|
|
608
|
+
}
|
|
609
|
+
set groupBy(groupBy) {
|
|
610
|
+
this.config = {
|
|
611
|
+
...this._config,
|
|
612
|
+
groupBy
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
get title() {
|
|
616
|
+
return this.#title ?? `${this.table.module} ${this.table.table}`;
|
|
617
|
+
}
|
|
618
|
+
set title(title) {
|
|
619
|
+
this.#title = title;
|
|
620
|
+
this.emit("title-changed", this.viewport, title);
|
|
621
|
+
}
|
|
622
|
+
get _clientCallback() {
|
|
623
|
+
return this.clientCallback;
|
|
624
|
+
}
|
|
625
|
+
createLink({ parentVpId, link: { fromColumn, toColumn } }) {
|
|
626
|
+
console.log("create link", {
|
|
627
|
+
parentVpId,
|
|
628
|
+
fromColumn,
|
|
629
|
+
toColumn
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
removeLink() {
|
|
633
|
+
console.log("remove link");
|
|
634
|
+
}
|
|
635
|
+
async remoteProcedureCall() {
|
|
636
|
+
return Promise.reject();
|
|
637
|
+
}
|
|
638
|
+
async menuRpcCall(rpcRequest) {
|
|
639
|
+
console.log({
|
|
640
|
+
rpcRequest
|
|
641
|
+
});
|
|
642
|
+
return new Promise(()=>{});
|
|
643
|
+
}
|
|
644
|
+
freeze() {
|
|
645
|
+
if (this.isFrozen) throw Error("[BaseDataSource] cannot freeze, dataSource is already frozen");
|
|
646
|
+
this.#freezeTimestamp = new Date().getTime();
|
|
647
|
+
this.emit("freeze", true, this.#freezeTimestamp);
|
|
648
|
+
this.preserveScrollPositionAcrossConfigChange = true;
|
|
649
|
+
this.baseFilter = {
|
|
650
|
+
filter: `vuuCreatedTimestamp < ${this.#freezeTimestamp}`
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
unfreeze() {
|
|
654
|
+
if (this.isFrozen) {
|
|
655
|
+
const freezeTimestamp = this.#freezeTimestamp;
|
|
656
|
+
this.#freezeTimestamp = void 0;
|
|
657
|
+
this.emit("freeze", false, freezeTimestamp);
|
|
658
|
+
this.preserveScrollPositionAcrossConfigChange = true;
|
|
659
|
+
this.baseFilter = {
|
|
660
|
+
filter: ""
|
|
661
|
+
};
|
|
662
|
+
} else throw Error("[BaseDataSource] cannot freeze, dataSource is already frozen");
|
|
663
|
+
}
|
|
664
|
+
get freezeTimestamp() {
|
|
665
|
+
return this.#freezeTimestamp;
|
|
666
|
+
}
|
|
667
|
+
get isFrozen() {
|
|
668
|
+
return "number" == typeof this.#freezeTimestamp;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
export { ArrayDataSource };
|