@platforma-sdk/ui-vue 1.80.9 → 1.80.10
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/.turbo/turbo-build.log +14 -14
- package/.turbo/turbo-formatter$colon$check.log +2 -2
- package/.turbo/turbo-linter$colon$check.log +2 -2
- package/.turbo/turbo-types$colon$check.log +1 -1
- package/CHANGELOG.md +15 -0
- package/dist/components/PlAgCsvExporter/PlAgCsvExporter.js.map +1 -1
- package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue.d.ts +2 -1
- package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue.d.ts.map +1 -1
- package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue2.js +6 -2
- package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue2.js.map +1 -1
- package/dist/components/PlAgCsvExporter/export-csv.d.ts +16 -5
- package/dist/components/PlAgCsvExporter/export-csv.d.ts.map +1 -1
- package/dist/components/PlAgCsvExporter/export-csv.js +48 -39
- package/dist/components/PlAgCsvExporter/export-csv.js.map +1 -1
- package/dist/components/PlAgDataTable/PlAgDataTableV2.js.map +1 -1
- package/dist/components/PlAgDataTable/PlAgDataTableV2.style.js.map +1 -1
- package/dist/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts.map +1 -1
- package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js +22 -17
- package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js.map +1 -1
- package/dist/components/PlAgDataTable/sources/table-source-v2.js +1 -1
- package/dist/components/PlAnnotations/components/PlAnnotations.vue2.js.map +1 -1
- package/dist/index.js +8 -8
- package/dist/lib.js +4 -4
- package/package.json +9 -9
- package/src/components/PlAgCsvExporter/PlAgCsvExporter.vue +3 -2
- package/src/components/PlAgCsvExporter/export-csv.ts +40 -9
- package/src/components/PlAgDataTable/PlAgDataTableV2.vue +4 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
WARN Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
|
|
2
2
|
|
|
3
|
-
> @platforma-sdk/ui-vue@1.80.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.80.10 build /home/runner/_work/platforma/platforma/sdk/ui-vue
|
|
4
4
|
> ts-builder build --target browser-lib
|
|
5
5
|
|
|
6
6
|
Building browser-lib project...
|
|
@@ -16,7 +16,7 @@ Building browser-lib project...
|
|
|
16
16
|
rendering chunks...
|
|
17
17
|
|
|
18
18
|
[vite:dts] Start generate declaration files...
|
|
19
|
-
[vite:dts] Declaration files built in
|
|
19
|
+
[vite:dts] Declaration files built in 4868ms.
|
|
20
20
|
|
|
21
21
|
computing gzip size...
|
|
22
22
|
dist/components/PlAnnotations/components/PlAnnotations.vue?vue&type=style&index=0&lang.css 0.04 kB │ gzip: 0.06 kB
|
|
@@ -83,7 +83,7 @@ dist/components/PlAgRowNumHeader.js
|
|
|
83
83
|
dist/components/PlAgCellFile/PlAgCellFile.js 0.21 kB │ gzip: 0.16 kB │ map: 2.49 kB
|
|
84
84
|
dist/components/PlAgDataTable/PlAgRowCount.js 0.21 kB │ gzip: 0.17 kB │ map: 1.72 kB
|
|
85
85
|
dist/plugins/Monetization/EndOfPeriod.vue_vue_type_style_index_0_lang.module.js 0.22 kB │ gzip: 0.18 kB │ map: 0.42 kB
|
|
86
|
-
dist/components/PlAgCsvExporter/PlAgCsvExporter.js 0.22 kB │ gzip: 0.16 kB │ map: 1.
|
|
86
|
+
dist/components/PlAgCsvExporter/PlAgCsvExporter.js 0.22 kB │ gzip: 0.16 kB │ map: 1.59 kB
|
|
87
87
|
dist/components/PlAnnotations/components/PlAnnotations.vue_vue_type_style_index_0_lang.module.js 0.22 kB │ gzip: 0.18 kB │ map: 0.42 kB
|
|
88
88
|
dist/components/PlAgCellProgress/PlAgCellProgress.js 0.23 kB │ gzip: 0.16 kB │ map: 0.55 kB
|
|
89
89
|
dist/components/PlAgColumnHeader/PlAgColumnHeader.js 0.23 kB │ gzip: 0.17 kB │ map: 3.22 kB
|
|
@@ -121,7 +121,7 @@ dist/components/PlAdvancedFilter/FilterEditor.js
|
|
|
121
121
|
dist/components/PlAdvancedFilter/OperandButton.js 0.35 kB │ gzip: 0.24 kB │ map: 1.31 kB
|
|
122
122
|
dist/components/PlAnnotations/components/FilterSidebar.js 0.35 kB │ gzip: 0.24 kB │ map: 5.40 kB
|
|
123
123
|
dist/components/PlAnnotations/components/PlAnnotations.js 0.35 kB │ gzip: 0.24 kB │ map: 3.11 kB
|
|
124
|
-
dist/components/PlAgDataTable/PlAgDataTableV2.js 0.35 kB │ gzip: 0.25 kB │ map: 22.
|
|
124
|
+
dist/components/PlAgDataTable/PlAgDataTableV2.js 0.35 kB │ gzip: 0.25 kB │ map: 22.36 kB
|
|
125
125
|
dist/plugins/Monetization/UserCabinetCard.js 0.35 kB │ gzip: 0.24 kB │ map: 3.00 kB
|
|
126
126
|
dist/components/PlAdvancedFilter/PlAdvancedFilter.js 0.36 kB │ gzip: 0.24 kB │ map: 13.36 kB
|
|
127
127
|
dist/components/PlAgDataTable/PlAgOverlayNoRows.js 0.36 kB │ gzip: 0.21 kB │ map: 0.74 kB
|
|
@@ -176,7 +176,6 @@ dist/components/PlAgGridColumnManager/useFilteredItems.js
|
|
|
176
176
|
dist/lib/util/helpers/dist/objects.js 1.20 kB │ gzip: 0.58 kB │ map: 9.83 kB
|
|
177
177
|
dist/createModel.js 1.22 kB │ gzip: 0.61 kB │ map: 3.11 kB
|
|
178
178
|
dist/utils.js 1.26 kB │ gzip: 0.62 kB │ map: 3.40 kB
|
|
179
|
-
dist/components/PlAgCsvExporter/PlAgCsvExporter.vue_vue_type_script_setup_true_lang.js 1.28 kB │ gzip: 0.72 kB │ map: 2.05 kB
|
|
180
179
|
dist/lib/util/helpers/dist/prettyBytes.js 1.29 kB │ gzip: 0.67 kB │ map: 3.79 kB
|
|
181
180
|
dist/AgGridVue/AgGridTheme.js 1.30 kB │ gzip: 0.59 kB │ map: 2.07 kB
|
|
182
181
|
dist/components/PlBtnExportArchive/Summary.vue_vue_type_script_setup_true_lang.js 1.31 kB │ gzip: 0.65 kB │ map: 2.35 kB
|
|
@@ -184,6 +183,7 @@ dist/components/PlAgDataTable/PlAgRowCount.vue_vue_type_script_setup_true_lang.j
|
|
|
184
183
|
dist/internal/UpdateSerializer.js 1.33 kB │ gzip: 0.61 kB │ map: 4.28 kB
|
|
185
184
|
dist/components/PlAgDataTable/PlAgOverlayLoading.vue_vue_type_script_setup_true_lang.js 1.33 kB │ gzip: 0.76 kB │ map: 2.49 kB
|
|
186
185
|
dist/components/PlAgDataTable/types.js 1.33 kB │ gzip: 0.53 kB │ map: 7.63 kB
|
|
186
|
+
dist/components/PlAgCsvExporter/PlAgCsvExporter.vue_vue_type_script_setup_true_lang.js 1.34 kB │ gzip: 0.74 kB │ map: 2.16 kB
|
|
187
187
|
dist/components/PlBtnExportArchive/Item.vue_vue_type_script_setup_true_lang.js 1.36 kB │ gzip: 0.63 kB │ map: 2.58 kB
|
|
188
188
|
dist/components/PlAgTextAndButtonCell/PlAgTextAndButtonCell.vue_vue_type_script_setup_true_lang.js 1.46 kB │ gzip: 0.77 kB │ map: 3.56 kB
|
|
189
189
|
dist/internal/createAppModel.js 1.48 kB │ gzip: 0.75 kB │ map: 3.89 kB
|
|
@@ -202,11 +202,11 @@ dist/components/PlAgDataTable/PlAgDataTableSheets.vue_vue_type_script_setup_true
|
|
|
202
202
|
dist/components/PlDatasetSelector/PlDatasetSelector.vue_vue_type_script_setup_true_lang.js 2.58 kB │ gzip: 1.07 kB │ map: 6.02 kB
|
|
203
203
|
dist/components/PlAnnotations/components/PlAnnotations.vue_vue_type_script_setup_true_lang.js 2.69 kB │ gzip: 1.12 kB │ map: 4.16 kB
|
|
204
204
|
dist/lib.js 2.73 kB │ gzip: 0.57 kB │ map: 3.98 kB
|
|
205
|
-
dist/components/PlAgCsvExporter/export-csv.js 2.83 kB │ gzip: 1.27 kB │ map: 9.11 kB
|
|
206
205
|
dist/components/PlAgDataTable/compositions/useGrid.js 2.91 kB │ gzip: 1.29 kB │ map: 6.51 kB
|
|
207
206
|
dist/components/PlAgColumnHeader/PlAgColumnHeader.vue_vue_type_script_setup_true_lang.js 2.93 kB │ gzip: 1.26 kB │ map: 7.97 kB
|
|
208
207
|
dist/components/PlAgDataTable/sources/row-number.js 3.01 kB │ gzip: 1.31 kB │ map: 7.47 kB
|
|
209
208
|
dist/defineApp.js 3.01 kB │ gzip: 1.05 kB │ map: 14.38 kB
|
|
209
|
+
dist/components/PlAgCsvExporter/export-csv.js 3.13 kB │ gzip: 1.39 kB │ map: 10.80 kB
|
|
210
210
|
dist/plugins/Monetization/LimitCard.vue_vue_type_script_setup_true_lang.js 3.31 kB │ gzip: 1.15 kB │ map: 9.08 kB
|
|
211
211
|
dist/composition/fileContent.js 3.78 kB │ gzip: 1.45 kB │ map: 12.95 kB
|
|
212
212
|
dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue_vue_type_script_setup_true_lang.js 3.83 kB │ gzip: 1.64 kB │ map: 6.89 kB
|
|
@@ -221,18 +221,18 @@ dist/components/PlBtnExportArchive/PlBtnExportArchive.vue_vue_type_script_setup_
|
|
|
221
221
|
dist/internal/createAppV2.js 5.26 kB │ gzip: 1.98 kB │ map: 17.78 kB
|
|
222
222
|
dist/components/PlAgDataTable/sources/table-state-v2.js 5.66 kB │ gzip: 1.82 kB │ map: 19.14 kB
|
|
223
223
|
dist/internal/createAppV3.js 6.19 kB │ gzip: 2.34 kB │ map: 21.40 kB
|
|
224
|
-
dist/components/PlAgDataTable/sources/table-source-v2.js 7.
|
|
224
|
+
dist/components/PlAgDataTable/sources/table-source-v2.js 7.59 kB │ gzip: 2.88 kB │ map: 27.20 kB
|
|
225
225
|
dist/components/PlAdvancedFilter/PlAdvancedFilter.vue_vue_type_script_setup_true_lang.js 8.54 kB │ gzip: 2.59 kB │ map: 18.44 kB
|
|
226
226
|
dist/components/PlAdvancedFilter/FilterEditor.vue_vue_type_script_setup_true_lang.js 10.58 kB │ gzip: 3.14 kB │ map: 24.73 kB
|
|
227
|
-
dist/components/PlAgDataTable/PlAgDataTableV2.vue_vue_type_script_setup_true_lang.js 12.
|
|
227
|
+
dist/components/PlAgDataTable/PlAgDataTableV2.vue_vue_type_script_setup_true_lang.js 12.54 kB │ gzip: 3.96 kB │ map: 29.97 kB
|
|
228
228
|
|
|
229
229
|
[33m[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugins. Here is a breakdown:
|
|
230
|
-
-
|
|
231
|
-
-
|
|
232
|
-
- vite:vue (
|
|
233
|
-
- vite:css (
|
|
234
|
-
- vite:css-post (
|
|
230
|
+
- sourcemaps (30%)
|
|
231
|
+
- vite:dts (25%)
|
|
232
|
+
- vite:vue (12%)
|
|
233
|
+
- vite:css (9%)
|
|
234
|
+
- vite:css-post (8%)
|
|
235
235
|
See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
236
236
|
[39m
|
|
237
|
-
[32m✓ built in 5.
|
|
237
|
+
[32m✓ built in 5.75s[39m
|
|
238
238
|
Build completed successfully
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
WARN Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
|
|
2
2
|
|
|
3
|
-
> @platforma-sdk/ui-vue@1.80.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.80.10 formatter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
|
|
4
4
|
> ts-builder formatter --check
|
|
5
5
|
|
|
6
6
|
Checking formatting...
|
|
@@ -8,5 +8,5 @@ Checking formatting...
|
|
|
8
8
|
Checking formatting...
|
|
9
9
|
|
|
10
10
|
All matched files use the correct format.
|
|
11
|
-
Finished in
|
|
11
|
+
Finished in 2624ms on 137 files using 16 threads.
|
|
12
12
|
Format check completed successfully
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
WARN Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
|
|
2
2
|
|
|
3
|
-
> @platforma-sdk/ui-vue@1.80.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.80.10 linter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
|
|
4
4
|
> ts-builder linter --check
|
|
5
5
|
|
|
6
6
|
Linting project...
|
|
7
7
|
↳ oxlint --config /home/runner/_work/platforma/platforma/sdk/ui-vue/.oxlintrc.json --deny-warnings
|
|
8
8
|
Found 0 warnings and 0 errors.
|
|
9
|
-
Finished in
|
|
9
|
+
Finished in 28ms on 120 files with 108 rules using 16 threads.
|
|
10
10
|
Linting completed successfully
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
WARN Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
|
|
2
2
|
|
|
3
|
-
> @platforma-sdk/ui-vue@1.80.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.80.10 types:check /home/runner/_work/platforma/platforma/sdk/ui-vue
|
|
4
4
|
> ts-builder type-check --target browser-lib
|
|
5
5
|
|
|
6
6
|
↳ vue-tsc.js --noEmit --project ./tsconfig.json --customConditions ,
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @platforma-sdk/ui-vue
|
|
2
2
|
|
|
3
|
+
## 1.80.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4ac0df5: Native table export now uses the disambiguated column labels shown in the table (from the `PlDataTable` `columnsMeta` sidecar) instead of the raw spec `pl7.app/label`. This fixes export failures when exporting a table with many columns whose intrinsic labels collide — the native `exportPTable` path rejects duplicate header names.
|
|
8
|
+
|
|
9
|
+
`exportPTable`/`writePTableToFs` options gain an optional `headerNames` field (aligned 1:1 with `columnIndices`). It is additive and backward compatible: older runtimes ignore it and keep deriving header names from the column spec.
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [4ac0df5]
|
|
12
|
+
- @milaboratories/pl-model-common@1.47.3
|
|
13
|
+
- @milaboratories/columns-collection-driver@0.2.3
|
|
14
|
+
- @milaboratories/pf-spec-driver@1.4.24
|
|
15
|
+
- @platforma-sdk/model@1.80.10
|
|
16
|
+
- @milaboratories/uikit@2.15.18
|
|
17
|
+
|
|
3
18
|
## 1.80.9
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlAgCsvExporter.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { GridApi } from \"ag-grid-enterprise\";\nimport type { PTableHandle } from \"@platforma-sdk/model\";\nimport { PlBtnGhost, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport { shallowRef, toRefs } from \"vue\";\nimport { isNil } from \"es-toolkit\";\nimport { exportCsv, isCsvExportAvailable } from \"./export-csv\";\nimport type { ExportOptions } from \"./export-csv\";\n\nconst props = defineProps<{\n api: GridApi;\n tableHandle?: PTableHandle;\n}>();\nconst { api: gridApi } = toRefs(props);\nconst csvExportAvailable = isCsvExportAvailable();\n\nconst exporting = shallowRef(false);\nconst initiateExport = () => {\n const nativeOptions: ExportOptions | undefined = !isNil(props.tableHandle)\n ? { tableHandle: props.tableHandle }\n : undefined;\n\n if (isNil(nativeOptions)) {\n return;\n }\n\n exporting.value = true;\n exportCsv(gridApi.value, nativeOptions).finally(() => (exporting.value = false));\n};\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlAgCsvExporter\");\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget && csvExportAvailable\" :to=\"teleportTarget\">\n <PlBtnGhost :loading=\"exporting\" icon=\"export\" @click.stop=\"initiateExport\">\n Export\n </PlBtnGhost>\n </Teleport>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"PlAgCsvExporter.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { GridApi } from \"ag-grid-enterprise\";\nimport type { PTableHandle, PlDataTableColumnsMeta } from \"@platforma-sdk/model\";\nimport { PlBtnGhost, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport { shallowRef, toRefs } from \"vue\";\nimport { isNil } from \"es-toolkit\";\nimport { exportCsv, isCsvExportAvailable } from \"./export-csv\";\nimport type { ExportOptions } from \"./export-csv\";\n\nconst props = defineProps<{\n api: GridApi;\n tableHandle?: PTableHandle;\n columnsMeta?: PlDataTableColumnsMeta;\n}>();\nconst { api: gridApi } = toRefs(props);\nconst csvExportAvailable = isCsvExportAvailable();\n\nconst exporting = shallowRef(false);\nconst initiateExport = () => {\n const nativeOptions: ExportOptions | undefined = !isNil(props.tableHandle)\n ? { tableHandle: props.tableHandle, columnsMeta: props.columnsMeta }\n : undefined;\n\n if (isNil(nativeOptions)) {\n return;\n }\n\n exporting.value = true;\n exportCsv(gridApi.value, nativeOptions).finally(() => (exporting.value = false));\n};\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlAgCsvExporter\");\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget && csvExportAvailable\" :to=\"teleportTarget\">\n <PlBtnGhost :loading=\"exporting\" icon=\"export\" @click.stop=\"initiateExport\">\n Export\n </PlBtnGhost>\n </Teleport>\n</template>\n"],"mappings":""}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { GridApi } from 'ag-grid-enterprise';
|
|
2
|
-
import { PTableHandle } from '@platforma-sdk/model';
|
|
2
|
+
import { PTableHandle, PlDataTableColumnsMeta } from '@platforma-sdk/model';
|
|
3
3
|
type __VLS_Props = {
|
|
4
4
|
api: GridApi;
|
|
5
5
|
tableHandle?: PTableHandle;
|
|
6
|
+
columnsMeta?: PlDataTableColumnsMeta;
|
|
6
7
|
};
|
|
7
8
|
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
8
9
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlAgCsvExporter.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlAgCsvExporter.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAOjF,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,WAAW,CAAC,EAAE,sBAAsB,CAAC;CACtC,CAAC;;AA4FF,wBAMG"}
|
|
@@ -7,11 +7,15 @@ var _ = /* @__PURE__ */ s({
|
|
|
7
7
|
__name: "PlAgCsvExporter",
|
|
8
8
|
props: {
|
|
9
9
|
api: {},
|
|
10
|
-
tableHandle: {}
|
|
10
|
+
tableHandle: {},
|
|
11
|
+
columnsMeta: {}
|
|
11
12
|
},
|
|
12
13
|
setup(s) {
|
|
13
14
|
let _ = s, { api: v } = u(_), y = t(), b = l(!1), x = () => {
|
|
14
|
-
let t = m(_.tableHandle) ? void 0 : {
|
|
15
|
+
let t = m(_.tableHandle) ? void 0 : {
|
|
16
|
+
tableHandle: _.tableHandle,
|
|
17
|
+
columnsMeta: _.columnsMeta
|
|
18
|
+
};
|
|
15
19
|
m(t) || (b.value = !0, e(v.value, t).finally(() => b.value = !1));
|
|
16
20
|
}, S = g("PlAgCsvExporter");
|
|
17
21
|
return (e, t) => d(S) && d(y) ? (c(), r(n, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlAgCsvExporter.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { GridApi } from \"ag-grid-enterprise\";\nimport type { PTableHandle } from \"@platforma-sdk/model\";\nimport { PlBtnGhost, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport { shallowRef, toRefs } from \"vue\";\nimport { isNil } from \"es-toolkit\";\nimport { exportCsv, isCsvExportAvailable } from \"./export-csv\";\nimport type { ExportOptions } from \"./export-csv\";\n\nconst props = defineProps<{\n api: GridApi;\n tableHandle?: PTableHandle;\n}>();\nconst { api: gridApi } = toRefs(props);\nconst csvExportAvailable = isCsvExportAvailable();\n\nconst exporting = shallowRef(false);\nconst initiateExport = () => {\n const nativeOptions: ExportOptions | undefined = !isNil(props.tableHandle)\n ? { tableHandle: props.tableHandle }\n : undefined;\n\n if (isNil(nativeOptions)) {\n return;\n }\n\n exporting.value = true;\n exportCsv(gridApi.value, nativeOptions).finally(() => (exporting.value = false));\n};\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlAgCsvExporter\");\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget && csvExportAvailable\" :to=\"teleportTarget\">\n <PlBtnGhost :loading=\"exporting\" icon=\"export\" @click.stop=\"initiateExport\">\n Export\n </PlBtnGhost>\n </Teleport>\n</template>\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlAgCsvExporter.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { GridApi } from \"ag-grid-enterprise\";\nimport type { PTableHandle, PlDataTableColumnsMeta } from \"@platforma-sdk/model\";\nimport { PlBtnGhost, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport { shallowRef, toRefs } from \"vue\";\nimport { isNil } from \"es-toolkit\";\nimport { exportCsv, isCsvExportAvailable } from \"./export-csv\";\nimport type { ExportOptions } from \"./export-csv\";\n\nconst props = defineProps<{\n api: GridApi;\n tableHandle?: PTableHandle;\n columnsMeta?: PlDataTableColumnsMeta;\n}>();\nconst { api: gridApi } = toRefs(props);\nconst csvExportAvailable = isCsvExportAvailable();\n\nconst exporting = shallowRef(false);\nconst initiateExport = () => {\n const nativeOptions: ExportOptions | undefined = !isNil(props.tableHandle)\n ? { tableHandle: props.tableHandle, columnsMeta: props.columnsMeta }\n : undefined;\n\n if (isNil(nativeOptions)) {\n return;\n }\n\n exporting.value = true;\n exportCsv(gridApi.value, nativeOptions).finally(() => (exporting.value = false));\n};\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlAgCsvExporter\");\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget && csvExportAvailable\" :to=\"teleportTarget\">\n <PlBtnGhost :loading=\"exporting\" icon=\"export\" @click.stop=\"initiateExport\">\n Export\n </PlBtnGhost>\n </Teleport>\n</template>\n"],"mappings":";;;;;;;;;;;;;EASA,IAAM,IAAQ,GAKR,EAAE,KAAK,MAAY,EAAO,EAAM,EAChC,IAAqB,GAAsB,EAE3C,IAAY,EAAW,GAAM,EAC7B,UAAuB;GAC3B,IAAM,IAA4C,EAAM,EAAM,YAAW,GAErE,KAAA,IADA;IAAE,aAAa,EAAM;IAAa,aAAa,EAAM;IAAY;AAGjE,KAAM,EAAc,KAIxB,EAAU,QAAQ,IAClB,EAAU,EAAQ,OAAO,EAAc,CAAC,cAAe,EAAU,QAAQ,GAAO;KAG5E,IAAiB,EAAkC,kBAAkB;mBAIzD,EAAA,EAAc,IAAI,EAAA,EAAkB,IAAA,GAAA,EAApD,EAIW,GAAA;;GAJ4C,IAAI,EAAA,EAAc;MACvE,EAEa,EAAA,EAAA,EAAA;GAFA,SAAS,EAAA;GAAW,MAAK;GAAU,SAAK,EAAO,GAAc,CAAA,OAAA,CAAA;;oBAE1E,CAAA,GAAA,AAAA,EAAA,OAAA,CAAA,EAF4E,YAE5E,GAAA,CAAA,CAAA,CAAA"}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { GridApi } from 'ag-grid-enterprise';
|
|
2
|
-
import { PTableHandle, PTableColumnSpec, PFrameSpecDriver, WritePTableToFsResult } from '@platforma-sdk/model';
|
|
2
|
+
import { PTableHandle, PTableColumnSpec, PFrameSpecDriver, PlDataTableColumnsMeta, WritePTableToFsResult } from '@platforma-sdk/model';
|
|
3
3
|
import { Nil } from '@milaboratories/helpers';
|
|
4
4
|
/** Options for the native table export. */
|
|
5
5
|
export interface ExportOptions {
|
|
6
6
|
tableHandle: PTableHandle;
|
|
7
7
|
defaultFileName?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Sidecar display metadata for the visible table. Supplies the disambiguated
|
|
10
|
+
* header labels the UI shows — the ones the spec's intrinsic `pl7.app/label`
|
|
11
|
+
* may not carry — so the export reproduces exactly what the user sees.
|
|
12
|
+
*/
|
|
13
|
+
columnsMeta?: PlDataTableColumnsMeta;
|
|
8
14
|
}
|
|
9
15
|
/**
|
|
10
16
|
* Table export via the platforma desktop runtime. Prompts for a save
|
|
@@ -28,10 +34,15 @@ export declare function exportCsv(gridApi: GridApi, nativeOptions: ExportOptions
|
|
|
28
34
|
*/
|
|
29
35
|
export declare function isCsvExportAvailable(): boolean;
|
|
30
36
|
/**
|
|
31
|
-
* Collect unified
|
|
32
|
-
* ag-grid column defs, remapped onto the provided PTable spec array so
|
|
33
|
-
* indices match the current table handle (ColDef.field indices may be stale
|
|
37
|
+
* Collect `[unified index, header name]` pairs for visible (non-hidden) columns
|
|
38
|
+
* from the ag-grid column defs, remapped onto the provided PTable spec array so
|
|
39
|
+
* the indices match the current table handle (ColDef.field indices may be stale
|
|
34
40
|
* or diverge from the spec order).
|
|
41
|
+
*
|
|
42
|
+
* The header name is the disambiguated label the UI shows — sidecar override
|
|
43
|
+
* (`columnsMeta`) wins over the spec's intrinsic `pl7.app/label`, falling back
|
|
44
|
+
* to the spec name — so the export reproduces exactly the visible headers and
|
|
45
|
+
* avoids the duplicate-label collisions that break the native export path.
|
|
35
46
|
*/
|
|
36
|
-
export declare function
|
|
47
|
+
export declare function collectVisibleColumns(gridApi: GridApi, specs: PTableColumnSpec[], pframeSpec: PFrameSpecDriver, columnsMeta: PlDataTableColumnsMeta | undefined): Nil | [number, string][];
|
|
37
48
|
//# sourceMappingURL=export-csv.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export-csv.d.ts","sourceRoot":"","sources":["../../../src/components/PlAgCsvExporter/export-csv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,KAAK,EAEV,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"export-csv.d.ts","sourceRoot":"","sources":["../../../src/components/PlAgCsvExporter/export-csv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,KAAK,EAEV,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAK9C,2CAA2C;AAC3C,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,YAAY,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,sBAAsB,CAAC;CACtC;AAkCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,SAAS,GAAG,qBAAqB,CAAC,CAqE5C;AAUD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAO9C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,gBAAgB,EAAE,EACzB,UAAU,EAAE,gBAAgB,EAC5B,WAAW,EAAE,sBAAsB,GAAG,SAAS,GAC9C,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAwB1B"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { getServices as e } from "../../internal/getServices.js";
|
|
2
2
|
import "../PlAgDataTable/sources/row-number.js";
|
|
3
3
|
import "../PlAgDataTable/index.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { effectiveLabel as t } from "../PlAgDataTable/sources/table-source-v2.js";
|
|
5
|
+
import { XLSX_MAX_ROWS_PER_SHEET as n, getPTableColumnId as r } from "@platforma-sdk/model";
|
|
6
|
+
import { isNil as i } from "es-toolkit";
|
|
6
7
|
//#region src/components/PlAgCsvExporter/export-csv.ts
|
|
7
|
-
var
|
|
8
|
+
var a = [
|
|
8
9
|
{
|
|
9
10
|
name: "CSV",
|
|
10
11
|
extensions: ["csv"]
|
|
@@ -21,7 +22,7 @@ var i = [
|
|
|
21
22
|
name: "Excel",
|
|
22
23
|
extensions: ["xlsx"]
|
|
23
24
|
}
|
|
24
|
-
],
|
|
25
|
+
], o = [
|
|
25
26
|
{
|
|
26
27
|
name: "CSV (gzip)",
|
|
27
28
|
extensions: ["csv.gz"]
|
|
@@ -39,63 +40,71 @@ var i = [
|
|
|
39
40
|
extensions: ["tsv"]
|
|
40
41
|
}
|
|
41
42
|
];
|
|
42
|
-
function
|
|
43
|
+
function s(e) {
|
|
43
44
|
let t = e.toLowerCase(), n = t.endsWith(".gz") ? t.slice(0, -3) : t;
|
|
44
45
|
return n.slice(n.lastIndexOf(".") + 1);
|
|
45
46
|
}
|
|
46
|
-
function
|
|
47
|
+
function c(e, t) {
|
|
47
48
|
return t.some(({ extensions: t }) => t.some((t) => e.endsWith("." + t)));
|
|
48
49
|
}
|
|
49
|
-
async function
|
|
50
|
-
let { dialog:
|
|
51
|
-
if (
|
|
52
|
-
if (
|
|
53
|
-
if (
|
|
54
|
-
let m =
|
|
55
|
-
if (
|
|
56
|
-
let h =
|
|
57
|
-
if (typeof
|
|
58
|
-
let { rows: e } = await
|
|
59
|
-
defaultFileName: `${
|
|
60
|
-
filters:
|
|
50
|
+
async function l(t, r) {
|
|
51
|
+
let { dialog: l, pframe: d, pframeSpec: p } = e();
|
|
52
|
+
if (i(l)) throw Error("dialog service is not available in the current environment");
|
|
53
|
+
if (i(d)) throw Error("pframe service is not available");
|
|
54
|
+
if (i(p)) throw Error("pframeSpec service is not available");
|
|
55
|
+
let m = f(t, await d.getSpec(r.tableHandle), p, r.columnsMeta);
|
|
56
|
+
if (i(m)) return;
|
|
57
|
+
let h = m.map(([e]) => e), g = m.map(([, e]) => e), _ = r.defaultFileName ?? `table_${u(/* @__PURE__ */ new Date())}`;
|
|
58
|
+
if (typeof d.exportPTable == "function") {
|
|
59
|
+
let { rows: e } = await d.getShape(r.tableHandle), t = e > n ? a.filter(({ name: e }) => e !== "Excel") : a, { canceled: o, path: s } = await l.showSaveDialog({
|
|
60
|
+
defaultFileName: `${_}.csv`,
|
|
61
|
+
filters: t
|
|
61
62
|
});
|
|
62
|
-
if (
|
|
63
|
-
await
|
|
64
|
-
path:
|
|
65
|
-
columnIndices:
|
|
63
|
+
if (o || i(s) || !c(s, t)) return;
|
|
64
|
+
await d.exportPTable(r.tableHandle, {
|
|
65
|
+
path: s,
|
|
66
|
+
columnIndices: h,
|
|
67
|
+
headerNames: g
|
|
66
68
|
});
|
|
67
69
|
return;
|
|
68
70
|
}
|
|
69
|
-
let { canceled:
|
|
70
|
-
defaultFileName: `${
|
|
71
|
-
filters:
|
|
71
|
+
let { canceled: v, path: y } = await l.showSaveDialog({
|
|
72
|
+
defaultFileName: `${_}.csv.gz`,
|
|
73
|
+
filters: o
|
|
72
74
|
});
|
|
73
|
-
if (
|
|
74
|
-
let
|
|
75
|
-
if (!(
|
|
76
|
-
path:
|
|
77
|
-
format:
|
|
78
|
-
columnIndices:
|
|
79
|
-
|
|
75
|
+
if (v || i(y) || !c(y, o)) return;
|
|
76
|
+
let b = s(y);
|
|
77
|
+
if (!(b !== "csv" && b !== "tsv")) return d.writePTableToFs(r.tableHandle, {
|
|
78
|
+
path: y,
|
|
79
|
+
format: b,
|
|
80
|
+
columnIndices: h,
|
|
81
|
+
headerNames: g,
|
|
82
|
+
...y.toLowerCase().endsWith(".gz") && { compression: { type: "gzip" } }
|
|
80
83
|
});
|
|
81
84
|
}
|
|
82
|
-
function
|
|
85
|
+
function u(e) {
|
|
83
86
|
let t = (e) => String(e).padStart(2, "0");
|
|
84
87
|
return `${t(e.getDate())}-${t(e.getMonth() + 1)}-${e.getFullYear()}_${t(e.getHours())}-${t(e.getMinutes())}-${t(e.getSeconds())}`;
|
|
85
88
|
}
|
|
86
|
-
function
|
|
89
|
+
function d() {
|
|
87
90
|
try {
|
|
88
91
|
let t = e();
|
|
89
|
-
return !
|
|
92
|
+
return !i(t?.dialog) && !i(t?.pframe);
|
|
90
93
|
} catch {
|
|
91
94
|
return !1;
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
|
-
function
|
|
95
|
-
let
|
|
96
|
-
|
|
97
|
+
function f(e, n, a, o) {
|
|
98
|
+
let s = e.getColumnDefs();
|
|
99
|
+
if (i(s)) return;
|
|
100
|
+
let c = (e) => a.findTableColumn(n, r(e)), l = (e) => "children" in e || e.hide === !0 || i(e.colId) || e.colId === "\"##RowNumberColumnId##\"" ? [] : [e.context], u = /* @__PURE__ */ new Map();
|
|
101
|
+
for (let e of s.flatMap(l)) {
|
|
102
|
+
let n = c(e);
|
|
103
|
+
n === -1 || u.has(n) || u.set(n, t(e, o)?.trim() ?? e.spec.name);
|
|
104
|
+
}
|
|
105
|
+
return [...u];
|
|
97
106
|
}
|
|
98
107
|
//#endregion
|
|
99
|
-
export {
|
|
108
|
+
export { l as exportCsv, d as isCsvExportAvailable };
|
|
100
109
|
|
|
101
110
|
//# sourceMappingURL=export-csv.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export-csv.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/export-csv.ts"],"sourcesContent":["import { type ColDef, type ColGroupDef, type GridApi } from \"ag-grid-enterprise\";\nimport type {\n FileFilter,\n PTableHandle,\n PTableColumnSpec,\n PFrameSpecDriver,\n WritePTableToFsResult,\n} from \"@platforma-sdk/model\";\nimport { getPTableColumnId, XLSX_MAX_ROWS_PER_SHEET } from \"@platforma-sdk/model\";\nimport { isNil } from \"es-toolkit\";\nimport { Nil } from \"@milaboratories/helpers\";\nimport { getServices } from \"../../internal/getServices\";\nimport { PlAgDataTableRowNumberColId } from \"../PlAgDataTable\";\n\n/** Options for the native table export. */\nexport interface ExportOptions {\n tableHandle: PTableHandle;\n defaultFileName?: string;\n}\n\n/** Save-dialog file-type filters for the native `exportPTable` path. */\nconst NATIVE_EXPORT_FILTERS: FileFilter[] = [\n { name: \"CSV\", extensions: [\"csv\"] },\n { name: \"TSV\", extensions: [\"tsv\"] },\n { name: \"Parquet\", extensions: [\"parquet\"] },\n { name: \"Excel\", extensions: [\"xlsx\"] },\n];\n\n/**\n * Save-dialog file-type filters for the `writePTableToFs` fallback. csv/tsv\n * only, offered both plain and gzip-compressed — a trailing `.gz` in the\n * chosen path selects gzip compression.\n */\nconst FALLBACK_EXPORT_FILTERS: FileFilter[] = [\n { name: \"CSV (gzip)\", extensions: [\"csv.gz\"] },\n { name: \"CSV\", extensions: [\"csv\"] },\n { name: \"TSV (gzip)\", extensions: [\"tsv.gz\"] },\n { name: \"TSV\", extensions: [\"tsv\"] },\n];\n\n/** Table format from a save path, ignoring a trailing `.gz`. e.g. `\"t.csv.gz\"` → `\"csv\"`. */\nfunction tableFormatFromPath(path: string): string {\n const lower = path.toLowerCase();\n const base = lower.endsWith(\".gz\") ? lower.slice(0, -3) : lower;\n return base.slice(base.lastIndexOf(\".\") + 1);\n}\n\n/** True when `path` ends with one of the filters' extensions (e.g. `\".csv\"`). */\nfunction matchesFilterExtension(path: string, filters: FileFilter[]): boolean {\n return filters.some(({ extensions }) => extensions.some((ext) => path.endsWith(\".\" + ext)));\n}\n\n/**\n * Table export via the platforma desktop runtime. Prompts for a save\n * destination via the `Dialog` service — offering the available output formats\n * as file-type filters so the user picks the format — then writes the PTable to\n * the chosen path.\n *\n * Prefers the native `PFrame.exportPTable` when the runtime advertises it: the\n * table handle is the *visible* one, so exporting the whole handle reproduces\n * exactly the visible columns/rows, and the format (`csv`/`tsv`/`parquet`/\n * `xlsx`) is taken from the chosen file extension. Falls back to streaming the\n * visible columns via `PFrame.writePTableToFs` (`csv`/`tsv`, optionally\n * gzip-compressed when the chosen path ends in `.gz`) on runtimes that do not\n * advertise `exportPTable`.\n */\nexport async function exportCsv(\n gridApi: GridApi,\n nativeOptions: ExportOptions,\n): Promise<undefined | WritePTableToFsResult> {\n const { dialog, pframe, pframeSpec } = getServices();\n if (isNil(dialog)) {\n throw new Error(\"dialog service is not available in the current environment\");\n }\n if (isNil(pframe)) {\n throw new Error(\"pframe service is not available\");\n }\n if (isNil(pframeSpec)) {\n throw new Error(\"pframeSpec service is not available\");\n }\n\n const specs = await pframe.getSpec(nativeOptions.tableHandle);\n const columnIndices = collectVisibleColumnIndices(gridApi, specs, pframeSpec);\n if (isNil(columnIndices)) {\n return undefined;\n }\n\n const baseFileName = nativeOptions.defaultFileName ?? `table_${formatTimestamp(new Date())}`;\n\n if (typeof pframe.exportPTable === \"function\") {\n const { rows } = await pframe.getShape(nativeOptions.tableHandle);\n const filters =\n rows > XLSX_MAX_ROWS_PER_SHEET\n ? NATIVE_EXPORT_FILTERS.filter(({ name }) => name !== \"Excel\")\n : NATIVE_EXPORT_FILTERS;\n\n const { canceled, path } = await dialog.showSaveDialog({\n defaultFileName: `${baseFileName}.csv`,\n filters,\n });\n if (canceled || isNil(path) || !matchesFilterExtension(path, filters)) {\n return undefined;\n }\n\n await pframe.exportPTable(nativeOptions.tableHandle, { path, columnIndices });\n return undefined;\n }\n\n const { canceled, path } = await dialog.showSaveDialog({\n defaultFileName: `${baseFileName}.csv.gz`,\n filters: FALLBACK_EXPORT_FILTERS,\n });\n if (canceled || isNil(path) || !matchesFilterExtension(path, FALLBACK_EXPORT_FILTERS)) {\n return undefined;\n }\n\n const format = tableFormatFromPath(path);\n if (format !== \"csv\" && format !== \"tsv\") {\n return undefined;\n }\n\n return pframe.writePTableToFs(nativeOptions.tableHandle, {\n path,\n format,\n columnIndices,\n ...(path.toLowerCase().endsWith(\".gz\") && { compression: { type: \"gzip\" } }),\n });\n}\n\nfunction formatTimestamp(d: Date): string {\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()}` +\n `_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}`\n );\n}\n\n/**\n * Checks whether the native CSV export capability is available in the\n * current platforma runtime environment. Both `Dialog` and `PFrame`\n * services must be present — desktop-app wires them, web/preview do not.\n */\nexport function isCsvExportAvailable(): boolean {\n try {\n const services = getServices();\n return !isNil(services?.dialog) && !isNil(services?.pframe);\n } catch {\n return false;\n }\n}\n\n/**\n * Collect unified column indices for visible (non-hidden) columns from the\n * ag-grid column defs, remapped onto the provided PTable spec array so the\n * indices match the current table handle (ColDef.field indices may be stale\n * or diverge from the spec order).\n */\nexport function collectVisibleColumnIndices(\n gridApi: GridApi,\n specs: PTableColumnSpec[],\n pframeSpec: PFrameSpecDriver,\n): Nil | number[] {\n const columnDefs = gridApi.getColumnDefs();\n if (isNil(columnDefs)) {\n return;\n }\n\n const findIndex = (spec: PTableColumnSpec) =>\n pframeSpec.findTableColumn(specs, getPTableColumnId(spec));\n\n const specsForDef = (def: ColDef | ColGroupDef): PTableColumnSpec[] => {\n if (\"children\" in def) return [];\n if (def.hide === true) return [];\n if (isNil(def.colId)) return [];\n if (def.colId === PlAgDataTableRowNumberColId) return [];\n return [def.context as PTableColumnSpec];\n };\n\n return [...new Set(columnDefs.flatMap(specsForDef).map(findIndex))].filter((idx) => idx !== -1);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"export-csv.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/export-csv.ts"],"sourcesContent":["import { type ColDef, type ColGroupDef, type GridApi } from \"ag-grid-enterprise\";\nimport type {\n FileFilter,\n PTableHandle,\n PTableColumnSpec,\n PFrameSpecDriver,\n PlDataTableColumnsMeta,\n WritePTableToFsResult,\n} from \"@platforma-sdk/model\";\nimport { getPTableColumnId, XLSX_MAX_ROWS_PER_SHEET } from \"@platforma-sdk/model\";\nimport { isNil } from \"es-toolkit\";\nimport { Nil } from \"@milaboratories/helpers\";\nimport { getServices } from \"../../internal/getServices\";\nimport { PlAgDataTableRowNumberColId } from \"../PlAgDataTable\";\nimport { effectiveLabel } from \"../PlAgDataTable/sources/table-source-v2\";\n\n/** Options for the native table export. */\nexport interface ExportOptions {\n tableHandle: PTableHandle;\n defaultFileName?: string;\n /**\n * Sidecar display metadata for the visible table. Supplies the disambiguated\n * header labels the UI shows — the ones the spec's intrinsic `pl7.app/label`\n * may not carry — so the export reproduces exactly what the user sees.\n */\n columnsMeta?: PlDataTableColumnsMeta;\n}\n\n/** Save-dialog file-type filters for the native `exportPTable` path. */\nconst NATIVE_EXPORT_FILTERS: FileFilter[] = [\n { name: \"CSV\", extensions: [\"csv\"] },\n { name: \"TSV\", extensions: [\"tsv\"] },\n { name: \"Parquet\", extensions: [\"parquet\"] },\n { name: \"Excel\", extensions: [\"xlsx\"] },\n];\n\n/**\n * Save-dialog file-type filters for the `writePTableToFs` fallback. csv/tsv\n * only, offered both plain and gzip-compressed — a trailing `.gz` in the\n * chosen path selects gzip compression.\n */\nconst FALLBACK_EXPORT_FILTERS: FileFilter[] = [\n { name: \"CSV (gzip)\", extensions: [\"csv.gz\"] },\n { name: \"CSV\", extensions: [\"csv\"] },\n { name: \"TSV (gzip)\", extensions: [\"tsv.gz\"] },\n { name: \"TSV\", extensions: [\"tsv\"] },\n];\n\n/** Table format from a save path, ignoring a trailing `.gz`. e.g. `\"t.csv.gz\"` → `\"csv\"`. */\nfunction tableFormatFromPath(path: string): string {\n const lower = path.toLowerCase();\n const base = lower.endsWith(\".gz\") ? lower.slice(0, -3) : lower;\n return base.slice(base.lastIndexOf(\".\") + 1);\n}\n\n/** True when `path` ends with one of the filters' extensions (e.g. `\".csv\"`). */\nfunction matchesFilterExtension(path: string, filters: FileFilter[]): boolean {\n return filters.some(({ extensions }) => extensions.some((ext) => path.endsWith(\".\" + ext)));\n}\n\n/**\n * Table export via the platforma desktop runtime. Prompts for a save\n * destination via the `Dialog` service — offering the available output formats\n * as file-type filters so the user picks the format — then writes the PTable to\n * the chosen path.\n *\n * Prefers the native `PFrame.exportPTable` when the runtime advertises it: the\n * table handle is the *visible* one, so exporting the whole handle reproduces\n * exactly the visible columns/rows, and the format (`csv`/`tsv`/`parquet`/\n * `xlsx`) is taken from the chosen file extension. Falls back to streaming the\n * visible columns via `PFrame.writePTableToFs` (`csv`/`tsv`, optionally\n * gzip-compressed when the chosen path ends in `.gz`) on runtimes that do not\n * advertise `exportPTable`.\n */\nexport async function exportCsv(\n gridApi: GridApi,\n nativeOptions: ExportOptions,\n): Promise<undefined | WritePTableToFsResult> {\n const { dialog, pframe, pframeSpec } = getServices();\n if (isNil(dialog)) {\n throw new Error(\"dialog service is not available in the current environment\");\n }\n if (isNil(pframe)) {\n throw new Error(\"pframe service is not available\");\n }\n if (isNil(pframeSpec)) {\n throw new Error(\"pframeSpec service is not available\");\n }\n\n const specs = await pframe.getSpec(nativeOptions.tableHandle);\n const visibleColumns = collectVisibleColumns(\n gridApi,\n specs,\n pframeSpec,\n nativeOptions.columnsMeta,\n );\n if (isNil(visibleColumns)) {\n return undefined;\n }\n // `columnIndices` is the long-standing field every runtime understands;\n // `headerNames` is additive so a newer UI still works against an older\n // desktop-app runtime (which ignores it and derives labels from the spec).\n const columnIndices = visibleColumns.map(([index]) => index);\n const headerNames = visibleColumns.map(([, name]) => name);\n\n const baseFileName = nativeOptions.defaultFileName ?? `table_${formatTimestamp(new Date())}`;\n\n if (typeof pframe.exportPTable === \"function\") {\n const { rows } = await pframe.getShape(nativeOptions.tableHandle);\n const filters =\n rows > XLSX_MAX_ROWS_PER_SHEET\n ? NATIVE_EXPORT_FILTERS.filter(({ name }) => name !== \"Excel\")\n : NATIVE_EXPORT_FILTERS;\n\n const { canceled, path } = await dialog.showSaveDialog({\n defaultFileName: `${baseFileName}.csv`,\n filters,\n });\n if (canceled || isNil(path) || !matchesFilterExtension(path, filters)) {\n return undefined;\n }\n\n await pframe.exportPTable(nativeOptions.tableHandle, { path, columnIndices, headerNames });\n return undefined;\n }\n\n const { canceled, path } = await dialog.showSaveDialog({\n defaultFileName: `${baseFileName}.csv.gz`,\n filters: FALLBACK_EXPORT_FILTERS,\n });\n if (canceled || isNil(path) || !matchesFilterExtension(path, FALLBACK_EXPORT_FILTERS)) {\n return undefined;\n }\n\n const format = tableFormatFromPath(path);\n if (format !== \"csv\" && format !== \"tsv\") {\n return undefined;\n }\n\n return pframe.writePTableToFs(nativeOptions.tableHandle, {\n path,\n format,\n columnIndices,\n headerNames,\n ...(path.toLowerCase().endsWith(\".gz\") && { compression: { type: \"gzip\" } }),\n });\n}\n\nfunction formatTimestamp(d: Date): string {\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()}` +\n `_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}`\n );\n}\n\n/**\n * Checks whether the native CSV export capability is available in the\n * current platforma runtime environment. Both `Dialog` and `PFrame`\n * services must be present — desktop-app wires them, web/preview do not.\n */\nexport function isCsvExportAvailable(): boolean {\n try {\n const services = getServices();\n return !isNil(services?.dialog) && !isNil(services?.pframe);\n } catch {\n return false;\n }\n}\n\n/**\n * Collect `[unified index, header name]` pairs for visible (non-hidden) columns\n * from the ag-grid column defs, remapped onto the provided PTable spec array so\n * the indices match the current table handle (ColDef.field indices may be stale\n * or diverge from the spec order).\n *\n * The header name is the disambiguated label the UI shows — sidecar override\n * (`columnsMeta`) wins over the spec's intrinsic `pl7.app/label`, falling back\n * to the spec name — so the export reproduces exactly the visible headers and\n * avoids the duplicate-label collisions that break the native export path.\n */\nexport function collectVisibleColumns(\n gridApi: GridApi,\n specs: PTableColumnSpec[],\n pframeSpec: PFrameSpecDriver,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): Nil | [number, string][] {\n const columnDefs = gridApi.getColumnDefs();\n if (isNil(columnDefs)) {\n return;\n }\n\n const findIndex = (spec: PTableColumnSpec) =>\n pframeSpec.findTableColumn(specs, getPTableColumnId(spec));\n\n const specsForDef = (def: ColDef | ColGroupDef): PTableColumnSpec[] => {\n if (\"children\" in def) return [];\n if (def.hide === true) return [];\n if (isNil(def.colId)) return [];\n if (def.colId === PlAgDataTableRowNumberColId) return [];\n return [def.context as PTableColumnSpec];\n };\n\n const byIndex = new Map<number, string>();\n for (const spec of columnDefs.flatMap(specsForDef)) {\n const index = findIndex(spec);\n if (index === -1 || byIndex.has(index)) continue;\n byIndex.set(index, effectiveLabel(spec, columnsMeta)?.trim() ?? spec.spec.name);\n }\n return [...byIndex];\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlAgDataTableV2.js","names":[],"sources":["../../../src/components/PlAgDataTable/PlAgDataTableV2.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { promiseTimeout, isJsonEqual } from \"@milaboratories/helpers\";\nimport type {\n AxisId,\n PlDataTableColumnsMeta,\n PlDataTableGridStateCore,\n PlDataTableStateV2,\n PlSelectionModel,\n PlTableColumnIdJson,\n PTableColumnSpec,\n PTableKey,\n} from \"@platforma-sdk/model\";\nimport {\n getRawPlatformaInstance,\n createPlSelectionModel,\n matchAxisId,\n getAxisId,\n canonicalizeJson,\n isAbortError,\n} from \"@platforma-sdk/model\";\nimport type { CellRendererSelectorFunc, GridApi, GridState } from \"ag-grid-enterprise\";\nimport { AgGridVue } from \"ag-grid-vue3\";\nimport { computed, effectScope, nextTick, ref, toRefs, watch, watchEffect } from \"vue\";\nimport PlAgCsvExporter from \"../PlAgCsvExporter/PlAgCsvExporter.vue\";\nimport { PlAgGridColumnManager } from \"../PlAgGridColumnManager\";\nimport PlTableFiltersV2 from \"../PlTableFilters/PlTableFiltersV2.vue\";\nimport { PlTableFastSearch } from \"../PlTableFastSearch\";\nimport PlAgDataTableSheets from \"./PlAgDataTableSheets.vue\";\nimport PlAgRowCount from \"./PlAgRowCount.vue\";\nimport { DeferredCircular, ensureNodeVisible } from \"./sources/focus-row\";\nimport { PlAgDataTableRowNumberColId } from \"./sources/row-number\";\nimport type { PlAgCellButtonAxisParams } from \"./sources/table-source-v2\";\nimport { calculateGridOptions, effectiveVisibility } from \"./sources/table-source-v2\";\nimport { useTableState } from \"./sources/table-state-v2\";\nimport type {\n PlAgDataTableV2Controller,\n PlAgDataTableV2Row,\n PlAgOverlayLoadingParams,\n PlAgOverlayNoRowsParams,\n PlDataTableSettingsV2,\n PlDataTableSheetsSettings,\n PlTableRowId,\n} from \"./types\";\nimport { useFilterableColumns } from \"./compositions/useFilterableColumns\";\nimport { useGrid } from \"./compositions/useGrid\";\nimport { AgGridTheme } from \"../../AgGridVue/AgGridTheme\";\n\nconst tableState = defineModel<PlDataTableStateV2>({\n required: true,\n});\n/** Warning: selection model value updates are ignored, use updateSelection instead */\nconst selection = defineModel<PlSelectionModel>(\"selection\");\nconst props = defineProps<{\n /** Required component settings */\n settings: Readonly<PlDataTableSettingsV2>;\n\n /**\n * The disableColumnsPanel prop controls the display of a button that activates\n * the columns management panel in the table. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n disableColumnsPanel?: boolean;\n\n /**\n * The disableFiltersPanel prop controls the display of a button that activates\n * the filters management panel in the table. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n disableFiltersPanel?: boolean;\n\n /**\n * The showExportButton prop controls the display of a button that allows\n * to export table data in CSV format. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n showExportButton?: boolean;\n\n /**\n * The AxisId property is used to configure and display the PlAgTextAndButtonCell component\n */\n showCellButtonForAxisId?: AxisId;\n\n /**\n * If cellButtonInvokeRowsOnDoubleClick = true, clicking a button inside the row\n * triggers the doubleClick event for the entire row.\n *\n * If cellButtonInvokeRowsOnDoubleClick = false, the doubleClick event for the row\n * is not triggered, but will triggered cellButtonClicked event with (key: PTableRowKey) argument.\n */\n cellButtonInvokeRowsOnDoubleClick?: boolean;\n\n /** @see {@link PlAgOverlayLoadingParams.loadingText} */\n loadingText?: string;\n\n /** @see {@link PlAgOverlayLoadingParams.runningText} */\n runningText?: string;\n\n /** @see {@link PlAgOverlayLoadingParams.notReadyText} */\n notReadyText?: string;\n\n /** @see {@link PlAgOverlayNoRowsParams.text} */\n noRowsText?: string;\n\n /**\n * Callback to override the default renderer for a given cell.\n * @see https://www.ag-grid.com/vue-data-grid/component-cell-renderer/#dynamic-component-selection\n */\n cellRendererSelector?: CellRendererSelectorFunc<PlAgDataTableV2Row>;\n}>();\nconst { settings } = toRefs(props);\nconst emit = defineEmits<{\n rowDoubleClicked: [key?: PTableKey];\n cellButtonClicked: [key?: PTableKey];\n newDataRendered: [];\n}>();\n\nconst dataRenderedTracker = new DeferredCircular<GridApi<PlAgDataTableV2Row>>();\nconst { gridApi, gridOptions } = useGrid({\n selection,\n noRowsText: props.noRowsText,\n runningText: props.runningText,\n loadingText: props.loadingText,\n notReadyText: props.notReadyText,\n cellRendererSelector: props.cellRendererSelector,\n});\nlet isReloading = false;\ngridOptions.value.onGridPreDestroyed = (event) => {\n if (!isReloading) {\n gridOptions.value.initialState = gridState.value = normalizeColumnVisibility(\n makePartialState(event.api.getState()),\n gridState.value,\n event.api,\n currentColumnsMeta(),\n );\n }\n gridApi.value = null;\n};\ngridOptions.value.onRowDoubleClicked = (event) => {\n if (event.data && event.data.axesKey) emit(\"rowDoubleClicked\", event.data.axesKey);\n};\ngridOptions.value.onStateUpdated = (event) => {\n const partialState = normalizeColumnVisibility(\n makePartialState(event.state),\n gridState.value,\n event.api,\n currentColumnsMeta(),\n );\n // We have to keep initialState synchronized with gridState for gridState recovery after key updating.\n gridOptions.value.initialState = gridState.value = partialState;\n\n if (!isJsonEqual(event.sources, [\"columnSizing\"])) {\n event.api.autoSizeColumns(\n event.api\n .getAllDisplayedColumns()\n .filter((column) => column.getColId() !== PlAgDataTableRowNumberColId),\n );\n }\n};\n\nconst [filterableColumns, visibleFilterableColumns] = useFilterableColumns(\n () => settings.value.sourceId,\n () => gridOptions.value.columnDefs ?? null,\n);\nconst defaultFilters = computed(() =>\n settings.value.sourceId !== null ? settings.value.model?.defaultFilters : undefined,\n);\nconst {\n gridState,\n sheetsState,\n searchString,\n filtersState,\n defaultFiltersState,\n resetDefaultFilters,\n} = useTableState(tableState, settings, visibleFilterableColumns, defaultFilters);\nconst sheetsSettings = computed<PlDataTableSheetsSettings>(() => {\n const settingsCopy = { ...settings.value };\n return settingsCopy.sourceId !== null\n ? {\n sheets: settingsCopy.sheets ?? [],\n cachedState: [...sheetsState.value],\n }\n : {\n sheets: [],\n cachedState: [],\n };\n});\ngridOptions.value.initialState = gridState.value;\n\n// Restore proper types erased by AgGrid\nfunction makePartialState(state: GridState): PlDataTableGridStateCore {\n return {\n columnOrder: state.columnOrder as\n | {\n orderedColIds: PlTableColumnIdJson[];\n }\n | undefined,\n sort: state.sort as\n | {\n sortModel: {\n colId: PlTableColumnIdJson;\n sort: \"asc\" | \"desc\";\n }[];\n }\n | undefined,\n columnVisibility: state.columnVisibility as\n | {\n hiddenColIds: PlTableColumnIdJson[];\n }\n | undefined,\n };\n}\n\n// AG Grid returns columnVisibility: undefined when all columns are visible.\n// We need to distinguish \"no state yet\" (use isColumnOptional defaults) from\n// \"user explicitly showed all columns\" (store []). This function normalizes\n// the undefined from AG Grid based on the previous state.\nfunction normalizeColumnVisibility(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlDataTableGridStateCore {\n if (partialState.columnVisibility !== undefined) return partialState;\n\n if (prevState.columnVisibility !== undefined) {\n // Had explicit visibility state before → user made all columns visible → store [].\n return { ...partialState, columnVisibility: { hiddenColIds: [] } };\n }\n\n // No previous explicit state → compute defaults from current columns\n // to replicate: hide: hiddenColIds?.includes(colId) ?? isColumnOptional(spec.spec)\n const defaultHidden = getDefaultHiddenColIds(api, columnsMeta);\n if (defaultHidden.length > 0) {\n return { ...partialState, columnVisibility: { hiddenColIds: defaultHidden } };\n }\n\n return partialState;\n}\n\n/** Sidecar display meta is only on the `model` branch of the settings union. */\nfunction currentColumnsMeta(): PlDataTableColumnsMeta | undefined {\n return \"model\" in props.settings ? props.settings.model?.columnsMeta : undefined;\n}\n\nfunction getDefaultHiddenColIds(\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlTableColumnIdJson[] {\n const cols = api.getAllGridColumns();\n if (!cols) return [];\n\n return cols\n .filter((col) => {\n const spec = col.getColDef().context as PTableColumnSpec | undefined;\n return (\n spec !== undefined &&\n spec.type === \"column\" &&\n effectiveVisibility(spec, columnsMeta) === \"optional\"\n );\n })\n .map((col) => col.getColId() as PlTableColumnIdJson);\n}\n\n// Normalize columnVisibility for comparison: undefined and { hiddenColIds: [] } are equivalent.\nfunction stateForReloadCompare(state: PlDataTableGridStateCore): PlDataTableGridStateCore {\n const cv = state.columnVisibility;\n const normalizedCv = !cv || cv.hiddenColIds.length === 0 ? undefined : state.columnVisibility;\n return { ...state, columnVisibility: normalizedCv };\n}\n\n// Reload AgGrid when new state arrives from server\nconst reloadKey = ref(0);\nwatch(\n () => [gridApi.value, gridState.value] as const,\n ([gridApi, gridState]) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n const selfState = makePartialState(gridApi.getState());\n if (\n !isJsonEqual(gridState, {}) &&\n !isJsonEqual(stateForReloadCompare(gridState), stateForReloadCompare(selfState))\n ) {\n isReloading = true;\n gridOptions.value.initialState = gridState;\n ++reloadKey.value;\n nextTick(() => {\n isReloading = false;\n });\n }\n },\n);\n\n// Make cellRendererSelector reactive\nconst cellRendererSelector = computed(() => props.cellRendererSelector ?? null);\nwatch(\n () => [gridApi.value, cellRendererSelector.value] as const,\n ([gridApi, cellRendererSelector]) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n gridApi.setGridOption(\"defaultColDef\", {\n ...gridOptions.value.defaultColDef,\n cellRendererSelector: cellRendererSelector ?? undefined,\n });\n },\n);\n\ndefineExpose<PlAgDataTableV2Controller>({\n focusRow: async (rowKey) => {\n const gridApi = await dataRenderedTracker.promise;\n if (gridApi.isDestroyed()) return false;\n\n return ensureNodeVisible(gridApi, (row) => isJsonEqual(row.data?.axesKey, rowKey));\n },\n updateSelection: async ({ axesSpec, selectedKeys }) => {\n const gridApi = await dataRenderedTracker.promise;\n if (gridApi.isDestroyed()) return false;\n\n const axes = selection.value?.axesSpec;\n if (!axes || axes.length !== axesSpec.length) return false;\n\n const mapping = axesSpec.map((spec) => {\n const id = getAxisId(spec);\n return axes.findIndex((axis) => matchAxisId(axis, id));\n });\n const mappingSet = new Set(mapping);\n if (mappingSet.has(-1) || mappingSet.size !== axesSpec.length) return false;\n\n const selectedNodes = selectedKeys.map((key) =>\n canonicalizeJson<PlTableRowId>(mapping.map((index) => key[index])),\n );\n const oldSelectedKeys = gridApi.getServerSideSelectionState()?.toggledNodes ?? [];\n if (!isJsonEqual(oldSelectedKeys, selectedNodes)) {\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: selectedNodes,\n });\n\n // wait for `onSelectionChanged` to update `selection` model\n const scope = effectScope();\n const { resolve, promise } = Promise.withResolvers();\n scope.run(() => watch(selection, resolve, { once: true }));\n try {\n await promiseTimeout(promise, 500);\n } catch {\n return false;\n } finally {\n scope.stop();\n }\n }\n return true;\n },\n});\n\n// Update AgGrid when settings change\nconst defaultSelection = createPlSelectionModel();\nlet oldSettings: PlDataTableSettingsV2 | null = null;\nconst generation = ref(0);\nwatch(\n () => [gridApi.value, settings.value] as const,\n ([gridApi, settings]) => {\n // Wait for AgGrid reinitialization, gridApi will eventually become initialized\n if (!gridApi || gridApi.isDestroyed()) return;\n // Verify that this is not a false watch trigger\n if (isJsonEqual(settings, oldSettings)) return;\n ++generation.value;\n try {\n // Hide no rows overlay if it is shown, or else loading overlay will not be shown\n gridApi.hideOverlay();\n dataRenderedTracker.reset();\n\n // No data source selected -> reset state to default\n if (settings.sourceId === null) {\n gridApi.updateGridOptions({\n loading: true,\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n variant: settings.pending ? \"running\" : \"not-ready\",\n } satisfies PlAgOverlayLoadingParams,\n columnDefs: undefined,\n serverSideDatasource: undefined,\n });\n if (selection.value) {\n if (selection.value && !isJsonEqual(selection.value, defaultSelection)) {\n selection.value = createPlSelectionModel();\n }\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n return;\n }\n\n if (\n settings.model?.fullTableHandle === undefined ||\n settings.model?.visibleTableHandle === undefined\n ) {\n return;\n }\n\n // Data source changed -> show full page loader, clear selection\n if (settings.sourceId !== oldSettings?.sourceId) {\n gridApi.updateGridOptions({\n loading: true,\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n variant: \"loading\",\n } satisfies PlAgOverlayLoadingParams,\n });\n if (selection.value && oldSettings?.sourceId) {\n if (selection.value && !isJsonEqual(selection.value, defaultSelection)) {\n selection.value = createPlSelectionModel();\n }\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n }\n\n // Model updated -> show skeletons instead of data\n const sourceChanged =\n settings.model?.sourceId && settings.model.sourceId !== settings.sourceId;\n if (!settings.model || sourceChanged) {\n const state = gridApi.getServerSideGroupLevelState();\n const rowCount = !sourceChanged && state.length > 0 ? state[0].rowCount : 1;\n return gridApi.updateGridOptions({\n serverSideDatasource: {\n getRows: (params) => {\n params.success({ rowData: [], rowCount });\n },\n },\n });\n }\n\n // Model ready -> calculate new state\n const stateGeneration = generation.value;\n calculateGridOptions({\n generation,\n pfDriver: getRawPlatformaInstance().pFrameDriver,\n fullTableHandle: settings.model?.fullTableHandle,\n visibleTableHandle: settings.model?.visibleTableHandle,\n columnsMeta: settings.model?.columnsMeta,\n sheets: settings.sheets ?? [],\n dataRenderedTracker,\n hiddenColIds: gridState.value.columnVisibility?.hiddenColIds,\n cellButtonAxisParams: {\n showCellButtonForAxisId: props.showCellButtonForAxisId,\n cellButtonInvokeRowsOnDoubleClick: props.cellButtonInvokeRowsOnDoubleClick,\n trigger: (key?: PTableKey) => emit(\"cellButtonClicked\", key),\n } satisfies PlAgCellButtonAxisParams,\n })\n .then((result) => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n const { axesSpec, ...options } = result;\n gridApi.updateGridOptions({\n ...options,\n });\n if (selection.value) {\n // Update selection if axesSpec changed, as order of axes may have changed and so we need to remap selected keys\n const { axesSpec: oldAxesSpec, selectedKeys: oldSelectedKeys } = selection.value;\n if (!isJsonEqual(oldAxesSpec, axesSpec)) {\n if (!oldAxesSpec || axesSpec.length !== oldAxesSpec.length) {\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: [] };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n\n const mapping = oldAxesSpec\n .map(getAxisId)\n .map((id) => axesSpec.findIndex((axis) => matchAxisId(axis, id)));\n const mappingSet = new Set(mapping);\n if (mappingSet.has(-1) || mappingSet.size !== axesSpec.length) {\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: [] };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n\n const selectedNodes = oldSelectedKeys.map((key) =>\n mapping.map((index) => key[index]),\n );\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: selectedNodes };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: selectedNodes.map((key) => canonicalizeJson<PlTableRowId>(key)),\n });\n }\n }\n })\n .catch((error: unknown) => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n if (isAbortError(error)) return;\n console.trace(error);\n })\n .finally(() => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n gridApi.updateGridOptions({\n loading: false,\n });\n });\n dataRenderedTracker.promise.then(() => emit(\"newDataRendered\"));\n } catch (error: unknown) {\n console.trace(error);\n } finally {\n oldSettings = settings;\n }\n },\n);\n\nwatch(\n () => ({\n gridApi: gridApi.value,\n loadingText: props.loadingText,\n runningText: props.runningText,\n notReadyText: props.notReadyText,\n noRowsText: props.noRowsText,\n }),\n ({ gridApi, loadingText, runningText, notReadyText, noRowsText }) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n gridApi.updateGridOptions({\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n loadingText,\n runningText,\n notReadyText,\n },\n noRowsOverlayComponentParams: {\n ...gridOptions.value.noRowsOverlayComponentParams,\n text: noRowsText,\n },\n });\n },\n);\n\nwatchEffect(() => {\n if (!gridApi.value || gridApi.value?.isDestroyed()) return;\n gridApi.value.updateGridOptions({\n statusBar: gridOptions.value.loading\n ? undefined\n : {\n statusPanels: [{ statusPanel: PlAgRowCount, align: \"left\" }],\n },\n });\n});\n</script>\n\n<template>\n <div :class=\"$style.container\">\n <PlAgGridColumnManager v-if=\"gridApi && !disableColumnsPanel\" :api=\"gridApi\" />\n <PlTableFiltersV2\n v-if=\"!disableFiltersPanel\"\n :filters=\"filtersState\"\n :default-filters=\"defaultFiltersState\"\n :pframe-handle=\"'model' in settings ? settings?.model?.fullPframeHandle : undefined\"\n :columns=\"filterableColumns\"\n @updateFilters=\"(v) => (filtersState = v)\"\n @resetDefaultFilters=\"resetDefaultFilters\"\n @updateDefaultFilters=\"(v) => (defaultFiltersState = v)\"\n v-model=\"filtersState\"\n />\n <PlAgCsvExporter\n v-if=\"gridApi && showExportButton\"\n :api=\"gridApi\"\n :table-handle=\"'model' in settings ? settings?.model?.visibleTableHandle : undefined\"\n />\n <PlAgDataTableSheets v-model=\"sheetsState\" :settings=\"sheetsSettings\">\n <template v-if=\"$slots['before-sheets']\" #before>\n <slot name=\"before-sheets\" />\n </template>\n <template v-if=\"$slots['after-sheets']\" #after>\n <slot name=\"after-sheets\" />\n </template>\n </PlAgDataTableSheets>\n <PlTableFastSearch v-model=\"searchString\" />\n <AgGridVue\n :key=\"reloadKey\"\n :theme=\"AgGridTheme\"\n :class=\"$style.grid\"\n :grid-options=\"gridOptions\"\n />\n </div>\n</template>\n\n<style lang=\"css\" module>\n.container {\n display: flex;\n flex-direction: column;\n height: 100%;\n gap: 12px;\n}\n\n.grid {\n flex: 1;\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"PlAgDataTableV2.js","names":[],"sources":["../../../src/components/PlAgDataTable/PlAgDataTableV2.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { promiseTimeout, isJsonEqual } from \"@milaboratories/helpers\";\nimport type {\n AxisId,\n PlDataTableColumnsMeta,\n PlDataTableGridStateCore,\n PlDataTableStateV2,\n PlSelectionModel,\n PlTableColumnIdJson,\n PTableColumnSpec,\n PTableKey,\n} from \"@platforma-sdk/model\";\nimport {\n getRawPlatformaInstance,\n createPlSelectionModel,\n matchAxisId,\n getAxisId,\n canonicalizeJson,\n isAbortError,\n} from \"@platforma-sdk/model\";\nimport type { CellRendererSelectorFunc, GridApi, GridState } from \"ag-grid-enterprise\";\nimport { AgGridVue } from \"ag-grid-vue3\";\nimport { computed, effectScope, nextTick, ref, toRefs, watch, watchEffect } from \"vue\";\nimport PlAgCsvExporter from \"../PlAgCsvExporter/PlAgCsvExporter.vue\";\nimport { PlAgGridColumnManager } from \"../PlAgGridColumnManager\";\nimport PlTableFiltersV2 from \"../PlTableFilters/PlTableFiltersV2.vue\";\nimport { PlTableFastSearch } from \"../PlTableFastSearch\";\nimport PlAgDataTableSheets from \"./PlAgDataTableSheets.vue\";\nimport PlAgRowCount from \"./PlAgRowCount.vue\";\nimport { DeferredCircular, ensureNodeVisible } from \"./sources/focus-row\";\nimport { PlAgDataTableRowNumberColId } from \"./sources/row-number\";\nimport type { PlAgCellButtonAxisParams } from \"./sources/table-source-v2\";\nimport { calculateGridOptions, effectiveVisibility } from \"./sources/table-source-v2\";\nimport { useTableState } from \"./sources/table-state-v2\";\nimport type {\n PlAgDataTableV2Controller,\n PlAgDataTableV2Row,\n PlAgOverlayLoadingParams,\n PlAgOverlayNoRowsParams,\n PlDataTableSettingsV2,\n PlDataTableSheetsSettings,\n PlTableRowId,\n} from \"./types\";\nimport { useFilterableColumns } from \"./compositions/useFilterableColumns\";\nimport { useGrid } from \"./compositions/useGrid\";\nimport { AgGridTheme } from \"../../AgGridVue/AgGridTheme\";\n\nconst tableState = defineModel<PlDataTableStateV2>({\n required: true,\n});\n/** Warning: selection model value updates are ignored, use updateSelection instead */\nconst selection = defineModel<PlSelectionModel>(\"selection\");\nconst props = defineProps<{\n /** Required component settings */\n settings: Readonly<PlDataTableSettingsV2>;\n\n /**\n * The disableColumnsPanel prop controls the display of a button that activates\n * the columns management panel in the table. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n disableColumnsPanel?: boolean;\n\n /**\n * The disableFiltersPanel prop controls the display of a button that activates\n * the filters management panel in the table. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n disableFiltersPanel?: boolean;\n\n /**\n * The showExportButton prop controls the display of a button that allows\n * to export table data in CSV format. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n showExportButton?: boolean;\n\n /**\n * The AxisId property is used to configure and display the PlAgTextAndButtonCell component\n */\n showCellButtonForAxisId?: AxisId;\n\n /**\n * If cellButtonInvokeRowsOnDoubleClick = true, clicking a button inside the row\n * triggers the doubleClick event for the entire row.\n *\n * If cellButtonInvokeRowsOnDoubleClick = false, the doubleClick event for the row\n * is not triggered, but will triggered cellButtonClicked event with (key: PTableRowKey) argument.\n */\n cellButtonInvokeRowsOnDoubleClick?: boolean;\n\n /** @see {@link PlAgOverlayLoadingParams.loadingText} */\n loadingText?: string;\n\n /** @see {@link PlAgOverlayLoadingParams.runningText} */\n runningText?: string;\n\n /** @see {@link PlAgOverlayLoadingParams.notReadyText} */\n notReadyText?: string;\n\n /** @see {@link PlAgOverlayNoRowsParams.text} */\n noRowsText?: string;\n\n /**\n * Callback to override the default renderer for a given cell.\n * @see https://www.ag-grid.com/vue-data-grid/component-cell-renderer/#dynamic-component-selection\n */\n cellRendererSelector?: CellRendererSelectorFunc<PlAgDataTableV2Row>;\n}>();\nconst { settings } = toRefs(props);\nconst emit = defineEmits<{\n rowDoubleClicked: [key?: PTableKey];\n cellButtonClicked: [key?: PTableKey];\n newDataRendered: [];\n}>();\n\nconst dataRenderedTracker = new DeferredCircular<GridApi<PlAgDataTableV2Row>>();\nconst { gridApi, gridOptions } = useGrid({\n selection,\n noRowsText: props.noRowsText,\n runningText: props.runningText,\n loadingText: props.loadingText,\n notReadyText: props.notReadyText,\n cellRendererSelector: props.cellRendererSelector,\n});\nlet isReloading = false;\ngridOptions.value.onGridPreDestroyed = (event) => {\n if (!isReloading) {\n gridOptions.value.initialState = gridState.value = normalizeColumnVisibility(\n makePartialState(event.api.getState()),\n gridState.value,\n event.api,\n getColumnsMeta(),\n );\n }\n gridApi.value = null;\n};\ngridOptions.value.onRowDoubleClicked = (event) => {\n if (event.data && event.data.axesKey) emit(\"rowDoubleClicked\", event.data.axesKey);\n};\ngridOptions.value.onStateUpdated = (event) => {\n const partialState = normalizeColumnVisibility(\n makePartialState(event.state),\n gridState.value,\n event.api,\n getColumnsMeta(),\n );\n // We have to keep initialState synchronized with gridState for gridState recovery after key updating.\n gridOptions.value.initialState = gridState.value = partialState;\n\n if (!isJsonEqual(event.sources, [\"columnSizing\"])) {\n event.api.autoSizeColumns(\n event.api\n .getAllDisplayedColumns()\n .filter((column) => column.getColId() !== PlAgDataTableRowNumberColId),\n );\n }\n};\n\nconst [filterableColumns, visibleFilterableColumns] = useFilterableColumns(\n () => settings.value.sourceId,\n () => gridOptions.value.columnDefs ?? null,\n);\nconst defaultFilters = computed(() =>\n settings.value.sourceId !== null ? settings.value.model?.defaultFilters : undefined,\n);\nconst {\n gridState,\n sheetsState,\n searchString,\n filtersState,\n defaultFiltersState,\n resetDefaultFilters,\n} = useTableState(tableState, settings, visibleFilterableColumns, defaultFilters);\nconst sheetsSettings = computed<PlDataTableSheetsSettings>(() => {\n const settingsCopy = { ...settings.value };\n return settingsCopy.sourceId !== null\n ? {\n sheets: settingsCopy.sheets ?? [],\n cachedState: [...sheetsState.value],\n }\n : {\n sheets: [],\n cachedState: [],\n };\n});\ngridOptions.value.initialState = gridState.value;\n\n// Restore proper types erased by AgGrid\nfunction makePartialState(state: GridState): PlDataTableGridStateCore {\n return {\n columnOrder: state.columnOrder as\n | {\n orderedColIds: PlTableColumnIdJson[];\n }\n | undefined,\n sort: state.sort as\n | {\n sortModel: {\n colId: PlTableColumnIdJson;\n sort: \"asc\" | \"desc\";\n }[];\n }\n | undefined,\n columnVisibility: state.columnVisibility as\n | {\n hiddenColIds: PlTableColumnIdJson[];\n }\n | undefined,\n };\n}\n\n// AG Grid returns columnVisibility: undefined when all columns are visible.\n// We need to distinguish \"no state yet\" (use isColumnOptional defaults) from\n// \"user explicitly showed all columns\" (store []). This function normalizes\n// the undefined from AG Grid based on the previous state.\nfunction normalizeColumnVisibility(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlDataTableGridStateCore {\n if (partialState.columnVisibility !== undefined) return partialState;\n\n if (prevState.columnVisibility !== undefined) {\n // Had explicit visibility state before → user made all columns visible → store [].\n return { ...partialState, columnVisibility: { hiddenColIds: [] } };\n }\n\n // No previous explicit state → compute defaults from current columns\n // to replicate: hide: hiddenColIds?.includes(colId) ?? isColumnOptional(spec.spec)\n const defaultHidden = getDefaultHiddenColIds(api, columnsMeta);\n if (defaultHidden.length > 0) {\n return { ...partialState, columnVisibility: { hiddenColIds: defaultHidden } };\n }\n\n return partialState;\n}\n\n/** Sidecar display meta is only on the `model` branch of the settings union. */\nfunction getColumnsMeta(): PlDataTableColumnsMeta | undefined {\n return \"model\" in props.settings ? props.settings.model?.columnsMeta : undefined;\n}\n\nfunction getDefaultHiddenColIds(\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlTableColumnIdJson[] {\n const cols = api.getAllGridColumns();\n if (!cols) return [];\n\n return cols\n .filter((col) => {\n const spec = col.getColDef().context as PTableColumnSpec | undefined;\n return (\n spec !== undefined &&\n spec.type === \"column\" &&\n effectiveVisibility(spec, columnsMeta) === \"optional\"\n );\n })\n .map((col) => col.getColId() as PlTableColumnIdJson);\n}\n\n// Normalize columnVisibility for comparison: undefined and { hiddenColIds: [] } are equivalent.\nfunction stateForReloadCompare(state: PlDataTableGridStateCore): PlDataTableGridStateCore {\n const cv = state.columnVisibility;\n const normalizedCv = !cv || cv.hiddenColIds.length === 0 ? undefined : state.columnVisibility;\n return { ...state, columnVisibility: normalizedCv };\n}\n\n// Reload AgGrid when new state arrives from server\nconst reloadKey = ref(0);\nwatch(\n () => [gridApi.value, gridState.value] as const,\n ([gridApi, gridState]) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n const selfState = makePartialState(gridApi.getState());\n if (\n !isJsonEqual(gridState, {}) &&\n !isJsonEqual(stateForReloadCompare(gridState), stateForReloadCompare(selfState))\n ) {\n isReloading = true;\n gridOptions.value.initialState = gridState;\n ++reloadKey.value;\n nextTick(() => {\n isReloading = false;\n });\n }\n },\n);\n\n// Make cellRendererSelector reactive\nconst cellRendererSelector = computed(() => props.cellRendererSelector ?? null);\nwatch(\n () => [gridApi.value, cellRendererSelector.value] as const,\n ([gridApi, cellRendererSelector]) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n gridApi.setGridOption(\"defaultColDef\", {\n ...gridOptions.value.defaultColDef,\n cellRendererSelector: cellRendererSelector ?? undefined,\n });\n },\n);\n\ndefineExpose<PlAgDataTableV2Controller>({\n focusRow: async (rowKey) => {\n const gridApi = await dataRenderedTracker.promise;\n if (gridApi.isDestroyed()) return false;\n\n return ensureNodeVisible(gridApi, (row) => isJsonEqual(row.data?.axesKey, rowKey));\n },\n updateSelection: async ({ axesSpec, selectedKeys }) => {\n const gridApi = await dataRenderedTracker.promise;\n if (gridApi.isDestroyed()) return false;\n\n const axes = selection.value?.axesSpec;\n if (!axes || axes.length !== axesSpec.length) return false;\n\n const mapping = axesSpec.map((spec) => {\n const id = getAxisId(spec);\n return axes.findIndex((axis) => matchAxisId(axis, id));\n });\n const mappingSet = new Set(mapping);\n if (mappingSet.has(-1) || mappingSet.size !== axesSpec.length) return false;\n\n const selectedNodes = selectedKeys.map((key) =>\n canonicalizeJson<PlTableRowId>(mapping.map((index) => key[index])),\n );\n const oldSelectedKeys = gridApi.getServerSideSelectionState()?.toggledNodes ?? [];\n if (!isJsonEqual(oldSelectedKeys, selectedNodes)) {\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: selectedNodes,\n });\n\n // wait for `onSelectionChanged` to update `selection` model\n const scope = effectScope();\n const { resolve, promise } = Promise.withResolvers();\n scope.run(() => watch(selection, resolve, { once: true }));\n try {\n await promiseTimeout(promise, 500);\n } catch {\n return false;\n } finally {\n scope.stop();\n }\n }\n return true;\n },\n});\n\n// Update AgGrid when settings change\nconst defaultSelection = createPlSelectionModel();\nlet oldSettings: PlDataTableSettingsV2 | null = null;\nconst generation = ref(0);\nwatch(\n () => [gridApi.value, settings.value] as const,\n ([gridApi, settings]) => {\n // Wait for AgGrid reinitialization, gridApi will eventually become initialized\n if (!gridApi || gridApi.isDestroyed()) return;\n // Verify that this is not a false watch trigger\n if (isJsonEqual(settings, oldSettings)) return;\n ++generation.value;\n try {\n // Hide no rows overlay if it is shown, or else loading overlay will not be shown\n gridApi.hideOverlay();\n dataRenderedTracker.reset();\n\n // No data source selected -> reset state to default\n if (settings.sourceId === null) {\n gridApi.updateGridOptions({\n loading: true,\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n variant: settings.pending ? \"running\" : \"not-ready\",\n } satisfies PlAgOverlayLoadingParams,\n columnDefs: undefined,\n serverSideDatasource: undefined,\n });\n if (selection.value) {\n if (selection.value && !isJsonEqual(selection.value, defaultSelection)) {\n selection.value = createPlSelectionModel();\n }\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n return;\n }\n\n if (\n settings.model?.fullTableHandle === undefined ||\n settings.model?.visibleTableHandle === undefined\n ) {\n return;\n }\n\n // Data source changed -> show full page loader, clear selection\n if (settings.sourceId !== oldSettings?.sourceId) {\n gridApi.updateGridOptions({\n loading: true,\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n variant: \"loading\",\n } satisfies PlAgOverlayLoadingParams,\n });\n if (selection.value && oldSettings?.sourceId) {\n if (selection.value && !isJsonEqual(selection.value, defaultSelection)) {\n selection.value = createPlSelectionModel();\n }\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n }\n\n // Model updated -> show skeletons instead of data\n const sourceChanged =\n settings.model?.sourceId && settings.model.sourceId !== settings.sourceId;\n if (!settings.model || sourceChanged) {\n const state = gridApi.getServerSideGroupLevelState();\n const rowCount = !sourceChanged && state.length > 0 ? state[0].rowCount : 1;\n return gridApi.updateGridOptions({\n serverSideDatasource: {\n getRows: (params) => {\n params.success({ rowData: [], rowCount });\n },\n },\n });\n }\n\n // Model ready -> calculate new state\n const stateGeneration = generation.value;\n calculateGridOptions({\n generation,\n pfDriver: getRawPlatformaInstance().pFrameDriver,\n fullTableHandle: settings.model?.fullTableHandle,\n visibleTableHandle: settings.model?.visibleTableHandle,\n columnsMeta: settings.model?.columnsMeta,\n sheets: settings.sheets ?? [],\n dataRenderedTracker,\n hiddenColIds: gridState.value.columnVisibility?.hiddenColIds,\n cellButtonAxisParams: {\n showCellButtonForAxisId: props.showCellButtonForAxisId,\n cellButtonInvokeRowsOnDoubleClick: props.cellButtonInvokeRowsOnDoubleClick,\n trigger: (key?: PTableKey) => emit(\"cellButtonClicked\", key),\n } satisfies PlAgCellButtonAxisParams,\n })\n .then((result) => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n const { axesSpec, ...options } = result;\n gridApi.updateGridOptions({\n ...options,\n });\n if (selection.value) {\n // Update selection if axesSpec changed, as order of axes may have changed and so we need to remap selected keys\n const { axesSpec: oldAxesSpec, selectedKeys: oldSelectedKeys } = selection.value;\n if (!isJsonEqual(oldAxesSpec, axesSpec)) {\n if (!oldAxesSpec || axesSpec.length !== oldAxesSpec.length) {\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: [] };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n\n const mapping = oldAxesSpec\n .map(getAxisId)\n .map((id) => axesSpec.findIndex((axis) => matchAxisId(axis, id)));\n const mappingSet = new Set(mapping);\n if (mappingSet.has(-1) || mappingSet.size !== axesSpec.length) {\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: [] };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n\n const selectedNodes = oldSelectedKeys.map((key) =>\n mapping.map((index) => key[index]),\n );\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: selectedNodes };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: selectedNodes.map((key) => canonicalizeJson<PlTableRowId>(key)),\n });\n }\n }\n })\n .catch((error: unknown) => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n if (isAbortError(error)) return;\n console.trace(error);\n })\n .finally(() => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n gridApi.updateGridOptions({\n loading: false,\n });\n });\n dataRenderedTracker.promise.then(() => emit(\"newDataRendered\"));\n } catch (error: unknown) {\n console.trace(error);\n } finally {\n oldSettings = settings;\n }\n },\n);\n\nwatch(\n () => ({\n gridApi: gridApi.value,\n loadingText: props.loadingText,\n runningText: props.runningText,\n notReadyText: props.notReadyText,\n noRowsText: props.noRowsText,\n }),\n ({ gridApi, loadingText, runningText, notReadyText, noRowsText }) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n gridApi.updateGridOptions({\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n loadingText,\n runningText,\n notReadyText,\n },\n noRowsOverlayComponentParams: {\n ...gridOptions.value.noRowsOverlayComponentParams,\n text: noRowsText,\n },\n });\n },\n);\n\nwatchEffect(() => {\n if (!gridApi.value || gridApi.value?.isDestroyed()) return;\n gridApi.value.updateGridOptions({\n statusBar: gridOptions.value.loading\n ? undefined\n : {\n statusPanels: [{ statusPanel: PlAgRowCount, align: \"left\" }],\n },\n });\n});\n</script>\n\n<template>\n <div :class=\"$style.container\">\n <PlAgGridColumnManager v-if=\"gridApi && !disableColumnsPanel\" :api=\"gridApi\" />\n <PlTableFiltersV2\n v-if=\"!disableFiltersPanel\"\n :filters=\"filtersState\"\n :default-filters=\"defaultFiltersState\"\n :pframe-handle=\"'model' in settings ? settings?.model?.fullPframeHandle : undefined\"\n :columns=\"filterableColumns\"\n @updateFilters=\"(v) => (filtersState = v)\"\n @resetDefaultFilters=\"resetDefaultFilters\"\n @updateDefaultFilters=\"(v) => (defaultFiltersState = v)\"\n v-model=\"filtersState\"\n />\n <PlAgCsvExporter\n v-if=\"gridApi && showExportButton\"\n :api=\"gridApi\"\n :table-handle=\"'model' in settings ? settings?.model?.visibleTableHandle : undefined\"\n :columns-meta=\"getColumnsMeta()\"\n />\n <PlAgDataTableSheets v-model=\"sheetsState\" :settings=\"sheetsSettings\">\n <template v-if=\"$slots['before-sheets']\" #before>\n <slot name=\"before-sheets\" />\n </template>\n <template v-if=\"$slots['after-sheets']\" #after>\n <slot name=\"after-sheets\" />\n </template>\n </PlAgDataTableSheets>\n <PlTableFastSearch v-model=\"searchString\" />\n <AgGridVue\n :key=\"reloadKey\"\n :theme=\"AgGridTheme\"\n :class=\"$style.grid\"\n :grid-options=\"gridOptions\"\n />\n </div>\n</template>\n\n<style lang=\"css\" module>\n.container {\n display: flex;\n flex-direction: column;\n height: 100%;\n gap: 12px;\n}\n\n.grid {\n flex: 1;\n}\n</style>\n"],"mappings":""}
|