@bexis2/bexis2-core-ui 0.4.96 → 0.4.98
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 +104 -25
- package/dist/components/CodeEditor/CodeEditor.svelte +2 -2
- package/dist/components/Facets/ShowMore.svelte +4 -6
- package/dist/components/File/FileUploader.svelte +6 -3
- package/dist/components/Table/Table.svelte +26 -5
- package/dist/components/Table/TableContent.svelte +222 -22
- package/dist/components/Table/TablePaginationServer.svelte +27 -8
- package/dist/components/Table/clientDB.d.ts +22 -0
- package/dist/components/Table/clientDB.js +236 -0
- package/dist/components/Table/filter.js +5 -1
- package/dist/components/Table/tableWorker.d.ts +1 -0
- package/dist/components/Table/tableWorker.js +179 -0
- package/dist/components/Table/utils.d.ts +1 -2
- package/dist/components/Table/utils.js +45 -26
- package/dist/components/form/CheckboxKvPList.svelte +15 -15
- package/dist/components/form/CheckboxList.svelte +1 -1
- package/dist/components/form/DateInput.svelte +12 -1
- package/dist/components/form/DatePickerInput.svelte +51 -29
- package/dist/components/form/Dropdown.svelte +14 -3
- package/dist/components/form/DropdownKvP.svelte +13 -2
- package/dist/components/form/InputContainer.svelte +17 -15
- package/dist/components/form/MultiSelect.svelte +30 -29
- package/dist/components/form/NumberInput.svelte +12 -1
- package/dist/components/form/TextArea.svelte +12 -1
- package/dist/components/form/TextInput.svelte +12 -1
- package/dist/components/page/Alert.svelte +5 -1
- package/dist/components/page/BackToTop.svelte +3 -1
- package/dist/components/page/Docs.svelte +9 -2
- package/dist/components/page/GoToTop.svelte +14 -15
- package/dist/components/page/Page.svelte +50 -56
- package/dist/components/page/breadcrumb/BreadcrumbDataCaller.js +11 -15
- package/dist/components/page/menu/MenuAccountBar.svelte +1 -5
- package/dist/components/page/menu/MenuDataCaller.js +1 -1
- package/dist/components/page/menu/MenuItem.svelte +9 -7
- package/dist/components/page/menu/MenuSublist.svelte +7 -5
- package/dist/components/page/menu/SettingsBar.svelte +4 -1
- package/dist/components/toggle/Toggle.svelte +9 -9
- package/dist/css/core.ui.postcss +1 -2
- package/dist/services/Api.js +4 -4
- package/dist/services/BaseCaller.js +1 -1
- package/package.json +114 -114
- package/src/lib/components/CodeEditor/CodeEditor.svelte +4 -4
- package/src/lib/components/Facets/Facets.svelte +2 -2
- package/src/lib/components/Facets/ShowMore.svelte +4 -6
- package/src/lib/components/File/FileUploader.svelte +17 -14
- package/src/lib/components/Table/Table.svelte +31 -10
- package/src/lib/components/Table/TableContent.svelte +258 -33
- package/src/lib/components/Table/TableFilter.svelte +7 -2
- package/src/lib/components/Table/TablePagination.svelte +6 -2
- package/src/lib/components/Table/TablePaginationServer.svelte +36 -10
- package/src/lib/components/Table/clientDB.js +236 -0
- package/src/lib/components/Table/filter.ts +27 -23
- package/src/lib/components/Table/tableWorker.js +179 -0
- package/src/lib/components/Table/utils.ts +75 -56
- package/src/lib/components/form/Checkbox.svelte +1 -1
- package/src/lib/components/form/CheckboxKvPList.svelte +15 -16
- package/src/lib/components/form/CheckboxList.svelte +1 -1
- package/src/lib/components/form/DateInput.svelte +13 -2
- package/src/lib/components/form/DatePickerInput.svelte +51 -29
- package/src/lib/components/form/Dropdown.svelte +14 -3
- package/src/lib/components/form/DropdownKvP.svelte +13 -2
- package/src/lib/components/form/InputContainer.svelte +18 -17
- package/src/lib/components/form/MultiSelect.svelte +30 -29
- package/src/lib/components/form/NumberInput.svelte +21 -12
- package/src/lib/components/form/TextArea.svelte +13 -2
- package/src/lib/components/form/TextInput.svelte +13 -2
- package/src/lib/components/page/Alert.svelte +5 -1
- package/src/lib/components/page/BackToTop.svelte +3 -1
- package/src/lib/components/page/Docs.svelte +9 -2
- package/src/lib/components/page/GoToTop.svelte +14 -15
- package/src/lib/components/page/Notification.svelte +1 -1
- package/src/lib/components/page/Page.svelte +67 -78
- package/src/lib/components/page/breadcrumb/Breadcrumb.svelte +2 -3
- package/src/lib/components/page/breadcrumb/BreadcrumbDataCaller.js +11 -15
- package/src/lib/components/page/menu/MenuAccountBar.svelte +1 -6
- package/src/lib/components/page/menu/MenuDataCaller.js +1 -1
- package/src/lib/components/page/menu/MenuItem.svelte +10 -8
- package/src/lib/components/page/menu/MenuSublist.svelte +35 -41
- package/src/lib/components/page/menu/SettingsBar.svelte +5 -2
- package/src/lib/components/toggle/Toggle.svelte +28 -30
- package/src/lib/css/core.ui.postcss +1 -2
- package/src/lib/index.ts +1 -2
- package/src/lib/services/Api.ts +21 -20
- package/src/lib/services/BaseCaller.js +1 -1
- package/src/lib/stores/apiStores.ts +1 -5
- package/src/lib/stores/pageStores.ts +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>import { afterUpdate, createEventDispatcher, onDestroy, onMount } from "svelte";
|
|
2
|
-
import {
|
|
2
|
+
import { writable } from "svelte/store";
|
|
3
3
|
import Fa from "svelte-fa";
|
|
4
4
|
import { faCompress, faDownload, faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
5
5
|
import { createTable, Subscribe, Render, createRender } from "svelte-headless-table";
|
|
@@ -21,6 +21,7 @@ import TableFilterServer from "./TableFilterServer.svelte";
|
|
|
21
21
|
import TablePagination from "./TablePagination.svelte";
|
|
22
22
|
import TablePaginationServer from "./TablePaginationServer.svelte";
|
|
23
23
|
import ColumnsMenu from "./ColumnsMenu.svelte";
|
|
24
|
+
import ClientDB from "./clientDB.js";
|
|
24
25
|
import * as utils from "./utils";
|
|
25
26
|
import { columnFilter, searchFilter } from "./filter";
|
|
26
27
|
import { Send } from "../../models/Models";
|
|
@@ -56,20 +57,27 @@ let {
|
|
|
56
57
|
// Whether to display the export button and enable export functionality
|
|
57
58
|
server
|
|
58
59
|
} = config;
|
|
60
|
+
const clientDb = config.clientDb ?? false;
|
|
61
|
+
const clientDbSeedData = config.clientDbSeedData ?? [];
|
|
62
|
+
const initialServerCount = Number(config.__initialServerCount ?? 0);
|
|
59
63
|
let searchValue = "";
|
|
60
64
|
let isFetching = false;
|
|
61
65
|
let tableRef;
|
|
62
|
-
|
|
66
|
+
let serverItemCountValue = Number.isFinite(initialServerCount) ? initialServerCount : 0;
|
|
67
|
+
const serverSide = server !== void 0 || clientDb;
|
|
68
|
+
let _clientDbInstance = null;
|
|
69
|
+
let clientDbEnabled = clientDb;
|
|
70
|
+
let clientDbReady = false;
|
|
71
|
+
let clientDbInitSource = [];
|
|
72
|
+
let clientDbSeedSize = 0;
|
|
63
73
|
const { sendModel = new Send() } = server ?? {};
|
|
64
74
|
const filters = writable({});
|
|
65
75
|
const dispatch = createEventDispatcher();
|
|
66
76
|
const actionDispatcher = (obj) => dispatch("action", obj);
|
|
67
77
|
const rowHeights = writable({});
|
|
68
78
|
const colWidths = writable([]);
|
|
69
|
-
const serverItems = serverSide ? writable(0) : void 0;
|
|
70
|
-
const serverItemCount =
|
|
71
|
-
serverItems.subscribe((val) => set(val));
|
|
72
|
-
}) : void 0;
|
|
79
|
+
const serverItems = serverSide ? writable(Number.isFinite(initialServerCount) ? initialServerCount : 0) : void 0;
|
|
80
|
+
const serverItemCount = serverItems;
|
|
73
81
|
const table = createTable(data, {
|
|
74
82
|
colFilter: addColumnFilters(),
|
|
75
83
|
tableFilter: addTableFilter({
|
|
@@ -97,6 +105,16 @@ $data.forEach((item) => {
|
|
|
97
105
|
}
|
|
98
106
|
});
|
|
99
107
|
});
|
|
108
|
+
if (columns && Object.keys(columns).length > 0) {
|
|
109
|
+
Object.keys(columns).forEach((key) => {
|
|
110
|
+
if (optionsComponent !== void 0 && key === "optionsColumn") {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (!allCols[key]) {
|
|
114
|
+
allCols[key] = {};
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
100
118
|
Object.keys(allCols).forEach((key) => {
|
|
101
119
|
$filters = { ...$filters, [key]: {} };
|
|
102
120
|
});
|
|
@@ -226,10 +244,21 @@ const tableColumns = [
|
|
|
226
244
|
}
|
|
227
245
|
})
|
|
228
246
|
];
|
|
247
|
+
const getUniqueOptionsColumnId = () => {
|
|
248
|
+
const used = new Set(unexcludedColumns.map((col) => String(col)));
|
|
249
|
+
let id = "optionsColumn";
|
|
250
|
+
let i = 1;
|
|
251
|
+
while (used.has(id)) {
|
|
252
|
+
id = `optionsColumn_${i}`;
|
|
253
|
+
i += 1;
|
|
254
|
+
}
|
|
255
|
+
return id;
|
|
256
|
+
};
|
|
229
257
|
if (optionsComponent !== void 0) {
|
|
258
|
+
const optionsColumnId = getUniqueOptionsColumnId();
|
|
230
259
|
tableColumns.push(
|
|
231
260
|
table.display({
|
|
232
|
-
id:
|
|
261
|
+
id: optionsColumnId,
|
|
233
262
|
header: "",
|
|
234
263
|
plugins: {
|
|
235
264
|
export: {
|
|
@@ -251,7 +280,7 @@ const { filterValue } = pluginStates.tableFilter;
|
|
|
251
280
|
const { exportedData } = pluginStates.export;
|
|
252
281
|
const { pageIndex, pageSize } = pluginStates.page;
|
|
253
282
|
const { hiddenColumnIds } = pluginStates.hideColumns;
|
|
254
|
-
let prevSort =
|
|
283
|
+
let prevSort = [];
|
|
255
284
|
const sortServer = (order, id) => {
|
|
256
285
|
if (!sendModel) throw new Error("Server-side configuration is missing");
|
|
257
286
|
if (order === void 0) {
|
|
@@ -259,19 +288,137 @@ const sortServer = (order, id) => {
|
|
|
259
288
|
} else {
|
|
260
289
|
sendModel.order = [{ column: id, direction: order }];
|
|
261
290
|
}
|
|
262
|
-
if (JSON.stringify(sendModel.order) !== JSON.stringify(prevSort)
|
|
291
|
+
if (JSON.stringify(sendModel.order) !== JSON.stringify(prevSort)) {
|
|
263
292
|
$pageIndex = 0;
|
|
264
293
|
$data = [];
|
|
265
294
|
updateTableWithParams();
|
|
266
295
|
}
|
|
267
|
-
|
|
268
|
-
prevSort = [];
|
|
269
|
-
} else {
|
|
270
|
-
prevSort = sendModel.order;
|
|
271
|
-
}
|
|
296
|
+
prevSort = sendModel.order ? [...sendModel.order] : [];
|
|
272
297
|
};
|
|
273
298
|
const updateTableWithParams = async () => {
|
|
274
|
-
if (
|
|
299
|
+
if (clientDbEnabled) {
|
|
300
|
+
isFetching = true;
|
|
301
|
+
const filtersNormalized = utils.normalizeFilters($filters);
|
|
302
|
+
const q = sendModel?.q || "";
|
|
303
|
+
const offset2 = $pageSize * $pageIndex;
|
|
304
|
+
const limit = $pageSize;
|
|
305
|
+
if (!_clientDbInstance) {
|
|
306
|
+
_clientDbInstance = new ClientDB(tableId);
|
|
307
|
+
}
|
|
308
|
+
if (!clientDbReady) {
|
|
309
|
+
const source = clientDbInitSource.length > 0 ? clientDbInitSource : $data;
|
|
310
|
+
const getValue = (row, column) => {
|
|
311
|
+
const dot = String(column || "").replaceAll("%%%", ".");
|
|
312
|
+
return row[dot] ?? row[column] ?? row[dot.replaceAll(".", "%%%")];
|
|
313
|
+
};
|
|
314
|
+
const localMatches = (row) => {
|
|
315
|
+
if (q && q.length > 0) {
|
|
316
|
+
const ql = String(q).toLowerCase();
|
|
317
|
+
let found = false;
|
|
318
|
+
for (const k in row) {
|
|
319
|
+
const v = row[k];
|
|
320
|
+
if (v == null) continue;
|
|
321
|
+
if (String(v).toLowerCase().includes(ql)) {
|
|
322
|
+
found = true;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (!found) return false;
|
|
327
|
+
}
|
|
328
|
+
for (const f of filtersNormalized) {
|
|
329
|
+
const actual = getValue(row, f.column);
|
|
330
|
+
const val = f.value;
|
|
331
|
+
if (val == null) continue;
|
|
332
|
+
const aStr = String(actual ?? "").toLowerCase();
|
|
333
|
+
const vStr = String(val ?? "").toLowerCase();
|
|
334
|
+
const aNum = Number(actual);
|
|
335
|
+
const vNum = Number(val);
|
|
336
|
+
const aDate = new Date(actual).getTime();
|
|
337
|
+
const vDate = new Date(val).getTime();
|
|
338
|
+
switch (f.filterBy) {
|
|
339
|
+
case "c":
|
|
340
|
+
if (!aStr.includes(vStr)) return false;
|
|
341
|
+
break;
|
|
342
|
+
case "nc":
|
|
343
|
+
if (aStr.includes(vStr)) return false;
|
|
344
|
+
break;
|
|
345
|
+
case "sw":
|
|
346
|
+
if (!aStr.startsWith(vStr)) return false;
|
|
347
|
+
break;
|
|
348
|
+
case "ew":
|
|
349
|
+
if (!aStr.endsWith(vStr)) return false;
|
|
350
|
+
break;
|
|
351
|
+
case "e":
|
|
352
|
+
if (actual != val) return false;
|
|
353
|
+
break;
|
|
354
|
+
case "ne":
|
|
355
|
+
if (actual == val) return false;
|
|
356
|
+
break;
|
|
357
|
+
case "gt":
|
|
358
|
+
if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum > vNum)) return false;
|
|
359
|
+
break;
|
|
360
|
+
case "lt":
|
|
361
|
+
if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum < vNum)) return false;
|
|
362
|
+
break;
|
|
363
|
+
case "gte":
|
|
364
|
+
if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum >= vNum)) return false;
|
|
365
|
+
break;
|
|
366
|
+
case "lte":
|
|
367
|
+
if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum <= vNum)) return false;
|
|
368
|
+
break;
|
|
369
|
+
case "o":
|
|
370
|
+
if (Number.isNaN(aDate) || Number.isNaN(vDate) || aDate !== vDate) return false;
|
|
371
|
+
break;
|
|
372
|
+
case "sf":
|
|
373
|
+
case "a":
|
|
374
|
+
if (Number.isNaN(aDate) || Number.isNaN(vDate) || !(aDate >= vDate)) return false;
|
|
375
|
+
break;
|
|
376
|
+
case "u":
|
|
377
|
+
case "b":
|
|
378
|
+
if (Number.isNaN(aDate) || Number.isNaN(vDate) || !(aDate <= vDate)) return false;
|
|
379
|
+
break;
|
|
380
|
+
case "no":
|
|
381
|
+
if (Number.isNaN(aDate) || Number.isNaN(vDate) || aDate === vDate) return false;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return true;
|
|
386
|
+
};
|
|
387
|
+
const filtered = source.filter(localMatches);
|
|
388
|
+
const order = sendModel?.order || [];
|
|
389
|
+
if (order.length > 0) {
|
|
390
|
+
const { column, direction } = order[0];
|
|
391
|
+
const dir = direction === "desc" ? -1 : 1;
|
|
392
|
+
filtered.sort((a, b) => {
|
|
393
|
+
const av = getValue(a, column);
|
|
394
|
+
const bv = getValue(b, column);
|
|
395
|
+
if (av == null && bv == null) return 0;
|
|
396
|
+
if (av == null) return -1 * dir;
|
|
397
|
+
if (bv == null) return 1 * dir;
|
|
398
|
+
if (typeof av === "number" && typeof bv === "number") return (av - bv) * dir;
|
|
399
|
+
return String(av).localeCompare(String(bv)) * dir;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
const localRows = filtered.slice(offset2, offset2 + limit);
|
|
403
|
+
serverItems?.set(filtered.length);
|
|
404
|
+
data.set(localRows);
|
|
405
|
+
isFetching = false;
|
|
406
|
+
return { rows: localRows, total: filtered.length };
|
|
407
|
+
}
|
|
408
|
+
const res = await _clientDbInstance.query({
|
|
409
|
+
q,
|
|
410
|
+
filters: filtersNormalized,
|
|
411
|
+
order: sendModel?.order || [],
|
|
412
|
+
offset: offset2,
|
|
413
|
+
limit
|
|
414
|
+
});
|
|
415
|
+
const payload = res.payload ? res.payload : res;
|
|
416
|
+
serverItems?.set(payload.total ?? payload.totalCount ?? 0);
|
|
417
|
+
data.set(payload.rows ?? []);
|
|
418
|
+
isFetching = false;
|
|
419
|
+
return payload;
|
|
420
|
+
}
|
|
421
|
+
if ($data.length > 0 && serverSide && Number($serverItemCount) > 0) {
|
|
275
422
|
console.log("Table is not empty");
|
|
276
423
|
return;
|
|
277
424
|
}
|
|
@@ -332,6 +479,9 @@ afterUpdate(() => {
|
|
|
332
479
|
if (resizable !== "rows" && resizable !== "both") {
|
|
333
480
|
return;
|
|
334
481
|
}
|
|
482
|
+
if (!$pageRows || $pageRows.length === 0) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
335
485
|
const e = tableRef?.querySelector(`#${tableId}-row-${$pageRows[0].id}`);
|
|
336
486
|
if (e) {
|
|
337
487
|
getDimensions();
|
|
@@ -340,10 +490,45 @@ afterUpdate(() => {
|
|
|
340
490
|
onDestroy(() => {
|
|
341
491
|
resizeColumnsObserver.disconnect();
|
|
342
492
|
resizeRowsObserver.disconnect();
|
|
493
|
+
if (_clientDbInstance) {
|
|
494
|
+
try {
|
|
495
|
+
_clientDbInstance.destroy();
|
|
496
|
+
} catch (e) {
|
|
497
|
+
}
|
|
498
|
+
}
|
|
343
499
|
});
|
|
344
500
|
$: sortKeys = pluginStates.sort.sortKeys;
|
|
345
|
-
|
|
346
|
-
|
|
501
|
+
$: serverItemCountValue = clientDbEnabled ? Number($serverItemCount) > 0 ? Math.max(
|
|
502
|
+
Number($serverItemCount),
|
|
503
|
+
initialServerCount,
|
|
504
|
+
clientDbSeedSize,
|
|
505
|
+
clientDbInitSource.length,
|
|
506
|
+
$data.length
|
|
507
|
+
) : Math.max(initialServerCount, clientDbSeedSize, clientDbInitSource.length, $data.length) : Number($serverItemCount);
|
|
508
|
+
onMount(async () => {
|
|
509
|
+
if (clientDbEnabled) {
|
|
510
|
+
const seed = Array.isArray(clientDbSeedData) ? clientDbSeedData : [];
|
|
511
|
+
const hasSeed = seed.length > 0;
|
|
512
|
+
clientDbSeedSize = seed.length;
|
|
513
|
+
if (hasSeed || $data && $data.length > 0) {
|
|
514
|
+
clientDbInitSource = hasSeed ? [...seed] : [...$data];
|
|
515
|
+
const firstPage = clientDbInitSource.slice(0, $pageSize);
|
|
516
|
+
serverItems?.set(clientDbInitSource.length);
|
|
517
|
+
data.set(firstPage);
|
|
518
|
+
_clientDbInstance = new ClientDB(tableId);
|
|
519
|
+
_clientDbInstance.init(clientDbInitSource).then(async () => {
|
|
520
|
+
const result = await updateTableWithParams();
|
|
521
|
+
clientDbReady = true;
|
|
522
|
+
clientDbInitSource = [];
|
|
523
|
+
return result;
|
|
524
|
+
}).catch(() => {
|
|
525
|
+
clientDbReady = false;
|
|
526
|
+
});
|
|
527
|
+
} else if (!_clientDbInstance) {
|
|
528
|
+
_clientDbInstance = new ClientDB(tableId);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (server !== void 0) {
|
|
347
532
|
updateTableWithParams();
|
|
348
533
|
}
|
|
349
534
|
});
|
|
@@ -358,14 +543,19 @@ $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => co
|
|
|
358
543
|
{#if search}
|
|
359
544
|
<form
|
|
360
545
|
class="flex gap-2"
|
|
361
|
-
on:submit|preventDefault={() => {
|
|
546
|
+
on:submit|preventDefault={async () => {
|
|
362
547
|
if (serverSide && !sendModel) {
|
|
363
548
|
throw new Error('Server-side configuration is missing');
|
|
364
549
|
} else {
|
|
365
550
|
sendModel.q = searchValue;
|
|
366
551
|
}
|
|
367
552
|
|
|
553
|
+
$pageIndex = 0;
|
|
368
554
|
$filterValue = searchValue;
|
|
555
|
+
if (serverSide) {
|
|
556
|
+
$data = [];
|
|
557
|
+
await updateTableWithParams();
|
|
558
|
+
}
|
|
369
559
|
}}
|
|
370
560
|
>
|
|
371
561
|
<div class="relative w-full flex items-center">
|
|
@@ -383,15 +573,20 @@ $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => co
|
|
|
383
573
|
id="{tableId}-searchReset"
|
|
384
574
|
class="absolute right-3 items-center"
|
|
385
575
|
aria-label="Clear search"
|
|
386
|
-
on:click|preventDefault={() => {
|
|
576
|
+
on:click|preventDefault={async () => {
|
|
387
577
|
if (serverSide && !sendModel) {
|
|
388
578
|
throw new Error('Server-side configuration is missing');
|
|
389
579
|
} else {
|
|
390
580
|
sendModel.q = '';
|
|
391
581
|
}
|
|
392
582
|
|
|
583
|
+
$pageIndex = 0;
|
|
393
584
|
searchValue = '';
|
|
394
585
|
$filterValue = '';
|
|
586
|
+
if (serverSide) {
|
|
587
|
+
$data = [];
|
|
588
|
+
await updateTableWithParams();
|
|
589
|
+
}
|
|
395
590
|
}}><Fa icon={faXmark} /></button
|
|
396
591
|
>
|
|
397
592
|
</div>
|
|
@@ -401,14 +596,19 @@ $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => co
|
|
|
401
596
|
id="{tableId}-searchSubmit"
|
|
402
597
|
class="btn variant-filled-primary"
|
|
403
598
|
aria-label="Search"
|
|
404
|
-
on:click|preventDefault={() => {
|
|
599
|
+
on:click|preventDefault={async () => {
|
|
405
600
|
if (serverSide && !sendModel) {
|
|
406
601
|
throw new Error('Server-side configuration is missing');
|
|
407
602
|
} else {
|
|
408
603
|
sendModel.q = searchValue;
|
|
409
604
|
}
|
|
410
605
|
|
|
606
|
+
$pageIndex = 0;
|
|
411
607
|
$filterValue = searchValue;
|
|
608
|
+
if (serverSide) {
|
|
609
|
+
$data = [];
|
|
610
|
+
await updateTableWithParams();
|
|
611
|
+
}
|
|
412
612
|
}}>Search</button
|
|
413
613
|
>
|
|
414
614
|
</form>
|
|
@@ -530,7 +730,7 @@ $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => co
|
|
|
530
730
|
? `Remove sorting by ${cell.label} column`
|
|
531
731
|
: `Sort by ${cell.label} column in ascending order`}
|
|
532
732
|
>
|
|
533
|
-
{cell.render().replaceAll('%%%', '.')}
|
|
733
|
+
{String(cell.render()).replaceAll('%%%', '.')}
|
|
534
734
|
</span>
|
|
535
735
|
<div class="w-2">
|
|
536
736
|
{#if props.sort.order === 'asc'}
|
|
@@ -625,7 +825,7 @@ $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => co
|
|
|
625
825
|
<TablePaginationServer
|
|
626
826
|
pageConfig={pluginStates.page}
|
|
627
827
|
{pageSizes}
|
|
628
|
-
itemCount={
|
|
828
|
+
itemCount={serverItemCountValue}
|
|
629
829
|
updateTable={updateTableWithParams}
|
|
630
830
|
id={tableId}
|
|
631
831
|
{data}
|
|
@@ -7,6 +7,17 @@ export let pageSizes;
|
|
|
7
7
|
export let id;
|
|
8
8
|
export let updateTable;
|
|
9
9
|
export let data;
|
|
10
|
+
const toCount = (value) => {
|
|
11
|
+
if (typeof value === "number") return Number.isNaN(value) ? 0 : value;
|
|
12
|
+
const parsed = Number(String(value ?? "").replace(/[^0-9.-]/g, ""));
|
|
13
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
14
|
+
};
|
|
15
|
+
const getEffectiveCount = () => {
|
|
16
|
+
const count = toCount(itemCount);
|
|
17
|
+
if (count > 0) return count;
|
|
18
|
+
return Array.isArray($data) ? $data.length : 0;
|
|
19
|
+
};
|
|
20
|
+
let effectiveCount = 0;
|
|
10
21
|
let indexInformation = "";
|
|
11
22
|
const { pageIndex, pageCount, pageSize } = pageConfig;
|
|
12
23
|
let pageSizeDropdownValue = $pageSize;
|
|
@@ -17,17 +28,19 @@ const pageSizePopup = {
|
|
|
17
28
|
closeQuery: ".listbox-item"
|
|
18
29
|
};
|
|
19
30
|
const getIndexInfomationString = () => {
|
|
20
|
-
|
|
31
|
+
const count = effectiveCount;
|
|
32
|
+
return count === 0 ? "No items" : `Displaying items ${$pageIndex * $pageSize + 1} - ${Math.min(
|
|
21
33
|
($pageIndex + 1) * $pageSize,
|
|
22
|
-
|
|
23
|
-
)} of ${
|
|
34
|
+
count
|
|
35
|
+
)} of ${count}`;
|
|
24
36
|
};
|
|
25
37
|
function updateTableServer() {
|
|
26
38
|
$data = [];
|
|
27
39
|
updateTable();
|
|
28
40
|
}
|
|
41
|
+
$: effectiveCount = getEffectiveCount();
|
|
29
42
|
$: paginationSettings = {
|
|
30
|
-
size:
|
|
43
|
+
size: effectiveCount,
|
|
31
44
|
limit: $pageSize,
|
|
32
45
|
page: $pageIndex,
|
|
33
46
|
amounts: pageSizes
|
|
@@ -52,9 +65,12 @@ $: $pageCount, $pageIndex, $pageSize, itemCount, indexInformation = getIndexInfo
|
|
|
52
65
|
{#each pageSizes as size}
|
|
53
66
|
<ListBoxItem
|
|
54
67
|
bind:group={pageSizeDropdownValue}
|
|
55
|
-
name="medium"
|
|
56
|
-
|
|
57
|
-
|
|
68
|
+
name="medium"
|
|
69
|
+
value={size}
|
|
70
|
+
on:click={() => {
|
|
71
|
+
$pageSize = size;
|
|
72
|
+
updateTableServer();
|
|
73
|
+
}}>{size}</ListBoxItem
|
|
58
74
|
>
|
|
59
75
|
{/each}
|
|
60
76
|
</ListBox>
|
|
@@ -63,7 +79,10 @@ $: $pageCount, $pageIndex, $pageSize, itemCount, indexInformation = getIndexInfo
|
|
|
63
79
|
</div>
|
|
64
80
|
<div class="flex justify-center">
|
|
65
81
|
<Paginator
|
|
66
|
-
on:page={(page) => {
|
|
82
|
+
on:page={(page) => {
|
|
83
|
+
$pageIndex = page.detail;
|
|
84
|
+
updateTableServer();
|
|
85
|
+
}}
|
|
67
86
|
settings={paginationSettings}
|
|
68
87
|
select="hidden"
|
|
69
88
|
active="!variant-filled-secondary !text-on-secondary-token"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default class ClientDB {
|
|
2
|
+
constructor(tableId: any);
|
|
3
|
+
tableId: any;
|
|
4
|
+
db: any;
|
|
5
|
+
init(rows: any): Promise<{
|
|
6
|
+
tableId: any;
|
|
7
|
+
count: any;
|
|
8
|
+
}>;
|
|
9
|
+
query({ q, filters, order, offset, limit }?: {
|
|
10
|
+
q?: string | undefined;
|
|
11
|
+
filters?: never[] | undefined;
|
|
12
|
+
order?: never[] | undefined;
|
|
13
|
+
offset?: number | undefined;
|
|
14
|
+
limit?: number | undefined;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
tableId: any;
|
|
17
|
+
rows: any[];
|
|
18
|
+
total: number;
|
|
19
|
+
}>;
|
|
20
|
+
clear(): Promise<void>;
|
|
21
|
+
destroy(): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// Simple client-side DB wrapper using IndexedDB directly (no worker)
|
|
2
|
+
const DB_PREFIX = 'table-db-';
|
|
3
|
+
|
|
4
|
+
function openDB(name) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const req = indexedDB.open(DB_PREFIX + name, 1);
|
|
7
|
+
req.onupgradeneeded = () => {
|
|
8
|
+
const db = req.result;
|
|
9
|
+
if (!db.objectStoreNames.contains('rows')) {
|
|
10
|
+
db.createObjectStore('rows', { keyPath: '__id', autoIncrement: true });
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
req.onsuccess = () => resolve(req.result);
|
|
14
|
+
req.onerror = () => reject(req.error);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function clearStore(db) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const tx = db.transaction('rows', 'readwrite');
|
|
21
|
+
const store = tx.objectStore('rows');
|
|
22
|
+
const req = store.clear();
|
|
23
|
+
req.onsuccess = () => resolve(undefined);
|
|
24
|
+
req.onerror = () => reject(req.error);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function bulkInsert(db, rows) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const tx = db.transaction('rows', 'readwrite');
|
|
31
|
+
const store = tx.objectStore('rows');
|
|
32
|
+
for (let i = 0; i < rows.length; i++) {
|
|
33
|
+
store.add({ __r: rows[i] });
|
|
34
|
+
}
|
|
35
|
+
tx.oncomplete = () => resolve(undefined);
|
|
36
|
+
tx.onerror = () => reject(tx.error);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function matches(rowObj, q, filters) {
|
|
41
|
+
const row = rowObj.__r || rowObj;
|
|
42
|
+
|
|
43
|
+
if (q && q.length > 0) {
|
|
44
|
+
const ql = String(q).toLowerCase();
|
|
45
|
+
let found = false;
|
|
46
|
+
for (const k in row) {
|
|
47
|
+
const v = row[k];
|
|
48
|
+
if (v == null) continue;
|
|
49
|
+
if (typeof v === 'string' && v.toLowerCase().includes(ql)) {
|
|
50
|
+
found = true;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
if (typeof v === 'number' && String(v).includes(ql)) {
|
|
54
|
+
found = true;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!found) return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const normalizeKey = (k) => String(k || '').replaceAll('%%%', '.');
|
|
62
|
+
const getValue = (column) => {
|
|
63
|
+
const dot = normalizeKey(column);
|
|
64
|
+
return row[dot] ?? row[column] ?? row[dot.replaceAll('.', '%%%')];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const asComparableNumber = (v) => {
|
|
68
|
+
if (typeof v === 'number') return v;
|
|
69
|
+
if (typeof v === 'bigint') return Number(v);
|
|
70
|
+
const n = Number(v);
|
|
71
|
+
return Number.isNaN(n) ? null : n;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const asTime = (v) => {
|
|
75
|
+
if (v instanceof Date) return v.getTime();
|
|
76
|
+
const d = new Date(v);
|
|
77
|
+
const t = d.getTime();
|
|
78
|
+
return Number.isNaN(t) ? null : t;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (filters && filters.length > 0) {
|
|
82
|
+
for (const f of filters) {
|
|
83
|
+
const op = f.filterBy;
|
|
84
|
+
const val = f.value;
|
|
85
|
+
const actual = getValue(f.column);
|
|
86
|
+
if (val == null) continue;
|
|
87
|
+
|
|
88
|
+
const aStr = String(actual ?? '').toLowerCase();
|
|
89
|
+
const vStr = String(val ?? '').toLowerCase();
|
|
90
|
+
const aNum = asComparableNumber(actual);
|
|
91
|
+
const vNum = asComparableNumber(val);
|
|
92
|
+
const aDate = asTime(actual);
|
|
93
|
+
const vDate = asTime(val);
|
|
94
|
+
|
|
95
|
+
if (op === 'c' && !aStr.includes(vStr)) return false;
|
|
96
|
+
if (op === 'nc' && aStr.includes(vStr)) return false;
|
|
97
|
+
if (op === 'sw' && !aStr.startsWith(vStr)) return false;
|
|
98
|
+
if (op === 'ew' && !aStr.endsWith(vStr)) return false;
|
|
99
|
+
|
|
100
|
+
if (op === 'e' && actual != val) return false;
|
|
101
|
+
if (op === 'ne' && actual == val) return false;
|
|
102
|
+
|
|
103
|
+
if (op === 'gt') {
|
|
104
|
+
if (aNum == null || vNum == null || !(aNum > vNum)) return false;
|
|
105
|
+
}
|
|
106
|
+
if (op === 'lt') {
|
|
107
|
+
if (aNum == null || vNum == null || !(aNum < vNum)) return false;
|
|
108
|
+
}
|
|
109
|
+
if (op === 'gte') {
|
|
110
|
+
if (aNum == null || vNum == null || !(aNum >= vNum)) return false;
|
|
111
|
+
}
|
|
112
|
+
if (op === 'lte') {
|
|
113
|
+
if (aNum == null || vNum == null || !(aNum <= vNum)) return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (op === 'o') {
|
|
117
|
+
if (aDate == null || vDate == null || aDate !== vDate) return false;
|
|
118
|
+
}
|
|
119
|
+
if (op === 'sf' || op === 'a') {
|
|
120
|
+
if (aDate == null || vDate == null || !(aDate > vDate || aDate === vDate)) return false;
|
|
121
|
+
}
|
|
122
|
+
if (op === 'u' || op === 'b') {
|
|
123
|
+
if (aDate == null || vDate == null || !(aDate < vDate || aDate === vDate)) return false;
|
|
124
|
+
}
|
|
125
|
+
if (op === 'no') {
|
|
126
|
+
if (aDate == null || vDate == null || aDate === vDate) return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export default class ClientDB {
|
|
135
|
+
constructor(tableId) {
|
|
136
|
+
this.tableId = tableId;
|
|
137
|
+
this.db = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async init(rows) {
|
|
141
|
+
this.db = this.db || (await openDB(this.tableId));
|
|
142
|
+
await clearStore(this.db);
|
|
143
|
+
|
|
144
|
+
const CHUNK = 1000;
|
|
145
|
+
for (let i = 0; i < rows.length; i += CHUNK) {
|
|
146
|
+
const chunk = rows.slice(i, i + CHUNK);
|
|
147
|
+
await bulkInsert(this.db, chunk);
|
|
148
|
+
// Yield to keep the UI responsive while importing
|
|
149
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { tableId: this.tableId, count: rows.length };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async query({ q = '', filters = [], order = [], offset = 0, limit = 100 } = {}) {
|
|
156
|
+
this.db = this.db || (await openDB(this.tableId));
|
|
157
|
+
|
|
158
|
+
const tx = this.db.transaction('rows', 'readonly');
|
|
159
|
+
const store = tx.objectStore('rows');
|
|
160
|
+
const req = store.openCursor();
|
|
161
|
+
|
|
162
|
+
const matched = [];
|
|
163
|
+
|
|
164
|
+
await new Promise((resolve, reject) => {
|
|
165
|
+
req.onsuccess = (evt) => {
|
|
166
|
+
const cursor = evt.target.result;
|
|
167
|
+
if (!cursor) {
|
|
168
|
+
resolve(undefined);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const rec = cursor.value;
|
|
173
|
+
if (matches(rec, q, filters)) {
|
|
174
|
+
matched.push(rec.__r || rec);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
cursor.continue();
|
|
178
|
+
};
|
|
179
|
+
req.onerror = () => reject(req.error);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const getValue = (row, column) => {
|
|
183
|
+
const dot = String(column || '').replaceAll('%%%', '.');
|
|
184
|
+
return row[dot] ?? row[column] ?? row[dot.replaceAll('.', '%%%')];
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
if (order && order.length > 0) {
|
|
188
|
+
const { column, direction } = order[0];
|
|
189
|
+
const dir = direction === 'desc' ? -1 : 1;
|
|
190
|
+
matched.sort((a, b) => {
|
|
191
|
+
const av = getValue(a, column);
|
|
192
|
+
const bv = getValue(b, column);
|
|
193
|
+
|
|
194
|
+
if (av == null && bv == null) return 0;
|
|
195
|
+
if (av == null) return -1 * dir;
|
|
196
|
+
if (bv == null) return 1 * dir;
|
|
197
|
+
|
|
198
|
+
if (typeof av === 'number' && typeof bv === 'number') {
|
|
199
|
+
return (av - bv) * dir;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (typeof av === 'bigint' || typeof bv === 'bigint') {
|
|
203
|
+
const ai = BigInt(av);
|
|
204
|
+
const bi = BigInt(bv);
|
|
205
|
+
if (ai === bi) return 0;
|
|
206
|
+
return ai > bi ? dir : -dir;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const ad = new Date(av).getTime();
|
|
210
|
+
const bd = new Date(bv).getTime();
|
|
211
|
+
if (!Number.isNaN(ad) && !Number.isNaN(bd)) {
|
|
212
|
+
return (ad - bd) * dir;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return String(av).localeCompare(String(bv)) * dir;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const total = matched.length;
|
|
220
|
+
const rows = matched.slice(offset, offset + limit);
|
|
221
|
+
|
|
222
|
+
return { tableId: this.tableId, rows, total };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async clear() {
|
|
226
|
+
this.db = this.db || (await openDB(this.tableId));
|
|
227
|
+
await clearStore(this.db);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
destroy() {
|
|
231
|
+
if (this.db) {
|
|
232
|
+
this.db.close();
|
|
233
|
+
this.db = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|