@platforma-sdk/ui-vue 1.36.0 → 1.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/lib.css +1 -1
  3. package/dist/lib.js +9163 -9045
  4. package/dist/lib.js.map +1 -1
  5. package/dist/src/AgGridVue/index.d.ts +1 -0
  6. package/dist/src/AgGridVue/index.d.ts.map +1 -1
  7. package/dist/src/AgGridVue/selection.d.ts +30 -0
  8. package/dist/src/AgGridVue/selection.d.ts.map +1 -0
  9. package/dist/src/components/PlAgDataTable/PlAgDataTable.vue.d.ts +4 -4
  10. package/dist/src/components/PlAgDataTable/PlAgDataTable.vue.d.ts.map +1 -1
  11. package/dist/src/components/PlAgDataTable/PlAgDataTableSheets.vue.d.ts +31 -0
  12. package/dist/src/components/PlAgDataTable/PlAgDataTableSheets.vue.d.ts.map +1 -0
  13. package/dist/src/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts +9 -14
  14. package/dist/src/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts.map +1 -1
  15. package/dist/src/components/PlAgDataTable/PlAgRowCount.vue.d.ts.map +1 -1
  16. package/dist/src/components/PlAgDataTable/sources/table-source-v2.d.ts +6 -13
  17. package/dist/src/components/PlAgDataTable/sources/table-source-v2.d.ts.map +1 -1
  18. package/dist/src/components/PlAgDataTable/sources/table-source.d.ts.map +1 -1
  19. package/dist/src/components/PlAgDataTable/sources/table-state-v2.d.ts +8 -0
  20. package/dist/src/components/PlAgDataTable/sources/table-state-v2.d.ts.map +1 -0
  21. package/dist/src/components/PlAgDataTable/types.d.ts +33 -9
  22. package/dist/src/components/PlAgDataTable/types.d.ts.map +1 -1
  23. package/dist/src/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue.d.ts.map +1 -1
  24. package/dist/src/components/PlAgRowNumHeader.vue.d.ts.map +1 -1
  25. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  26. package/package.json +7 -7
  27. package/src/AgGridVue/index.ts +1 -0
  28. package/src/AgGridVue/selection.ts +82 -0
  29. package/src/components/PlAgDataTable/PlAgDataTable.vue +35 -27
  30. package/src/components/PlAgDataTable/PlAgDataTableSheets.vue +125 -0
  31. package/src/components/PlAgDataTable/PlAgDataTableV2.vue +288 -440
  32. package/src/components/PlAgDataTable/PlAgRowCount.vue +5 -4
  33. package/src/components/PlAgDataTable/sources/table-source-v2.ts +33 -57
  34. package/src/components/PlAgDataTable/sources/table-source.ts +1 -2
  35. package/src/components/PlAgDataTable/sources/table-state-v2.ts +160 -0
  36. package/src/components/PlAgDataTable/types.ts +41 -13
  37. package/src/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue +5 -4
  38. package/src/components/PlAgRowNumHeader.vue +24 -15
@@ -1,19 +1,17 @@
1
1
  <script lang="ts" setup>
2
- import { Deferred, isJsonEqual } from '@milaboratories/helpers';
3
- import { PlDropdownLine } from '@milaboratories/uikit';
2
+ import {
3
+ isJsonEqual,
4
+ } from '@milaboratories/helpers';
4
5
  import type {
5
6
  AxisId,
6
- PlDataTableGridStateWithoutSheets,
7
- PlDataTableState,
7
+ PlDataTableGridStateCore,
8
+ PlDataTableStateV2,
8
9
  PlSelectionModel,
9
- PTableColumnSpec,
10
10
  PTableColumnSpecJson,
11
11
  PTableKey,
12
- PTableRecordFilter,
13
- PTableSorting,
14
12
  } from '@platforma-sdk/model';
15
13
  import {
16
- getAxisId,
14
+ createPlDataTableStateV2,
17
15
  getRawPlatformaInstance,
18
16
  parseJson,
19
17
  } from '@platforma-sdk/model';
@@ -23,31 +21,31 @@ import type {
23
21
  ColGroupDef,
24
22
  GridApi,
25
23
  GridOptions,
26
- GridReadyEvent,
27
24
  GridState,
28
25
  ManagedGridOptionKey,
29
26
  ManagedGridOptions,
30
- SortState,
31
- StateUpdatedEvent,
32
27
  } from 'ag-grid-enterprise';
33
28
  import {
34
- CellSelectionModule,
35
- ClientSideRowModelModule,
36
- ClipboardModule,
37
- isColumnSelectionCol,
38
- ModuleRegistry,
39
- ServerSideRowModelModule,
40
- } from 'ag-grid-enterprise';
41
- import { AgGridVue } from 'ag-grid-vue3';
42
- import canonicalize from 'canonicalize';
43
- import * as lodash from 'lodash';
44
- import { computed, nextTick, ref, shallowRef, toRefs, watch } from 'vue';
45
- import { AgGridTheme } from '../../lib';
29
+ AgGridVue,
30
+ } from 'ag-grid-vue3';
31
+ import {
32
+ computed,
33
+ ref,
34
+ shallowRef,
35
+ toRefs,
36
+ watch,
37
+ } from 'vue';
38
+ import {
39
+ AgGridTheme,
40
+ } from '../../lib';
46
41
  import PlAgCsvExporter from '../PlAgCsvExporter/PlAgCsvExporter.vue';
47
- import { PlAgGridColumnManager } from '../PlAgGridColumnManager';
42
+ import {
43
+ PlAgGridColumnManager,
44
+ } from '../PlAgGridColumnManager';
48
45
  import PlOverlayLoading from './PlAgOverlayLoading.vue';
49
46
  import PlOverlayNoRows from './PlAgOverlayNoRows.vue';
50
47
  import PlAgRowCount from './PlAgRowCount.vue';
48
+ import PlAgDataTableSheets from './PlAgDataTableSheets.vue';
51
49
  import {
52
50
  focusRow,
53
51
  makeOnceTracker,
@@ -57,33 +55,35 @@ import {
57
55
  autoSizeRowNumberColumn,
58
56
  PlAgDataTableRowNumberColId,
59
57
  } from './sources/row-number';
58
+ import type {
59
+ PlAgCellButtonAxisParams,
60
+ } from './sources/table-source-v2';
60
61
  import {
61
62
  makeRowId,
62
- type PlAgCellButtonAxisParams,
63
- updatePFrameGridOptions,
63
+ calculateGridOptions,
64
64
  } from './sources/table-source-v2';
65
65
  import type {
66
- PlAgDataTableSettings,
67
- PlAgDataTableSettingsPTable,
68
66
  PlAgDataTableV2Controller,
69
67
  PlAgDataTableV2Row,
70
68
  PlAgOverlayLoadingParams,
71
69
  PlAgOverlayNoRowsParams,
70
+ PlDataTableColumnsInfo,
71
+ PlDataTableSettingsV2,
72
+ PlDataTableSheetsSettings,
73
+ PTableKeyJson,
72
74
  } from './types';
75
+ import {
76
+ useTableState,
77
+ } from './sources/table-state-v2';
73
78
 
74
- ModuleRegistry.registerModules([
75
- ClientSideRowModelModule,
76
- ClipboardModule,
77
- ServerSideRowModelModule,
78
- CellSelectionModule,
79
- ]);
80
-
81
- const tableState = defineModel<PlDataTableState>({
82
- default: { gridState: {} },
79
+ const tableState = defineModel<PlDataTableStateV2>({
80
+ default: createPlDataTableStateV2(),
83
81
  });
84
82
  const selection = defineModel<PlSelectionModel>('selection');
85
83
  const props = defineProps<{
86
- settings?: Readonly<PlAgDataTableSettings>;
84
+ /** Required component settings */
85
+ settings: Readonly<PlDataTableSettingsV2>;
86
+
87
87
  /**
88
88
  * The showColumnsPanel prop controls the display of a button that activates
89
89
  * the columns management panel in the table. To make the button functional
@@ -91,10 +91,12 @@ const props = defineProps<{
91
91
  * This component serves as the target for teleporting the button.
92
92
  */
93
93
  showColumnsPanel?: boolean;
94
+
94
95
  /**
95
96
  * Css width of the Column Manager (Panel) modal (default value is `368px`)
96
97
  */
97
98
  columnsPanelWidth?: string;
99
+
98
100
  /**
99
101
  * The showExportButton prop controls the display of a button that allows
100
102
  * to export table data in CSV format. To make the button functional
@@ -102,12 +104,6 @@ const props = defineProps<{
102
104
  * This component serves as the target for teleporting the button.
103
105
  */
104
106
  showExportButton?: boolean;
105
- /**
106
- * Force use of client-side row model.
107
- * Required for reliable work of focusRow.
108
- * Auto-enabled when selectedRows provided.
109
- */
110
- clientSideModel?: boolean;
111
107
 
112
108
  /**
113
109
  * The AxisId property is used to configure and display the PlAgTextAndButtonCell component
@@ -141,137 +137,27 @@ const props = defineProps<{
141
137
  const { settings } = toRefs(props);
142
138
  const emit = defineEmits<{
143
139
  rowDoubleClicked: [key?: PTableKey];
144
- columnsChanged: [columns: PTableColumnSpec[]];
140
+ columnsChanged: [info: PlDataTableColumnsInfo];
145
141
  cellButtonClicked: [key?: PTableKey];
146
142
  }>();
147
143
 
148
- /** State upgrader */ (() => {
149
- if (!tableState.value.pTableParams) tableState.value.pTableParams = {};
150
- })();
144
+ const { gridState, sheetsState } = useTableState(tableState, settings);
151
145
 
152
- function makeSorting(state?: SortState): PTableSorting[] {
153
- return (
154
- state?.sortModel.map((item) => {
155
- const { spec, ...column } = parseJson(
156
- item.colId as PTableColumnSpecJson,
157
- );
158
- const _ = spec;
159
- return {
160
- column,
161
- ascending: item.sort === 'asc',
162
- naAndAbsentAreLeastValues: item.sort === 'asc',
146
+ const sheetsSettings = computed<PlDataTableSheetsSettings>(() => {
147
+ const settingsCopy = { ...settings.value };
148
+ return settingsCopy.sourceId
149
+ ? {
150
+ sheets: settingsCopy.sheets ?? [],
151
+ cachedState: [...sheetsState.value],
152
+ }
153
+ : {
154
+ sheets: [],
155
+ cachedState: [],
163
156
  };
164
- }) ?? []
165
- );
166
- }
167
-
168
- const gridState = computed<PlDataTableGridStateWithoutSheets>({
169
- get: () => {
170
- const state = tableState.value;
171
- return {
172
- sourceId: state.gridState.sourceId,
173
- columnOrder: state.gridState.columnOrder,
174
- sort: state.gridState.sort,
175
- columnVisibility: state.gridState.columnVisibility,
176
- };
177
- },
178
- set: (gridState) => {
179
- // do not apply driver sorting for client side rendering
180
- const sorting = settings.value?.sourceType !== 'ptable'
181
- || gridOptions.value.rowModelType === 'clientSide'
182
- ? undefined
183
- : makeSorting(gridState.sort);
184
-
185
- const oldState = tableState.value;
186
-
187
- const newState = {
188
- ...oldState,
189
- gridState: { ...oldState.gridState, ...gridState },
190
- pTableParams: { ...oldState.pTableParams, sorting },
191
- };
192
-
193
- // Note: the table constantly emits an unchanged state, so I added this
194
- if (!isJsonEqual(oldState, newState)) {
195
- tableState.value = newState;
196
- }
197
- },
198
- });
199
-
200
- const makeSheetId = (axis: AxisId) => canonicalize(getAxisId(axis))!;
201
-
202
- function makeFilters(
203
- sheetsState: Record<string, string | number>,
204
- ): PTableRecordFilter[] | undefined {
205
- if (settings.value?.sourceType !== 'ptable') return undefined;
206
- return (
207
- settings.value.sheets?.map((sheet) => ({
208
- type: 'bySingleColumnV2',
209
- column: {
210
- type: 'axis',
211
- id: sheet.axis,
212
- },
213
- predicate: {
214
- operator: 'Equal',
215
- reference: sheetsState[makeSheetId(sheet.axis)],
216
- },
217
- })) ?? []
218
- );
219
- }
220
-
221
- const sheetsState = computed({
222
- get: () => tableState.value.gridState.sheets ?? {},
223
- set: (sheetsState) => {
224
- const filters = makeFilters(sheetsState);
225
-
226
- const oldState = tableState.value;
227
- tableState.value = {
228
- ...oldState,
229
- gridState: { ...oldState.gridState, sheets: sheetsState },
230
- pTableParams: { ...oldState.pTableParams, filters },
231
- };
232
- },
233
157
  });
234
158
 
235
- const hasSheets = (
236
- settings?: Readonly<PlAgDataTableSettings>,
237
- ): settings is PlAgDataTableSettingsPTable => {
238
- return settings?.sourceType === 'ptable'
239
- && !!settings.sheets
240
- && settings.sheets.length > 0;
241
- };
242
-
243
- watch(
244
- () => settings.value,
245
- (settings, oldSettings) => {
246
- if (settings?.sourceType !== 'ptable' || !settings.sheets) {
247
- sheetsState.value = {};
248
- return;
249
- }
250
-
251
- if (
252
- oldSettings && oldSettings.sourceType === 'ptable'
253
- && lodash.isEqual(settings.sheets, oldSettings.sheets)
254
- ) return;
255
-
256
- const oldSheetsState = sheetsState.value;
257
- const newSheetsState: Record<string, string | number> = {};
258
- for (const sheet of settings.sheets) {
259
- const sheetId = makeSheetId(sheet.axis);
260
- newSheetsState[sheetId] = oldSheetsState[sheetId]
261
- ?? sheet.defaultValue
262
- ?? sheet.options[0]?.value;
263
- }
264
- sheetsState.value = newSheetsState;
265
- },
266
- { immediate: true },
267
- );
268
-
269
- const gridApi = shallowRef<GridApi>();
270
-
271
- const gridApiDef = shallowRef(new Deferred<GridApi>());
272
-
159
+ const gridApi = shallowRef<GridApi<PlAgDataTableV2Row> | null>(null);
273
160
  const firstDataRenderedTracker = makeOnceTracker<GridApi<PlAgDataTableV2Row>>();
274
-
275
161
  const gridOptions = shallowRef<GridOptions<PlAgDataTableV2Row>>({
276
162
  animateRows: false,
277
163
  suppressColumnMoveAnimation: true,
@@ -281,36 +167,17 @@ const gridOptions = shallowRef<GridOptions<PlAgDataTableV2Row>>({
281
167
  rowSelection: selection.value !== undefined
282
168
  ? {
283
169
  mode: 'multiRow',
170
+ selectAll: 'all',
171
+ groupSelects: 'self',
284
172
  checkboxes: false,
285
173
  headerCheckbox: false,
286
174
  enableClickSelection: false,
287
175
  }
288
176
  : undefined,
289
- selectionColumnDef: {
290
- mainMenuItems: [],
291
- contextMenuItems: [],
292
- pinned: 'left',
293
- lockPinned: true,
294
- suppressSizeToFit: true,
295
- suppressAutoSize: true,
296
- sortable: false,
297
- resizable: false,
298
- },
299
- onRowDataUpdated: (event) => {
300
- const selectionValue = selection.value;
301
- if (selectionValue) {
302
- const nodes = selectionValue
303
- .selectedKeys
304
- .map((rowKey) => event.api.getRowNode(makeRowId(rowKey)))
305
- .filter((node) => !!node);
306
- event.api.setNodesSelected({ nodes, newValue: true });
307
- }
308
- },
309
177
  onSelectionChanged: (event) => {
310
178
  if (selection.value) {
311
- const selectedKeys = event.api.getSelectedNodes()
312
- .map((rowNode) => rowNode.data?.key)
313
- .filter((rowKey) => !!rowKey);
179
+ const state = event.api.getServerSideSelectionState();
180
+ const selectedKeys = state?.toggledNodes?.map((nodeId) => parseJson(nodeId as PTableKeyJson)) ?? [];
314
181
  selection.value = { ...selection.value, selectedKeys };
315
182
  }
316
183
  },
@@ -326,9 +193,11 @@ const gridOptions = shallowRef<GridOptions<PlAgDataTableV2Row>>({
326
193
  localeText: {
327
194
  loadingError: '...',
328
195
  },
329
- rowModelType: 'clientSide',
330
- maxBlocksInCache: 1000,
331
- // cacheBlockSize: 100,
196
+ rowModelType: 'serverSide',
197
+ // cacheBlockSize should be tha same as PlMultiSequenceAlignment limit
198
+ // so that selectAll will add all rows to selection
199
+ cacheBlockSize: 1000,
200
+ maxBlocksInCache: 100,
332
201
  blockLoadDebounceMillis: 500,
333
202
  serverSideSortAllLevels: true,
334
203
  suppressServerSideFullWidthLoadingRow: true,
@@ -354,101 +223,147 @@ const gridOptions = shallowRef<GridOptions<PlAgDataTableV2Row>>({
354
223
  { statusPanel: PlAgRowCount, align: 'left' },
355
224
  ],
356
225
  },
226
+ onGridReady: (event) => {
227
+ const api = event.api;
228
+ trackFirstDataRendered(api, firstDataRenderedTracker);
229
+ autoSizeRowNumberColumn(api);
230
+ const setGridOption = (
231
+ key: ManagedGridOptionKey,
232
+ value: GridOptions[ManagedGridOptionKey],
233
+ ) => {
234
+ const options = { ...gridOptions.value };
235
+ options[key] = value;
236
+ gridOptions.value = options;
237
+ api.setGridOption(key, value);
238
+ };
239
+ const updateGridOptions = (options: ManagedGridOptions) => {
240
+ gridOptions.value = {
241
+ ...gridOptions.value,
242
+ ...options,
243
+ };
244
+ api.updateGridOptions(options);
245
+ };
246
+ gridApi.value = new Proxy(api, {
247
+ get(target, prop, receiver) {
248
+ switch (prop) {
249
+ case 'setGridOption':
250
+ return setGridOption;
251
+ case 'updateGridOptions':
252
+ return updateGridOptions;
253
+ default:
254
+ return Reflect.get(target, prop, receiver);
255
+ }
256
+ },
257
+ });
258
+ },
259
+ onStateUpdated: (event) => {
260
+ gridOptions.value.initialState = gridState.value = makePartialState(
261
+ event.state,
262
+ );
263
+ event.api.autoSizeColumns(
264
+ event.api.getAllDisplayedColumns().filter(
265
+ (column) => column.getColId() !== PlAgDataTableRowNumberColId,
266
+ ),
267
+ );
268
+ },
269
+ onGridPreDestroyed: (event) => {
270
+ gridOptions.value.initialState = gridState.value = makePartialState(
271
+ event.api.getState(),
272
+ );
273
+ gridApi.value = null;
274
+ },
357
275
  });
358
276
 
359
- const onGridReady = (event: GridReadyEvent) => {
360
- const api = event.api;
361
- trackFirstDataRendered(api, firstDataRenderedTracker);
362
- autoSizeRowNumberColumn(api);
363
- gridApi.value = new Proxy(api, {
364
- get(target, prop, receiver) {
365
- switch (prop) {
366
- case 'setGridOption':
367
- return (
368
- key: ManagedGridOptionKey,
369
- value: GridOptions[ManagedGridOptionKey],
370
- ) => {
371
- const options = gridOptions.value;
372
- options[key] = value;
373
- gridOptions.value = options;
374
- api.setGridOption(key, value);
375
- };
376
- case 'updateGridOptions':
377
- return (options: ManagedGridOptions) => {
378
- gridOptions.value = {
379
- ...gridOptions.value,
380
- ...options,
381
- };
382
- api.updateGridOptions(options);
383
- };
384
- default:
385
- return Reflect.get(target, prop, receiver);
386
- }
387
- },
388
- });
389
-
390
- gridApiDef.value.resolve(gridApi.value);
391
- };
392
-
393
- const makePartialState = (state: GridState) => {
277
+ // Restore proper types erased by AgGrid
278
+ function makePartialState(state: GridState): PlDataTableGridStateCore {
394
279
  return {
395
- sourceId: gridState.value.sourceId,
396
- columnOrder: state.columnOrder,
397
- sort: state.sort,
280
+ columnOrder: state.columnOrder as {
281
+ orderedColIds: PTableColumnSpecJson[];
282
+ } | undefined,
283
+ sort: state.sort as {
284
+ sortModel: {
285
+ colId: PTableColumnSpecJson;
286
+ sort: 'asc' | 'desc';
287
+ }[];
288
+ } | undefined,
398
289
  columnVisibility: state.columnVisibility as {
399
290
  hiddenColIds: PTableColumnSpecJson[];
400
- } ?? { hiddenColIds: [] },
291
+ } | undefined,
401
292
  };
402
293
  };
403
294
 
404
- const onStateUpdated = (event: StateUpdatedEvent) => {
405
- gridOptions.value.initialState = gridState.value = makePartialState(
406
- event.state,
407
- );
408
- event.api.autoSizeColumns(
409
- event.api.getAllDisplayedColumns().filter(
410
- (column) => column.getColId() !== PlAgDataTableRowNumberColId,
411
- ),
412
- );
413
- };
414
-
415
- const onGridPreDestroyed = () => {
416
- gridOptions.value.initialState = gridState.value = makePartialState(
417
- gridApi.value!.getState(),
418
- );
419
- gridApi.value = undefined;
420
- gridApiDef.value = new Deferred<GridApi>();
421
- };
422
-
423
- defineExpose<PlAgDataTableV2Controller>({
424
- focusRow: (rowKey) => focusRow(makeRowId(rowKey), firstDataRenderedTracker),
425
- });
426
-
295
+ // Reload AgGrid when new state arrives from server
427
296
  const reloadKey = ref(0);
428
297
  watch(
429
298
  () => [gridApi.value, gridState.value] as const,
430
- (state, oldState) => {
431
- if (lodash.isEqual(state, oldState)) return;
432
-
433
- const [gridApi, gridState] = state;
434
- if (!gridApi) return;
435
-
299
+ ([gridApi, gridState]) => {
300
+ if (!gridApi || gridApi.isDestroyed()) return;
436
301
  const selfState = makePartialState(gridApi.getState());
437
- if (lodash.isEqual(gridState, selfState)) return;
302
+ if (!isJsonEqual(gridState, {}) && !isJsonEqual(gridState, selfState)) {
303
+ gridOptions.value.initialState = gridState;
304
+ ++reloadKey.value;
305
+ }
306
+ },
307
+ );
308
+
309
+ // Make loadingOverlayComponentParams reactive
310
+ let oldOptions: GridOptions | null = null;
311
+ watch(
312
+ () => [gridApi.value, gridOptions.value] as const,
313
+ ([gridApi, options]) => {
314
+ // Wait for AgGrid reinitialization, gridApi will eventially become initialized
315
+ if (!gridApi || gridApi.isDestroyed()) return;
316
+ if (options.loading && oldOptions?.loading && !isJsonEqual(
317
+ options.loadingOverlayComponentParams,
318
+ oldOptions?.loadingOverlayComponentParams,
319
+ )) {
320
+ // Hack to reapply loadingOverlayComponentParams
321
+ gridApi.setGridOption('loading', false);
322
+ gridApi.setGridOption('loading', true);
323
+ }
324
+ oldOptions = options;
325
+ },
326
+ { immediate: true },
327
+ );
438
328
 
439
- gridOptions.value.initialState = gridState;
440
- ++reloadKey.value;
329
+ // Make cellRendererSelector reactive
330
+ const cellRendererSelector = computed(() => props.cellRendererSelector ?? null);
331
+ watch(
332
+ () => [gridApi.value, cellRendererSelector.value] as const,
333
+ ([gridApi, cellRendererSelector]) => {
334
+ if (!gridApi || gridApi.isDestroyed()) return;
335
+ gridApi.setGridOption('defaultColDef', {
336
+ ...gridOptions.value.defaultColDef,
337
+ cellRendererSelector: cellRendererSelector ?? undefined,
338
+ });
441
339
  },
442
340
  );
341
+
342
+ defineExpose<PlAgDataTableV2Controller>({
343
+ focusRow: (rowKey) => focusRow(makeRowId(rowKey), firstDataRenderedTracker),
344
+ });
345
+
346
+ // Propagate columns for filter component
443
347
  watch(
444
348
  () => gridOptions.value,
445
349
  (options, oldOptions) => {
446
- if (!oldOptions) return;
447
- if (options.rowModelType != oldOptions.rowModelType) ++reloadKey.value;
448
350
  if (
449
351
  options.columnDefs
450
- && !lodash.isEqual(options.columnDefs, oldOptions.columnDefs)
352
+ && !isJsonEqual(options.columnDefs, oldOptions?.columnDefs)
451
353
  ) {
354
+ const sourceId = settings.value.sourceId;
355
+ if (sourceId === null) {
356
+ if (selection.value) {
357
+ selection.value = {
358
+ axesSpec: [],
359
+ selectedKeys: [],
360
+ };
361
+ }
362
+ return emit('columnsChanged', {
363
+ sourceId: null,
364
+ columns: [],
365
+ });
366
+ }
452
367
  const isColDef = (def: ColDef | ColGroupDef): def is ColDef =>
453
368
  !('children' in def);
454
369
  const colDefs = options.columnDefs?.filter(isColDef) ?? [];
@@ -456,228 +371,161 @@ watch(
456
371
  .map((def) => def.colId)
457
372
  .filter((colId) => colId !== undefined)
458
373
  .filter((colId) => colId !== PlAgDataTableRowNumberColId)
459
- .filter((colId) => !isColumnSelectionCol(colId))
460
374
  .map((colId) => parseJson(colId as PTableColumnSpecJson))
461
375
  ?? [];
462
376
  const axesSpec = columns
463
377
  .filter((column) => column.type === 'axis')
464
378
  .map((axis) => axis.spec);
465
379
  if (selection.value) {
466
- selection.value = { ...selection.value, axesSpec };
380
+ selection.value = {
381
+ ...selection.value,
382
+ axesSpec,
383
+ };
467
384
  }
468
- // axesSpec.value = axes;
469
- emit('columnsChanged', columns);
470
- }
471
- if (
472
- !lodash.isEqual(
473
- options.loadingOverlayComponentParams,
474
- oldOptions.loadingOverlayComponentParams,
475
- ) && options.loading
476
- ) {
477
- gridApi.value?.setGridOption('loading', false);
478
- nextTick(() => gridApi.value?.setGridOption('loading', true));
385
+ emit('columnsChanged', {
386
+ sourceId,
387
+ columns,
388
+ });
479
389
  }
480
390
  },
481
391
  { immediate: true },
482
392
  );
483
393
 
484
- const onSheetChanged = (sheetId: string, newValue: string | number) => {
485
- const state = sheetsState.value;
486
- if (state[sheetId] === newValue) return;
487
- state[sheetId] = newValue;
488
- sheetsState.value = state;
489
- return gridApi.value?.updateGridOptions({
490
- loading: true,
491
- loadingOverlayComponentParams: {
492
- ...gridOptions.value.loadingOverlayComponentParams,
493
- notReady: false,
494
- } satisfies PlAgOverlayLoadingParams,
495
- });
496
- };
497
-
498
- let generation = 0;
499
- let oldSettings: PlAgDataTableSettings | undefined = undefined;
394
+ // Update AgGrid when settings change
395
+ let oldSettings: PlDataTableSettingsV2 | null = null;
396
+ const generation = ref(0);
500
397
  watch(
501
- () => [settings.value] as const,
502
- async (state) => {
398
+ () => [gridApi.value, settings.value] as const,
399
+ ([gridApi, settings]) => {
400
+ // Wait for AgGrid reinitialization, gridApi will eventially become initialized
401
+ if (!gridApi || gridApi.isDestroyed()) return;
402
+ // Verify that this is not a false watch trigger
403
+ if (isJsonEqual(settings, oldSettings)) return;
404
+ ++generation.value;
503
405
  try {
504
- const [settings] = state;
505
-
506
- const gridApi = await gridApiDef.value.promise;
507
-
508
- if (gridApi.isDestroyed()) {
509
- console.warn('gridApi is destroyed');
510
- return;
406
+ // Hide no rows overlay if it is shown, or else loading overlay will not be shown
407
+ gridApi.hideOverlay();
408
+
409
+ // No data source selected -> reset state to default
410
+ if (!settings.sourceId) {
411
+ return gridApi.updateGridOptions({
412
+ loading: true,
413
+ loadingOverlayComponentParams: {
414
+ ...gridOptions.value.loadingOverlayComponentParams,
415
+ notReady: true,
416
+ } satisfies PlAgOverlayLoadingParams,
417
+ columnDefs: undefined,
418
+ serverSideDatasource: undefined,
419
+ });
511
420
  }
512
421
 
513
- if (lodash.isEqual(settings, oldSettings)) {
514
- return;
515
- }
516
-
517
- const localGeneration = generation = generation + 1;
518
- oldSettings = settings;
519
-
520
- const sourceType = settings?.sourceType;
521
-
522
- switch (sourceType) {
523
- case undefined:
524
- return gridApi.updateGridOptions({
525
- loading: true,
526
- loadingOverlayComponentParams: {
527
- ...gridOptions.value.loadingOverlayComponentParams,
528
- notReady: true,
529
- } satisfies PlAgOverlayLoadingParams,
530
- columnDefs: [],
531
- rowData: undefined,
532
- datasource: undefined,
533
- });
534
-
535
- case 'ptable': {
536
- if (!settings?.model) {
537
- return gridApi.updateGridOptions({
538
- loading: true,
539
- loadingOverlayComponentParams: {
540
- ...gridOptions.value.loadingOverlayComponentParams,
541
- notReady: false,
542
- } satisfies PlAgOverlayLoadingParams,
543
- columnDefs: [],
544
- rowData: undefined,
545
- datasource: undefined,
546
- });
547
- }
548
-
549
- gridApi.updateGridOptions({
550
- loading: true,
551
- loadingOverlayComponentParams: {
552
- ...gridOptions.value.loadingOverlayComponentParams,
553
- notReady: false,
554
- } satisfies PlAgOverlayLoadingParams,
555
- });
556
-
557
- const options = await updatePFrameGridOptions(
558
- getRawPlatformaInstance().pFrameDriver,
559
- settings.model,
560
- settings.sheets ?? [],
561
- !!props.clientSideModel || !!selection.value,
562
- gridState,
563
- {
564
- showCellButtonForAxisId: props.showCellButtonForAxisId,
565
- cellButtonInvokeRowsOnDoubleClick:
566
- props.cellButtonInvokeRowsOnDoubleClick,
567
- trigger: (key?: PTableKey) => emit('cellButtonClicked', key),
568
- } satisfies PlAgCellButtonAxisParams,
569
- ).catch((err) => {
570
- if (localGeneration !== generation) return;
571
- gridApi.updateGridOptions({
572
- loading: false,
573
- loadingOverlayComponentParams: {
574
- ...gridOptions.value.loadingOverlayComponentParams,
575
- notReady: false,
576
- } satisfies PlAgOverlayLoadingParams,
577
- });
578
- throw err;
579
- });
580
- if (localGeneration !== generation) return;
581
-
582
- return gridApi.updateGridOptions({
583
- loading: false,
584
- loadingOverlayComponentParams: {
585
- ...gridOptions.value.loadingOverlayComponentParams,
586
- notReady: false,
587
- } satisfies PlAgOverlayLoadingParams,
588
- ...options,
422
+ // Data source changed -> show full page loader, clear selection
423
+ if (settings.sourceId !== oldSettings?.sourceId) {
424
+ gridApi.updateGridOptions({
425
+ loading: true,
426
+ loadingOverlayComponentParams: {
427
+ ...gridOptions.value.loadingOverlayComponentParams,
428
+ notReady: false,
429
+ } satisfies PlAgOverlayLoadingParams,
430
+ });
431
+ if (selection.value) {
432
+ gridApi.setServerSideSelectionState({
433
+ selectAll: false,
434
+ toggledNodes: [],
589
435
  });
590
436
  }
437
+ }
591
438
 
592
- default:
593
- throw Error(`unsupported source type: ${sourceType satisfies never}`);
439
+ // Model updated -> show skeletons instead of data
440
+ if (!settings.model) {
441
+ const state = gridApi.getServerSideGroupLevelState();
442
+ const rowCount = state.length > 0 ? state[0].rowCount : undefined;
443
+ return gridApi.updateGridOptions({
444
+ serverSideDatasource: {
445
+ getRows: (params) => {
446
+ params.success({ rowData: [], rowCount });
447
+ },
448
+ },
449
+ });
594
450
  }
451
+
452
+ // Model ready -> calculate new state
453
+ const stateGeneration = generation.value;
454
+ calculateGridOptions(
455
+ generation,
456
+ getRawPlatformaInstance().pFrameDriver,
457
+ settings.model,
458
+ settings.sheets ?? [],
459
+ gridState.value.columnVisibility?.hiddenColIds,
460
+ {
461
+ showCellButtonForAxisId: props.showCellButtonForAxisId,
462
+ cellButtonInvokeRowsOnDoubleClick:
463
+ props.cellButtonInvokeRowsOnDoubleClick,
464
+ trigger: (key?: PTableKey) => emit('cellButtonClicked', key),
465
+ } satisfies PlAgCellButtonAxisParams,
466
+ ).then((options) => {
467
+ if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;
468
+ return gridApi.updateGridOptions({
469
+ ...options,
470
+ });
471
+ }).catch((error: unknown) => {
472
+ if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;
473
+ console.trace(error);
474
+ }).finally(() => {
475
+ if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;
476
+ gridApi.updateGridOptions({
477
+ loading: false,
478
+ });
479
+ });
595
480
  } catch (error: unknown) {
596
- // How should we handle possible errors?
597
481
  console.trace(error);
482
+ } finally {
483
+ oldSettings = settings;
598
484
  }
599
485
  },
600
- { immediate: true, deep: true },
601
- );
602
-
603
- watch(
604
- () => props.cellRendererSelector,
605
- (cellRendererSelector) => {
606
- if (!gridApi.value) {
607
- return;
608
- }
609
- gridApi.value.setGridOption('defaultColDef', {
610
- ...gridOptions.value.defaultColDef,
611
- cellRendererSelector,
612
- });
613
- },
614
486
  );
615
487
  </script>
616
488
 
617
489
  <template>
618
- <div class="ap-ag-data-table-container">
490
+ <div :class="$style.container">
619
491
  <PlAgGridColumnManager
620
492
  v-if="gridApi && showColumnsPanel"
621
493
  :api="gridApi"
622
494
  :width="columnsPanelWidth"
623
495
  />
624
- <PlAgCsvExporter v-if="gridApi && showExportButton" :api="gridApi" />
625
- <div
626
- v-if="
627
- hasSheets(settings)
628
- || $slots['before-sheets']
629
- || $slots['after-sheets']
630
- "
631
- class="ap-ag-data-table-sheets"
496
+ <PlAgCsvExporter
497
+ v-if="gridApi && showExportButton"
498
+ :api="gridApi"
499
+ />
500
+ <PlAgDataTableSheets
501
+ v-model="sheetsState"
502
+ :settings="sheetsSettings"
632
503
  >
633
- <slot name="before-sheets" />
634
- <template v-if="hasSheets(settings)">
635
- <PlDropdownLine
636
- v-for="(sheet, i) in settings.sheets"
637
- :key="i"
638
- :model-value="sheetsState[makeSheetId(sheet.axis)]"
639
- :options="sheet.options"
640
- :prefix="
641
- (sheet.axis.annotations?.['pl7.app/label']?.trim()
642
- ?? `Unlabeled axis ${i}`) + ':'
643
- "
644
- @update:model-value="
645
- (newValue) =>
646
- onSheetChanged(makeSheetId(sheet.axis), newValue)
647
- "
648
- />
504
+ <template #before>
505
+ <slot name="before-sheets" />
506
+ </template>
507
+ <template #after>
508
+ <slot name="after-sheets" />
649
509
  </template>
650
- <slot name="after-sheets" />
651
- </div>
510
+ </PlAgDataTableSheets>
652
511
  <AgGridVue
653
512
  :key="reloadKey"
654
513
  :theme="AgGridTheme"
655
- class="ap-ag-data-table-grid"
514
+ :class="$style.grid"
656
515
  :grid-options="gridOptions"
657
- @grid-ready="onGridReady"
658
- @state-updated="onStateUpdated"
659
- @grid-pre-destroyed="onGridPreDestroyed"
660
516
  />
661
517
  </div>
662
518
  </template>
663
519
 
664
- <style lang="css" scoped>
665
- .ap-ag-data-table-container {
520
+ <style lang="css" module>
521
+ .container {
666
522
  display: flex;
667
523
  flex-direction: column;
668
524
  height: 100%;
669
525
  gap: 12px;
670
526
  }
671
527
 
672
- .ap-ag-data-table-sheets {
673
- display: flex;
674
- flex-direction: row;
675
- gap: 12px;
676
- flex-wrap: wrap;
677
- z-index: 3;
678
- }
679
-
680
- .ap-ag-data-table-grid {
528
+ .grid {
681
529
  flex: 1;
682
530
  }
683
531
  </style>