@platforma-sdk/ui-vue 1.65.10 → 1.66.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.
- package/.turbo/turbo-build.log +21 -21
- 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 +6 -0
- package/dist/components/PlAdvancedFilter/FilterEditor.js.map +1 -1
- package/dist/components/PlAdvancedFilter/FilterEditor.style.js.map +1 -1
- package/dist/components/PlAdvancedFilter/FilterEditor.test.d.ts +2 -0
- package/dist/components/PlAdvancedFilter/FilterEditor.test.d.ts.map +1 -0
- package/dist/components/PlAdvancedFilter/FilterEditor.vue.d.ts.map +1 -1
- package/dist/components/PlAdvancedFilter/FilterEditor.vue2.js +142 -145
- package/dist/components/PlAdvancedFilter/FilterEditor.vue2.js.map +1 -1
- package/dist/components/PlAdvancedFilter/PlAdvancedFilter.js.map +1 -1
- package/dist/components/PlAdvancedFilter/PlAdvancedFilter.style.js.map +1 -1
- package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.d.ts.map +1 -1
- package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue2.js.map +1 -1
- package/dist/components/PlAdvancedFilter/types.d.ts +5 -6
- package/dist/components/PlAdvancedFilter/types.d.ts.map +1 -1
- package/dist/components/PlAdvancedFilter/utils.d.ts +1 -0
- package/dist/components/PlAdvancedFilter/utils.d.ts.map +1 -1
- package/dist/components/PlAdvancedFilter/utils.js +10 -1
- package/dist/components/PlAdvancedFilter/utils.js.map +1 -1
- package/dist/components/PlAnnotations/components/AnnotationsSidebar.js.map +1 -1
- package/dist/components/PlAnnotations/components/AnnotationsSidebar.style.js.map +1 -1
- package/dist/components/PlAnnotations/components/AnnotationsSidebar.vue.d.ts +7 -10
- package/dist/components/PlAnnotations/components/AnnotationsSidebar.vue.d.ts.map +1 -1
- package/dist/components/PlAnnotations/components/AnnotationsSidebar.vue2.js +71 -50
- package/dist/components/PlAnnotations/components/AnnotationsSidebar.vue2.js.map +1 -1
- package/dist/components/PlAnnotations/components/FilterSidebar.js.map +1 -1
- package/dist/components/PlAnnotations/components/FilterSidebar.style.js.map +1 -1
- package/dist/components/PlAnnotations/components/FilterSidebar.vue.d.ts +5 -7
- package/dist/components/PlAnnotations/components/FilterSidebar.vue.d.ts.map +1 -1
- package/dist/components/PlAnnotations/components/FilterSidebar.vue2.js +81 -67
- package/dist/components/PlAnnotations/components/FilterSidebar.vue2.js.map +1 -1
- package/dist/components/PlAnnotations/components/PlAnnotations.js.map +1 -1
- package/dist/components/PlAnnotations/components/PlAnnotations.style.js.map +1 -1
- package/dist/components/PlAnnotations/components/PlAnnotations.vue.d.ts +4 -14
- package/dist/components/PlAnnotations/components/PlAnnotations.vue.d.ts.map +1 -1
- package/dist/components/PlAnnotations/components/PlAnnotations.vue2.js +43 -38
- package/dist/components/PlAnnotations/components/PlAnnotations.vue2.js.map +1 -1
- package/dist/components/PlAnnotations/components/PlAnnotationsModal.js.map +1 -1
- package/dist/components/PlAnnotations/components/PlAnnotationsModal.style.js.map +1 -1
- package/dist/components/PlAnnotations/components/PlAnnotationsModal.vue.d.ts +5 -13
- package/dist/components/PlAnnotations/components/PlAnnotationsModal.vue.d.ts.map +1 -1
- package/dist/components/PlAnnotations/components/PlAnnotationsModal.vue2.js +37 -40
- package/dist/components/PlAnnotations/components/PlAnnotationsModal.vue2.js.map +1 -1
- package/package.json +6 -6
- package/src/components/PlAdvancedFilter/FilterEditor.test.ts +315 -0
- package/src/components/PlAdvancedFilter/FilterEditor.vue +12 -18
- package/src/components/PlAdvancedFilter/PlAdvancedFilter.vue +1 -6
- package/src/components/PlAdvancedFilter/types.ts +6 -8
- package/src/components/PlAdvancedFilter/utils.ts +20 -0
- package/src/components/PlAnnotations/components/AnnotationsSidebar.vue +59 -30
- package/src/components/PlAnnotations/components/FilterSidebar.vue +65 -40
- package/src/components/PlAnnotations/components/PlAnnotations.vue +35 -19
- package/src/components/PlAnnotations/components/PlAnnotationsModal.vue +18 -21
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.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.66.0 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 11603ms.
|
|
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
|
|
@@ -115,20 +115,20 @@ dist/plugins/Monetization/LimitCard.js
|
|
|
115
115
|
dist/plugins/Monetization/RunStatus.js 0.34 kB │ gzip: 0.24 kB │ map: 2.30 kB
|
|
116
116
|
dist/components/BlockLayout.js 0.34 kB │ gzip: 0.24 kB │ map: 2.10 kB
|
|
117
117
|
dist/plugins/Monetization/EndOfPeriod.js 0.34 kB │ gzip: 0.24 kB │ map: 1.21 kB
|
|
118
|
-
dist/components/PlAdvancedFilter/FilterEditor.js 0.35 kB │ gzip: 0.24 kB │ map: 16.
|
|
118
|
+
dist/components/PlAdvancedFilter/FilterEditor.js 0.35 kB │ gzip: 0.24 kB │ map: 16.43 kB
|
|
119
119
|
dist/components/PlAdvancedFilter/OperandButton.js 0.35 kB │ gzip: 0.24 kB │ map: 1.31 kB
|
|
120
|
-
dist/components/PlAnnotations/components/FilterSidebar.js 0.35 kB │ gzip: 0.24 kB │ map:
|
|
121
|
-
dist/components/PlAnnotations/components/PlAnnotations.js 0.35 kB │ gzip: 0.24 kB │ map:
|
|
120
|
+
dist/components/PlAnnotations/components/FilterSidebar.js 0.35 kB │ gzip: 0.24 kB │ map: 5.40 kB
|
|
121
|
+
dist/components/PlAnnotations/components/PlAnnotations.js 0.35 kB │ gzip: 0.24 kB │ map: 3.11 kB
|
|
122
122
|
dist/components/PlAgDataTable/PlAgDataTableV2.js 0.35 kB │ gzip: 0.25 kB │ map: 21.62 kB
|
|
123
123
|
dist/plugins/Monetization/UserCabinetCard.js 0.35 kB │ gzip: 0.24 kB │ map: 3.00 kB
|
|
124
|
-
dist/components/PlAdvancedFilter/PlAdvancedFilter.js 0.36 kB │ gzip: 0.24 kB │ map: 13.
|
|
124
|
+
dist/components/PlAdvancedFilter/PlAdvancedFilter.js 0.36 kB │ gzip: 0.24 kB │ map: 13.36 kB
|
|
125
125
|
dist/components/PlAgDataTable/PlAgOverlayNoRows.js 0.36 kB │ gzip: 0.21 kB │ map: 0.74 kB
|
|
126
126
|
dist/components/PlTableFilters/PlTableFiltersV2.js 0.36 kB │ gzip: 0.25 kB │ map: 6.53 kB
|
|
127
127
|
dist/components/PlAgCellStatusTag/PlAgCellStatusTag.js 0.36 kB │ gzip: 0.20 kB │ map: 0.74 kB
|
|
128
128
|
dist/components/PlBtnExportArchive/PlBtnExportArchive.js 0.36 kB │ gzip: 0.24 kB │ map: 7.17 kB
|
|
129
129
|
dist/components/PlAgDataTable/PlAgDataTableSheets.js 0.37 kB │ gzip: 0.25 kB │ map: 3.61 kB
|
|
130
|
-
dist/components/PlAnnotations/components/AnnotationsSidebar.js 0.37 kB │ gzip: 0.24 kB │ map:
|
|
131
|
-
dist/components/PlAnnotations/components/PlAnnotationsModal.js 0.37 kB │ gzip: 0.24 kB │ map: 1.
|
|
130
|
+
dist/components/PlAnnotations/components/AnnotationsSidebar.js 0.37 kB │ gzip: 0.24 kB │ map: 4.56 kB
|
|
131
|
+
dist/components/PlAnnotations/components/PlAnnotationsModal.js 0.37 kB │ gzip: 0.24 kB │ map: 1.36 kB
|
|
132
132
|
dist/components/PlAgGridColumnManager/PlAgGridColumnManager.js 0.37 kB │ gzip: 0.25 kB │ map: 3.46 kB
|
|
133
133
|
dist/lib/util/helpers/dist/strings.js 0.39 kB │ gzip: 0.27 kB │ map: 4.11 kB
|
|
134
134
|
dist/components/PlAgChartHistogramCell/PlAgChartHistogramCell.js 0.39 kB │ gzip: 0.21 kB │ map: 0.77 kB
|
|
@@ -185,29 +185,29 @@ dist/components/PlAgTextAndButtonCell/PlAgTextAndButtonCell.vue_vue_type_script_
|
|
|
185
185
|
dist/internal/createAppModel.js 1.48 kB │ gzip: 0.75 kB │ map: 3.89 kB
|
|
186
186
|
dist/components/PlAgCsvExporter/export-csv.js 1.51 kB │ gzip: 0.73 kB │ map: 3.50 kB
|
|
187
187
|
dist/plugins/Monetization/useInfo.js 1.51 kB │ gzip: 0.77 kB │ map: 3.85 kB
|
|
188
|
-
dist/components/PlAdvancedFilter/utils.js 1.55 kB │ gzip: 0.71 kB │ map: 5.22 kB
|
|
189
188
|
dist/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue_vue_type_script_setup_true_lang.js 1.67 kB │ gzip: 0.84 kB │ map: 2.62 kB
|
|
189
|
+
dist/components/PlAnnotations/components/PlAnnotationsModal.vue_vue_type_script_setup_true_lang.js 1.75 kB │ gzip: 0.75 kB │ map: 1.78 kB
|
|
190
190
|
dist/AgGridVue/createAgGridColDef.js 1.76 kB │ gzip: 0.75 kB │ map: 9.21 kB
|
|
191
|
+
dist/components/PlAdvancedFilter/utils.js 1.76 kB │ gzip: 0.81 kB │ map: 6.00 kB
|
|
191
192
|
dist/components/PlAdvancedFilter/constants.js 1.80 kB │ gzip: 0.55 kB │ map: 3.67 kB
|
|
192
193
|
dist/components/PlTableFastSearch/PlTableFastSearch.vue_vue_type_script_setup_true_lang.js 1.82 kB │ gzip: 0.91 kB │ map: 2.10 kB
|
|
193
194
|
dist/components/PlAgRowNumHeader.vue_vue_type_script_setup_true_lang.js 1.96 kB │ gzip: 0.85 kB │ map: 3.50 kB
|
|
194
|
-
dist/components/PlAnnotations/components/PlAnnotationsModal.vue_vue_type_script_setup_true_lang.js 1.98 kB │ gzip: 0.90 kB │ map: 2.26 kB
|
|
195
195
|
dist/plugins/Monetization/UserCabinetCard.vue_vue_type_script_setup_true_lang.js 2.06 kB │ gzip: 1.00 kB │ map: 4.19 kB
|
|
196
196
|
dist/components/BlockLayout.vue_vue_type_script_setup_true_lang.js 2.18 kB │ gzip: 1.03 kB │ map: 5.92 kB
|
|
197
197
|
dist/components/PlAppErrorNotificationAlert/PlAppErrorNotificationAlert.vue_vue_type_script_setup_true_lang.js 2.40 kB │ gzip: 1.12 kB │ map: 6.67 kB
|
|
198
198
|
dist/components/PlAgDataTable/PlAgDataTableSheets.vue_vue_type_script_setup_true_lang.js 2.45 kB │ gzip: 1.17 kB │ map: 5.01 kB
|
|
199
199
|
dist/lib.js 2.60 kB │ gzip: 0.56 kB │ map: 3.82 kB
|
|
200
200
|
dist/components/PlAgColumnHeader/PlAgColumnHeader.vue_vue_type_script_setup_true_lang.js 2.62 kB │ gzip: 1.15 kB │ map: 6.90 kB
|
|
201
|
-
dist/components/PlAnnotations/components/PlAnnotations.vue_vue_type_script_setup_true_lang.js 2.
|
|
201
|
+
dist/components/PlAnnotations/components/PlAnnotations.vue_vue_type_script_setup_true_lang.js 2.69 kB │ gzip: 1.12 kB │ map: 4.16 kB
|
|
202
202
|
dist/components/PlAgDataTable/compositions/useGrid.js 2.80 kB │ gzip: 1.24 kB │ map: 6.28 kB
|
|
203
203
|
dist/components/PlAgDataTable/sources/row-number.js 2.99 kB │ gzip: 1.31 kB │ map: 7.42 kB
|
|
204
204
|
dist/defineApp.js 3.01 kB │ gzip: 1.05 kB │ map: 14.38 kB
|
|
205
205
|
dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue_vue_type_script_setup_true_lang.js 3.14 kB │ gzip: 1.42 kB │ map: 5.04 kB
|
|
206
206
|
dist/plugins/Monetization/LimitCard.vue_vue_type_script_setup_true_lang.js 3.31 kB │ gzip: 1.15 kB │ map: 9.08 kB
|
|
207
|
-
dist/components/PlAnnotations/components/AnnotationsSidebar.vue_vue_type_script_setup_true_lang.js 3.71 kB │ gzip: 1.59 kB │ map: 5.53 kB
|
|
208
207
|
dist/composition/fileContent.js 3.78 kB │ gzip: 1.45 kB │ map: 12.95 kB
|
|
209
|
-
dist/components/PlAnnotations/components/
|
|
208
|
+
dist/components/PlAnnotations/components/AnnotationsSidebar.vue_vue_type_script_setup_true_lang.js 3.89 kB │ gzip: 1.61 kB │ map: 6.51 kB
|
|
210
209
|
dist/plugins/Monetization/MonetizationSidebar.vue_vue_type_script_setup_true_lang.js 3.96 kB │ gzip: 1.71 kB │ map: 5.71 kB
|
|
210
|
+
dist/components/PlAnnotations/components/FilterSidebar.vue_vue_type_script_setup_true_lang.js 3.97 kB │ gzip: 1.56 kB │ map: 7.42 kB
|
|
211
211
|
dist/internal/createAppV1.js 4.01 kB │ gzip: 1.42 kB │ map: 14.21 kB
|
|
212
212
|
dist/AgGridVue/useAgGridOptions.js 4.23 kB │ gzip: 1.33 kB │ map: 14.12 kB
|
|
213
213
|
dist/index.js 4.42 kB │ gzip: 1.35 kB │ map: 0.25 kB
|
|
@@ -217,17 +217,17 @@ dist/internal/createAppV2.js
|
|
|
217
217
|
dist/components/PlAgDataTable/sources/table-state-v2.js 5.73 kB │ gzip: 1.85 kB │ map: 19.41 kB
|
|
218
218
|
dist/internal/createAppV3.js 6.21 kB │ gzip: 2.39 kB │ map: 21.41 kB
|
|
219
219
|
dist/components/PlAgDataTable/sources/table-source-v2.js 6.85 kB │ gzip: 2.69 kB │ map: 23.67 kB
|
|
220
|
-
dist/components/PlAdvancedFilter/PlAdvancedFilter.vue_vue_type_script_setup_true_lang.js 8.54 kB │ gzip: 2.59 kB │ map: 18.
|
|
221
|
-
dist/components/PlAdvancedFilter/FilterEditor.vue_vue_type_script_setup_true_lang.js 10.
|
|
220
|
+
dist/components/PlAdvancedFilter/PlAdvancedFilter.vue_vue_type_script_setup_true_lang.js 8.54 kB │ gzip: 2.59 kB │ map: 18.44 kB
|
|
221
|
+
dist/components/PlAdvancedFilter/FilterEditor.vue_vue_type_script_setup_true_lang.js 10.24 kB │ gzip: 3.04 kB │ map: 23.20 kB
|
|
222
222
|
dist/components/PlAgDataTable/PlAgDataTableV2.vue_vue_type_script_setup_true_lang.js 12.10 kB │ gzip: 3.86 kB │ map: 28.98 kB
|
|
223
223
|
|
|
224
224
|
[33m[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugins. Here is a breakdown:
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
- vite:
|
|
228
|
-
- vite:
|
|
229
|
-
- vite:css (
|
|
225
|
+
- vite:dts (37%)
|
|
226
|
+
- sourcemaps (25%)
|
|
227
|
+
- vite:css-post (11%)
|
|
228
|
+
- vite:vue (9%)
|
|
229
|
+
- vite:css (6%)
|
|
230
230
|
See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
231
231
|
[39m
|
|
232
|
-
[32m✓ built in
|
|
232
|
+
[32m✓ built in 12.62s[39m
|
|
233
233
|
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.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.66.0 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 2408ms on 130 files using 8 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.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.66.0 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 30ms on 113 files with 98 rules using 8 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.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.66.0 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterEditor.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport {\n PlAutocomplete,\n PlAutocompleteMulti,\n PlDropdown,\n PlIcon16,\n PlNumberField,\n PlTextField,\n PlToggleSwitch,\n Slider,\n} from \"@milaboratories/uikit\";\nimport type {\n AnchoredPColumnId,\n AxisFilterByIdx,\n AxisFilterValue,\n SUniversalPColumnId,\n} from \"@platforma-sdk/model\";\nimport {\n isFilteredPColumn,\n parseColumnId,\n stringifyColumnId,\n type ListOptionBase,\n} from \"@platforma-sdk/model\";\nimport { computed } from \"vue\";\nimport type { SUPPORTED_FILTER_TYPES } from \"./constants\";\nimport { DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from \"./constants\";\nimport OperandButton from \"./OperandButton.vue\";\nimport type { EditableFilter, Operand, PlAdvancedFilterColumnId, SourceOptionInfo } from \"./types\";\nimport { getFilterInfo, getNormalizedSpec, isNumericFilter, isPositionFilter } from \"./utils\";\nimport { Entries } from \"@milaboratories/helpers\";\n\nconst props = defineProps<{\n filter: EditableFilter;\n isLast: boolean;\n operand: Operand;\n enableDnd: boolean;\n columnOptions: SourceOptionInfo[];\n supportedFilters: typeof SUPPORTED_FILTER_TYPES;\n getSuggestOptions: (params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchType: \"value\" | \"label\";\n searchStr: string;\n }) => ListOptionBase<string | number>[] | Promise<ListOptionBase<string | number>[]>;\n onDelete: (columnId: PlAdvancedFilterColumnId) => void;\n onUpdateFilter: (filter: EditableFilter) => void;\n onChangeOperand: (op: Operand) => void;\n}>();\n\ntype AllKeys<U> = U extends unknown ? keyof U : never;\ntype ValueOf<U, K extends string> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;\n\nfunction updateFilterProp<K extends AllKeys<EditableFilter> & string>(\n key: K,\n v: ValueOf<EditableFilter, K>,\n) {\n props.onUpdateFilter({ ...props.filter, [key]: v } as EditableFilter);\n}\n\nasync function getSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string,\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n return props.getSuggestOptions({\n columnId: id,\n axisIdx,\n searchType: type,\n searchStr: str,\n }) as Promise<ListOptionBase<string>[]>;\n}\n\nasync function getMultiSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string | string[],\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n if (type === \"label\" && typeof str === \"string\") {\n return getSuggestOptionsFn(id, type, str, axisIdx);\n }\n if (type === \"value\" && Array.isArray(str)) {\n const results = await Promise.all(str.map((s) => getSuggestOptionsFn(id, type, s, axisIdx)));\n return results.map((x) => x[0]);\n }\n throw new Error(\"Invalid arguments combination\");\n}\n\nfunction changeFilterType(newType: EditableFilter[\"type\"]) {\n const defaultFilter = DEFAULT_FILTERS[newType];\n\n props.onUpdateFilter(\n (Object.entries(defaultFilter) as Entries<EditableFilter>).reduce(\n (res, [key, val]) => {\n res[key] = props.filter[key] ?? val;\n return res;\n },\n { ...props.filter, type: newType } as Record<\n keyof EditableFilter,\n EditableFilter[keyof EditableFilter]\n >,\n ) as EditableFilter,\n );\n}\n\nfunction changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {\n if (!newSourceId) {\n return;\n }\n const newSourceInfo = props.columnOptions.find((v) => v.id === getSourceId(newSourceId));\n if (!newSourceInfo) {\n return;\n }\n const filterInfo = getFilterInfo(props.filter.type);\n const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);\n if (filterInfo.supportedFor(newSourceSpec)) {\n props.onUpdateFilter({ ...props.filter, column: newSourceId });\n } else {\n props.onUpdateFilter({\n ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],\n column: newSourceId,\n });\n }\n}\n\nconst inconsistentSourceSelected = computed(() => {\n const selectedOption = props.columnOptions.find(\n (op) => op.id === getSourceId(props.filter.column),\n );\n return selectedOption === undefined;\n});\nconst sourceOptions = computed(() => {\n const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));\n if (inconsistentSourceSelected.value) {\n options.unshift({ value: props.filter.column, label: \"Inconsistent value\" });\n }\n return options;\n});\n\nfunction getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return stringifyColumnId(parsedColumnId.source);\n } else {\n return column;\n }\n } catch {\n return column;\n }\n}\n\n// similar to FilteredPColumnId but source is stringified and axis filters can be undefined\ntype ColumnAsSourceAndFixedAxes = {\n source: PlAdvancedFilterColumnId;\n axisFiltersByIndex: Record<number, AxisFilterValue | undefined>;\n};\nfunction getColumnAsSourceAndFixedAxes(\n column: PlAdvancedFilterColumnId,\n): ColumnAsSourceAndFixedAxes {\n const sourceId = getSourceId(column);\n const option = props.columnOptions.find((op) => op.id === sourceId);\n const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce(\n (res, item) => {\n res[item.idx] = undefined;\n return res;\n },\n {} as Record<number, AxisFilterValue | undefined>,\n );\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return {\n source: sourceId,\n axisFiltersByIndex: parsedColumnId.axisFilters.reduce((res, item) => {\n res[item[0]] = item[1];\n return res;\n }, axesToBeFixed),\n };\n }\n } catch {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n}\n\nfunction stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {\n if (Object.keys(value.axisFiltersByIndex).length === 0) {\n return value.source;\n }\n return stringifyColumnId({\n source: parseColumnId(value.source as SUniversalPColumnId) as AnchoredPColumnId,\n axisFilters: Object.entries(value.axisFiltersByIndex).map(\n ([idx, value]) => [Number(idx), value] as AxisFilterByIdx,\n ),\n });\n}\n\nconst columnAsSourceAndFixedAxes = computed({\n get: () => {\n return getColumnAsSourceAndFixedAxes(props.filter.column);\n },\n set: (value) => {\n props.onUpdateFilter({ ...props.filter, column: stringifyColumn(value) });\n },\n});\nfunction updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {\n columnAsSourceAndFixedAxes.value = {\n ...columnAsSourceAndFixedAxes.value,\n axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value },\n };\n}\n\nconst currentOption = computed(() =>\n props.columnOptions.find((op) => op.id === columnAsSourceAndFixedAxes.value.source),\n);\nconst currentSpec = computed(() =>\n currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null,\n);\nconst currentType = computed(() => currentSpec.value?.valueType);\nconst currentError = computed(\n () => Boolean(currentOption.value?.error) || inconsistentSourceSelected.value,\n);\n\nconst filterTypesOptions = computed(() =>\n props.supportedFilters\n .filter(\n (v) =>\n props.filter.type === v ||\n (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),\n )\n .map((v) => ({ value: v, label: getFilterInfo(v).label })),\n);\n\nconst wildcardOptions = computed(() => {\n if (props.filter.type !== \"patternFuzzyContainSubsequence\") {\n return [];\n }\n if (currentOption.value?.alphabet === \"nucleotide\") {\n return [{ label: \"N\", value: \"N\" }];\n }\n if (currentOption.value?.alphabet === \"aminoacid\") {\n return [{ label: \"X\", value: \"X\" }];\n }\n return [...new Set(props.filter.value.split(\"\"))].sort().map((v) => ({ value: v, label: v }));\n});\n\nconst stringMatchesError = computed(() => {\n if (props.filter.type !== \"patternMatchesRegularExpression\") {\n return false;\n }\n try {\n new RegExp(props.filter.value);\n return false;\n } catch {\n return true;\n }\n});\n</script>\n<template>\n <div :class=\"$style.filterWrapper\">\n <!-- top element - column selector / column label - for all filter types-->\n <div\n v-if=\"enableDnd\"\n :class=\"[$style.top, $style.columnChip, { [$style.error]: currentError }]\"\n >\n <div :class=\"[$style.typeIcon, { [$style.error]: currentError }]\">\n <PlIcon16 v-if=\"currentError\" name=\"warning\" />\n <PlIcon16\n v-else\n :name=\"\n currentType === 'String' || currentType === undefined\n ? 'cell-type-txt'\n : 'cell-type-num'\n \"\n />\n </div>\n <div :class=\"$style.titleWrapper\" :title=\"currentOption?.label ?? ''\">\n <div :class=\"$style.title\">\n {{\n inconsistentSourceSelected\n ? \"Inconsistent value\"\n : (currentOption?.label ?? props.filter.column)\n }}\n </div>\n </div>\n <div :class=\"$style.closeIcon\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n <div v-else :class=\"$style.top\">\n <PlDropdown\n :model-value=\"columnAsSourceAndFixedAxes.source\"\n :errorStatus=\"currentError\"\n :options=\"sourceOptions\"\n :style=\"{ width: '100%' }\"\n group-position=\"top-left\"\n @update:model-value=\"changeSourceId\"\n />\n <div :class=\"$style.closeButton\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n\n <div v-if=\"currentOption?.axesToBeFixed?.length\" :class=\"$style.fixedAxesBlock\">\n <template v-for=\"value in currentOption?.axesToBeFixed\" :key=\"value.idx\">\n <PlAutocomplete\n :model-value=\"columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]\"\n :label=\"value.label\"\n :options-search=\"\n (str, type) =>\n getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str, value.idx)\n \"\n :disabled=\"inconsistentSourceSelected\"\n :clearable=\"true\"\n @update:model-value=\"(v) => updateAxisFilterValue(value.idx, v)\"\n />\n </template>\n </div>\n\n <!-- middle - filter type selector - for all filter types -->\n <div\n :class=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA'\n ? $style.bottom\n : $style.middle\n \"\n >\n <PlDropdown\n :model-value=\"props.filter.type\"\n :options=\"filterTypesOptions\"\n :group-position=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA' ? 'bottom' : 'middle'\n \"\n @update:model-value=\"(v) => changeFilterType(v!)\"\n />\n </div>\n\n <!-- middle - for fuzzy contains filter -->\n <template v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\">\n <div :class=\"$style.middle\">\n <PlTextField\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"middle\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n </div>\n <div :class=\"$style.innerSection\">\n <Slider\n :model-value=\"props.filter.maxEdits\"\n :max=\"5\"\n breakpoints\n label=\"Maximum number of substitutions and indels\"\n @update:model-value=\"(v) => updateFilterProp('maxEdits', v)\"\n />\n <PlToggleSwitch\n :model-value=\"props.filter.substitutionsOnly\"\n label=\"Substitutions only\"\n @update:model-value=\"(v) => updateFilterProp('substitutionsOnly', v)\"\n />\n </div>\n </template>\n\n <!-- bottom element - individual settings for every filter type -->\n <div :class=\"$style.bottom\">\n <PlAutocomplete\n v-if=\"props.filter.type === 'patternEquals' || props.filter.type === 'patternNotEquals'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :clearable=\"true\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlAutocompleteMulti\n v-if=\"props.filter.type === 'inSet' || props.filter.type === 'notInSet'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getMultiSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :disabled=\"inconsistentSourceSelected\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlNumberField\n v-if=\"isNumericFilter(props.filter)\"\n :model-value=\"props.filter.x\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('x', v)\"\n />\n <PlNumberField\n v-if=\"isPositionFilter(props.filter)\"\n :model-value=\"props.filter.n\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('n', v)\"\n />\n <PlTextField\n v-if=\"\n props.filter.type === 'patternContainSubsequence' ||\n props.filter.type === 'patternNotContainSubsequence'\n \"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlTextField\n v-if=\"props.filter.type === 'patternMatchesRegularExpression'\"\n :model-value=\"props.filter.value\"\n :error=\"stringMatchesError ? 'Regular expression is not valid' : undefined\"\n placeholder=\"Regular expression\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlDropdown\n v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\"\n :model-value=\"props.filter.wildcard\"\n clearable\n placeholder=\"Wildcard value\"\n :options=\"wildcardOptions\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('wildcard', v)\"\n />\n </div>\n </div>\n <OperandButton :active=\"operand\" :disabled=\"isLast\" @select=\"onChangeOperand\" />\n</template>\n\n<style module>\n.filterWrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n cursor: default;\n}\n\n.typeIcon {\n display: inline-flex;\n margin-right: 8px;\n}\n\n.typeIcon.error {\n --icon-color: var(--txt-error);\n}\n\n.closeIcon {\n display: inline-flex;\n margin-left: 12px;\n cursor: pointer;\n}\n\n.titleWrapper {\n flex-grow: 1;\n overflow: hidden;\n}\n.title {\n overflow: hidden;\n color: var(--txt-01);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.columnChip {\n width: 100%;\n display: flex;\n padding: 10px 12px;\n align-items: center;\n border-radius: 6px;\n border: 1px solid var(--txt-01);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &.error {\n border-color: var(--txt-error);\n }\n}\n\n.innerSection {\n border: 1px solid var(--txt-01);\n border-top: none;\n padding: 16px 12px;\n}\n\n.closeButton {\n border: 1px solid var(--txt-01);\n border-top-right-radius: 6px;\n border-left: none;\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.top {\n position: relative;\n display: flex;\n width: 100%;\n z-index: 1;\n background: #fff;\n}\n\n.fixedAxesBlock {\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 8px;\n gap: 12px;\n border-left: 1px solid var(--txt-01);\n border-right: 1px solid var(--txt-01);\n}\n\n.fixedAxesBlock > * {\n background: #fff;\n}\n\n.middle,\n.bottom {\n position: relative;\n margin-top: -1px;\n background: #fff;\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"FilterEditor.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport {\n PlAutocomplete,\n PlAutocompleteMulti,\n PlDropdown,\n PlIcon16,\n PlNumberField,\n PlTextField,\n PlToggleSwitch,\n Slider,\n} from \"@milaboratories/uikit\";\nimport type {\n AnchoredPColumnId,\n AxisFilterByIdx,\n AxisFilterValue,\n SUniversalPColumnId,\n} from \"@platforma-sdk/model\";\nimport {\n isFilteredPColumn,\n parseColumnId,\n stringifyColumnId,\n type ListOptionBase,\n} from \"@platforma-sdk/model\";\nimport { computed } from \"vue\";\nimport type { SUPPORTED_FILTER_TYPES } from \"./constants\";\nimport { DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from \"./constants\";\nimport OperandButton from \"./OperandButton.vue\";\nimport type { EditableFilter, Operand, PlAdvancedFilterColumnId, SourceOptionInfo } from \"./types\";\nimport {\n getFilterInfo,\n getNormalizedSpec,\n isNumericFilter,\n isPositionFilter,\n mergeFilterForTypeChange,\n} from \"./utils\";\nimport { isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n filter: EditableFilter;\n isLast: boolean;\n operand: Operand;\n enableDnd: boolean;\n columnOptions: SourceOptionInfo[];\n supportedFilters: typeof SUPPORTED_FILTER_TYPES;\n getSuggestOptions: (params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchType: \"value\" | \"label\";\n searchStr: string;\n }) => ListOptionBase<string | number>[] | Promise<ListOptionBase<string | number>[]>;\n onDelete: (columnId: PlAdvancedFilterColumnId) => void;\n onUpdateFilter: (filter: EditableFilter) => void;\n onChangeOperand: (op: Operand) => void;\n}>();\n\ntype AllKeys<U> = U extends unknown ? keyof U : never;\ntype ValueOf<U, K extends string> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;\n\nfunction updateFilterProp<K extends AllKeys<EditableFilter> & string>(\n key: K,\n v: ValueOf<EditableFilter, K>,\n) {\n props.onUpdateFilter({ ...props.filter, [key]: v } as EditableFilter);\n}\n\nasync function getSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string,\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n return props.getSuggestOptions({\n columnId: id,\n axisIdx,\n searchType: type,\n searchStr: str,\n }) as Promise<ListOptionBase<string>[]>;\n}\n\nasync function getMultiSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string | string[],\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n if (type === \"label\" && typeof str === \"string\") {\n return getSuggestOptionsFn(id, type, str, axisIdx);\n }\n if (type === \"value\" && Array.isArray(str)) {\n const results = await Promise.all(str.map((s) => getSuggestOptionsFn(id, type, s, axisIdx)));\n return results.map((x) => x[0]);\n }\n throw new Error(\"Invalid arguments combination\");\n}\n\nfunction changeFilterType(newType?: EditableFilter[\"type\"]) {\n if (isNil(newType)) return;\n props.onUpdateFilter(mergeFilterForTypeChange(props.filter, newType));\n}\n\nfunction changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {\n if (!newSourceId) {\n return;\n }\n const newSourceInfo = props.columnOptions.find((v) => v.id === getSourceId(newSourceId));\n if (!newSourceInfo) {\n return;\n }\n const filterInfo = getFilterInfo(props.filter.type);\n const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);\n if (filterInfo.supportedFor(newSourceSpec)) {\n props.onUpdateFilter({ ...props.filter, column: newSourceId });\n } else {\n props.onUpdateFilter({\n ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],\n column: newSourceId,\n });\n }\n}\n\nconst inconsistentSourceSelected = computed(() => {\n const selectedOption = props.columnOptions.find(\n (op) => op.id === getSourceId(props.filter.column),\n );\n return selectedOption === undefined;\n});\nconst sourceOptions = computed(() => {\n const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));\n if (inconsistentSourceSelected.value) {\n options.unshift({ value: props.filter.column, label: \"Inconsistent value\" });\n }\n return options;\n});\n\nfunction getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return stringifyColumnId(parsedColumnId.source);\n } else {\n return column;\n }\n } catch {\n return column;\n }\n}\n\n// similar to FilteredPColumnId but source is stringified and axis filters can be undefined\ntype ColumnAsSourceAndFixedAxes = {\n source: PlAdvancedFilterColumnId;\n axisFiltersByIndex: Record<number, AxisFilterValue | undefined>;\n};\nfunction getColumnAsSourceAndFixedAxes(\n column: PlAdvancedFilterColumnId,\n): ColumnAsSourceAndFixedAxes {\n const sourceId = getSourceId(column);\n const option = props.columnOptions.find((op) => op.id === sourceId);\n const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce(\n (res, item) => {\n res[item.idx] = undefined;\n return res;\n },\n {} as Record<number, AxisFilterValue | undefined>,\n );\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return {\n source: sourceId,\n axisFiltersByIndex: parsedColumnId.axisFilters.reduce((res, item) => {\n res[item[0]] = item[1];\n return res;\n }, axesToBeFixed),\n };\n }\n } catch {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n}\n\nfunction stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {\n if (Object.keys(value.axisFiltersByIndex).length === 0) {\n return value.source;\n }\n return stringifyColumnId({\n source: parseColumnId(value.source as SUniversalPColumnId) as AnchoredPColumnId,\n axisFilters: Object.entries(value.axisFiltersByIndex).map(\n ([idx, value]) => [Number(idx), value] as AxisFilterByIdx,\n ),\n });\n}\n\nconst columnAsSourceAndFixedAxes = computed({\n get: () => {\n return getColumnAsSourceAndFixedAxes(props.filter.column);\n },\n set: (value) => {\n props.onUpdateFilter({ ...props.filter, column: stringifyColumn(value) });\n },\n});\nfunction updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {\n columnAsSourceAndFixedAxes.value = {\n ...columnAsSourceAndFixedAxes.value,\n axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value },\n };\n}\n\nconst currentOption = computed(() =>\n props.columnOptions.find((op) => op.id === columnAsSourceAndFixedAxes.value.source),\n);\nconst currentSpec = computed(() =>\n currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null,\n);\nconst currentType = computed(() => currentSpec.value?.valueType);\nconst currentError = computed(\n () => Boolean(currentOption.value?.error) || inconsistentSourceSelected.value,\n);\n\nconst filterTypesOptions = computed(() =>\n props.supportedFilters\n .filter(\n (v) =>\n props.filter.type === v ||\n (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),\n )\n .map((v) => ({ value: v, label: getFilterInfo(v).label })),\n);\n\nconst wildcardOptions = computed(() => {\n if (props.filter.type !== \"patternFuzzyContainSubsequence\") {\n return [];\n }\n if (currentOption.value?.alphabet === \"nucleotide\") {\n return [{ label: \"N\", value: \"N\" }];\n }\n if (currentOption.value?.alphabet === \"aminoacid\") {\n return [{ label: \"X\", value: \"X\" }];\n }\n return [...new Set(props.filter.value.split(\"\"))].sort().map((v) => ({ value: v, label: v }));\n});\n\nconst stringMatchesError = computed(() => {\n if (props.filter.type !== \"patternMatchesRegularExpression\") {\n return false;\n }\n try {\n new RegExp(props.filter.value);\n return false;\n } catch {\n return true;\n }\n});\n</script>\n<template>\n <div :class=\"$style.filterWrapper\">\n <!-- top element - column selector / column label - for all filter types-->\n <div\n v-if=\"enableDnd\"\n :class=\"[$style.top, $style.columnChip, { [$style.error]: currentError }]\"\n >\n <div :class=\"[$style.typeIcon, { [$style.error]: currentError }]\">\n <PlIcon16 v-if=\"currentError\" name=\"warning\" />\n <PlIcon16\n v-else\n :name=\"\n currentType === 'String' || currentType === undefined\n ? 'cell-type-txt'\n : 'cell-type-num'\n \"\n />\n </div>\n <div :class=\"$style.titleWrapper\" :title=\"currentOption?.label ?? ''\">\n <div :class=\"$style.title\">\n {{\n inconsistentSourceSelected\n ? \"Inconsistent value\"\n : (currentOption?.label ?? props.filter.column)\n }}\n </div>\n </div>\n <div :class=\"$style.closeIcon\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n <div v-else :class=\"$style.top\">\n <PlDropdown\n :model-value=\"columnAsSourceAndFixedAxes.source\"\n :errorStatus=\"currentError\"\n :options=\"sourceOptions\"\n :style=\"{ width: '100%' }\"\n group-position=\"top-left\"\n @update:model-value=\"changeSourceId\"\n />\n <div :class=\"$style.closeButton\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n\n <div v-if=\"currentOption?.axesToBeFixed?.length\" :class=\"$style.fixedAxesBlock\">\n <template v-for=\"value in currentOption?.axesToBeFixed\" :key=\"value.idx\">\n <PlAutocomplete\n :model-value=\"columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]\"\n :label=\"value.label\"\n :options-search=\"\n (str, type) =>\n getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str, value.idx)\n \"\n :disabled=\"inconsistentSourceSelected\"\n :clearable=\"true\"\n @update:model-value=\"(v) => updateAxisFilterValue(value.idx, v)\"\n />\n </template>\n </div>\n\n <!-- middle - filter type selector - for all filter types -->\n <div\n :class=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA'\n ? $style.bottom\n : $style.middle\n \"\n >\n <PlDropdown\n :model-value=\"props.filter.type\"\n :options=\"filterTypesOptions\"\n :group-position=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA' ? 'bottom' : 'middle'\n \"\n @update:model-value=\"changeFilterType\"\n />\n </div>\n\n <!-- middle - for fuzzy contains filter -->\n <template v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\">\n <div :class=\"$style.middle\">\n <PlTextField\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"middle\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n </div>\n <div :class=\"$style.innerSection\">\n <Slider\n :model-value=\"props.filter.maxEdits\"\n :max=\"5\"\n breakpoints\n label=\"Maximum number of substitutions and indels\"\n @update:model-value=\"(v) => updateFilterProp('maxEdits', v)\"\n />\n <PlToggleSwitch\n :model-value=\"props.filter.substitutionsOnly\"\n label=\"Substitutions only\"\n @update:model-value=\"(v) => updateFilterProp('substitutionsOnly', v)\"\n />\n </div>\n </template>\n\n <!-- bottom element - individual settings for every filter type -->\n <div :class=\"$style.bottom\">\n <PlAutocomplete\n v-if=\"props.filter.type === 'patternEquals' || props.filter.type === 'patternNotEquals'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :clearable=\"true\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlAutocompleteMulti\n v-if=\"props.filter.type === 'inSet' || props.filter.type === 'notInSet'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getMultiSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :disabled=\"inconsistentSourceSelected\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlNumberField\n v-if=\"isNumericFilter(props.filter)\"\n :model-value=\"props.filter.x\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('x', v)\"\n />\n <PlNumberField\n v-if=\"isPositionFilter(props.filter)\"\n :model-value=\"props.filter.n\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('n', v)\"\n />\n <PlTextField\n v-if=\"\n props.filter.type === 'patternContainSubsequence' ||\n props.filter.type === 'patternNotContainSubsequence'\n \"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlTextField\n v-if=\"props.filter.type === 'patternMatchesRegularExpression'\"\n :model-value=\"props.filter.value\"\n :error=\"stringMatchesError ? 'Regular expression is not valid' : undefined\"\n placeholder=\"Regular expression\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlDropdown\n v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\"\n :model-value=\"props.filter.wildcard\"\n clearable\n placeholder=\"Wildcard value\"\n :options=\"wildcardOptions\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('wildcard', v)\"\n />\n </div>\n </div>\n <OperandButton :active=\"operand\" :disabled=\"isLast\" @select=\"onChangeOperand\" />\n</template>\n\n<style module>\n.filterWrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n cursor: default;\n}\n\n.typeIcon {\n display: inline-flex;\n margin-right: 8px;\n}\n\n.typeIcon.error {\n --icon-color: var(--txt-error);\n}\n\n.closeIcon {\n display: inline-flex;\n margin-left: 12px;\n cursor: pointer;\n}\n\n.titleWrapper {\n flex-grow: 1;\n overflow: hidden;\n}\n.title {\n overflow: hidden;\n color: var(--txt-01);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.columnChip {\n width: 100%;\n display: flex;\n padding: 10px 12px;\n align-items: center;\n border-radius: 6px;\n border: 1px solid var(--txt-01);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &.error {\n border-color: var(--txt-error);\n }\n}\n\n.innerSection {\n border: 1px solid var(--txt-01);\n border-top: none;\n padding: 16px 12px;\n}\n\n.closeButton {\n border: 1px solid var(--txt-01);\n border-top-right-radius: 6px;\n border-left: none;\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.top {\n position: relative;\n display: flex;\n width: 100%;\n z-index: 1;\n background: #fff;\n}\n\n.fixedAxesBlock {\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 8px;\n gap: 12px;\n border-left: 1px solid var(--txt-01);\n border-right: 1px solid var(--txt-01);\n}\n\n.fixedAxesBlock > * {\n background: #fff;\n}\n\n.middle,\n.bottom {\n position: relative;\n margin-top: -1px;\n background: #fff;\n}\n</style>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterEditor.vue_vue_type_style_index_0_lang.module.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport {\n PlAutocomplete,\n PlAutocompleteMulti,\n PlDropdown,\n PlIcon16,\n PlNumberField,\n PlTextField,\n PlToggleSwitch,\n Slider,\n} from \"@milaboratories/uikit\";\nimport type {\n AnchoredPColumnId,\n AxisFilterByIdx,\n AxisFilterValue,\n SUniversalPColumnId,\n} from \"@platforma-sdk/model\";\nimport {\n isFilteredPColumn,\n parseColumnId,\n stringifyColumnId,\n type ListOptionBase,\n} from \"@platforma-sdk/model\";\nimport { computed } from \"vue\";\nimport type { SUPPORTED_FILTER_TYPES } from \"./constants\";\nimport { DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from \"./constants\";\nimport OperandButton from \"./OperandButton.vue\";\nimport type { EditableFilter, Operand, PlAdvancedFilterColumnId, SourceOptionInfo } from \"./types\";\nimport { getFilterInfo, getNormalizedSpec, isNumericFilter, isPositionFilter } from \"./utils\";\nimport { Entries } from \"@milaboratories/helpers\";\n\nconst props = defineProps<{\n filter: EditableFilter;\n isLast: boolean;\n operand: Operand;\n enableDnd: boolean;\n columnOptions: SourceOptionInfo[];\n supportedFilters: typeof SUPPORTED_FILTER_TYPES;\n getSuggestOptions: (params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchType: \"value\" | \"label\";\n searchStr: string;\n }) => ListOptionBase<string | number>[] | Promise<ListOptionBase<string | number>[]>;\n onDelete: (columnId: PlAdvancedFilterColumnId) => void;\n onUpdateFilter: (filter: EditableFilter) => void;\n onChangeOperand: (op: Operand) => void;\n}>();\n\ntype AllKeys<U> = U extends unknown ? keyof U : never;\ntype ValueOf<U, K extends string> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;\n\nfunction updateFilterProp<K extends AllKeys<EditableFilter> & string>(\n key: K,\n v: ValueOf<EditableFilter, K>,\n) {\n props.onUpdateFilter({ ...props.filter, [key]: v } as EditableFilter);\n}\n\nasync function getSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string,\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n return props.getSuggestOptions({\n columnId: id,\n axisIdx,\n searchType: type,\n searchStr: str,\n }) as Promise<ListOptionBase<string>[]>;\n}\n\nasync function getMultiSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string | string[],\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n if (type === \"label\" && typeof str === \"string\") {\n return getSuggestOptionsFn(id, type, str, axisIdx);\n }\n if (type === \"value\" && Array.isArray(str)) {\n const results = await Promise.all(str.map((s) => getSuggestOptionsFn(id, type, s, axisIdx)));\n return results.map((x) => x[0]);\n }\n throw new Error(\"Invalid arguments combination\");\n}\n\nfunction changeFilterType(newType: EditableFilter[\"type\"]) {\n const defaultFilter = DEFAULT_FILTERS[newType];\n\n props.onUpdateFilter(\n (Object.entries(defaultFilter) as Entries<EditableFilter>).reduce(\n (res, [key, val]) => {\n res[key] = props.filter[key] ?? val;\n return res;\n },\n { ...props.filter, type: newType } as Record<\n keyof EditableFilter,\n EditableFilter[keyof EditableFilter]\n >,\n ) as EditableFilter,\n );\n}\n\nfunction changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {\n if (!newSourceId) {\n return;\n }\n const newSourceInfo = props.columnOptions.find((v) => v.id === getSourceId(newSourceId));\n if (!newSourceInfo) {\n return;\n }\n const filterInfo = getFilterInfo(props.filter.type);\n const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);\n if (filterInfo.supportedFor(newSourceSpec)) {\n props.onUpdateFilter({ ...props.filter, column: newSourceId });\n } else {\n props.onUpdateFilter({\n ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],\n column: newSourceId,\n });\n }\n}\n\nconst inconsistentSourceSelected = computed(() => {\n const selectedOption = props.columnOptions.find(\n (op) => op.id === getSourceId(props.filter.column),\n );\n return selectedOption === undefined;\n});\nconst sourceOptions = computed(() => {\n const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));\n if (inconsistentSourceSelected.value) {\n options.unshift({ value: props.filter.column, label: \"Inconsistent value\" });\n }\n return options;\n});\n\nfunction getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return stringifyColumnId(parsedColumnId.source);\n } else {\n return column;\n }\n } catch {\n return column;\n }\n}\n\n// similar to FilteredPColumnId but source is stringified and axis filters can be undefined\ntype ColumnAsSourceAndFixedAxes = {\n source: PlAdvancedFilterColumnId;\n axisFiltersByIndex: Record<number, AxisFilterValue | undefined>;\n};\nfunction getColumnAsSourceAndFixedAxes(\n column: PlAdvancedFilterColumnId,\n): ColumnAsSourceAndFixedAxes {\n const sourceId = getSourceId(column);\n const option = props.columnOptions.find((op) => op.id === sourceId);\n const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce(\n (res, item) => {\n res[item.idx] = undefined;\n return res;\n },\n {} as Record<number, AxisFilterValue | undefined>,\n );\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return {\n source: sourceId,\n axisFiltersByIndex: parsedColumnId.axisFilters.reduce((res, item) => {\n res[item[0]] = item[1];\n return res;\n }, axesToBeFixed),\n };\n }\n } catch {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n}\n\nfunction stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {\n if (Object.keys(value.axisFiltersByIndex).length === 0) {\n return value.source;\n }\n return stringifyColumnId({\n source: parseColumnId(value.source as SUniversalPColumnId) as AnchoredPColumnId,\n axisFilters: Object.entries(value.axisFiltersByIndex).map(\n ([idx, value]) => [Number(idx), value] as AxisFilterByIdx,\n ),\n });\n}\n\nconst columnAsSourceAndFixedAxes = computed({\n get: () => {\n return getColumnAsSourceAndFixedAxes(props.filter.column);\n },\n set: (value) => {\n props.onUpdateFilter({ ...props.filter, column: stringifyColumn(value) });\n },\n});\nfunction updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {\n columnAsSourceAndFixedAxes.value = {\n ...columnAsSourceAndFixedAxes.value,\n axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value },\n };\n}\n\nconst currentOption = computed(() =>\n props.columnOptions.find((op) => op.id === columnAsSourceAndFixedAxes.value.source),\n);\nconst currentSpec = computed(() =>\n currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null,\n);\nconst currentType = computed(() => currentSpec.value?.valueType);\nconst currentError = computed(\n () => Boolean(currentOption.value?.error) || inconsistentSourceSelected.value,\n);\n\nconst filterTypesOptions = computed(() =>\n props.supportedFilters\n .filter(\n (v) =>\n props.filter.type === v ||\n (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),\n )\n .map((v) => ({ value: v, label: getFilterInfo(v).label })),\n);\n\nconst wildcardOptions = computed(() => {\n if (props.filter.type !== \"patternFuzzyContainSubsequence\") {\n return [];\n }\n if (currentOption.value?.alphabet === \"nucleotide\") {\n return [{ label: \"N\", value: \"N\" }];\n }\n if (currentOption.value?.alphabet === \"aminoacid\") {\n return [{ label: \"X\", value: \"X\" }];\n }\n return [...new Set(props.filter.value.split(\"\"))].sort().map((v) => ({ value: v, label: v }));\n});\n\nconst stringMatchesError = computed(() => {\n if (props.filter.type !== \"patternMatchesRegularExpression\") {\n return false;\n }\n try {\n new RegExp(props.filter.value);\n return false;\n } catch {\n return true;\n }\n});\n</script>\n<template>\n <div :class=\"$style.filterWrapper\">\n <!-- top element - column selector / column label - for all filter types-->\n <div\n v-if=\"enableDnd\"\n :class=\"[$style.top, $style.columnChip, { [$style.error]: currentError }]\"\n >\n <div :class=\"[$style.typeIcon, { [$style.error]: currentError }]\">\n <PlIcon16 v-if=\"currentError\" name=\"warning\" />\n <PlIcon16\n v-else\n :name=\"\n currentType === 'String' || currentType === undefined\n ? 'cell-type-txt'\n : 'cell-type-num'\n \"\n />\n </div>\n <div :class=\"$style.titleWrapper\" :title=\"currentOption?.label ?? ''\">\n <div :class=\"$style.title\">\n {{\n inconsistentSourceSelected\n ? \"Inconsistent value\"\n : (currentOption?.label ?? props.filter.column)\n }}\n </div>\n </div>\n <div :class=\"$style.closeIcon\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n <div v-else :class=\"$style.top\">\n <PlDropdown\n :model-value=\"columnAsSourceAndFixedAxes.source\"\n :errorStatus=\"currentError\"\n :options=\"sourceOptions\"\n :style=\"{ width: '100%' }\"\n group-position=\"top-left\"\n @update:model-value=\"changeSourceId\"\n />\n <div :class=\"$style.closeButton\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n\n <div v-if=\"currentOption?.axesToBeFixed?.length\" :class=\"$style.fixedAxesBlock\">\n <template v-for=\"value in currentOption?.axesToBeFixed\" :key=\"value.idx\">\n <PlAutocomplete\n :model-value=\"columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]\"\n :label=\"value.label\"\n :options-search=\"\n (str, type) =>\n getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str, value.idx)\n \"\n :disabled=\"inconsistentSourceSelected\"\n :clearable=\"true\"\n @update:model-value=\"(v) => updateAxisFilterValue(value.idx, v)\"\n />\n </template>\n </div>\n\n <!-- middle - filter type selector - for all filter types -->\n <div\n :class=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA'\n ? $style.bottom\n : $style.middle\n \"\n >\n <PlDropdown\n :model-value=\"props.filter.type\"\n :options=\"filterTypesOptions\"\n :group-position=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA' ? 'bottom' : 'middle'\n \"\n @update:model-value=\"(v) => changeFilterType(v!)\"\n />\n </div>\n\n <!-- middle - for fuzzy contains filter -->\n <template v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\">\n <div :class=\"$style.middle\">\n <PlTextField\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"middle\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n </div>\n <div :class=\"$style.innerSection\">\n <Slider\n :model-value=\"props.filter.maxEdits\"\n :max=\"5\"\n breakpoints\n label=\"Maximum number of substitutions and indels\"\n @update:model-value=\"(v) => updateFilterProp('maxEdits', v)\"\n />\n <PlToggleSwitch\n :model-value=\"props.filter.substitutionsOnly\"\n label=\"Substitutions only\"\n @update:model-value=\"(v) => updateFilterProp('substitutionsOnly', v)\"\n />\n </div>\n </template>\n\n <!-- bottom element - individual settings for every filter type -->\n <div :class=\"$style.bottom\">\n <PlAutocomplete\n v-if=\"props.filter.type === 'patternEquals' || props.filter.type === 'patternNotEquals'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :clearable=\"true\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlAutocompleteMulti\n v-if=\"props.filter.type === 'inSet' || props.filter.type === 'notInSet'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getMultiSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :disabled=\"inconsistentSourceSelected\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlNumberField\n v-if=\"isNumericFilter(props.filter)\"\n :model-value=\"props.filter.x\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('x', v)\"\n />\n <PlNumberField\n v-if=\"isPositionFilter(props.filter)\"\n :model-value=\"props.filter.n\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('n', v)\"\n />\n <PlTextField\n v-if=\"\n props.filter.type === 'patternContainSubsequence' ||\n props.filter.type === 'patternNotContainSubsequence'\n \"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlTextField\n v-if=\"props.filter.type === 'patternMatchesRegularExpression'\"\n :model-value=\"props.filter.value\"\n :error=\"stringMatchesError ? 'Regular expression is not valid' : undefined\"\n placeholder=\"Regular expression\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlDropdown\n v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\"\n :model-value=\"props.filter.wildcard\"\n clearable\n placeholder=\"Wildcard value\"\n :options=\"wildcardOptions\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('wildcard', v)\"\n />\n </div>\n </div>\n <OperandButton :active=\"operand\" :disabled=\"isLast\" @select=\"onChangeOperand\" />\n</template>\n\n<style module>\n.filterWrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n cursor: default;\n}\n\n.typeIcon {\n display: inline-flex;\n margin-right: 8px;\n}\n\n.typeIcon.error {\n --icon-color: var(--txt-error);\n}\n\n.closeIcon {\n display: inline-flex;\n margin-left: 12px;\n cursor: pointer;\n}\n\n.titleWrapper {\n flex-grow: 1;\n overflow: hidden;\n}\n.title {\n overflow: hidden;\n color: var(--txt-01);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.columnChip {\n width: 100%;\n display: flex;\n padding: 10px 12px;\n align-items: center;\n border-radius: 6px;\n border: 1px solid var(--txt-01);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &.error {\n border-color: var(--txt-error);\n }\n}\n\n.innerSection {\n border: 1px solid var(--txt-01);\n border-top: none;\n padding: 16px 12px;\n}\n\n.closeButton {\n border: 1px solid var(--txt-01);\n border-top-right-radius: 6px;\n border-left: none;\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.top {\n position: relative;\n display: flex;\n width: 100%;\n z-index: 1;\n background: #fff;\n}\n\n.fixedAxesBlock {\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 8px;\n gap: 12px;\n border-left: 1px solid var(--txt-01);\n border-right: 1px solid var(--txt-01);\n}\n\n.fixedAxesBlock > * {\n background: #fff;\n}\n\n.middle,\n.bottom {\n position: relative;\n margin-top: -1px;\n background: #fff;\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"FilterEditor.vue_vue_type_style_index_0_lang.module.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport {\n PlAutocomplete,\n PlAutocompleteMulti,\n PlDropdown,\n PlIcon16,\n PlNumberField,\n PlTextField,\n PlToggleSwitch,\n Slider,\n} from \"@milaboratories/uikit\";\nimport type {\n AnchoredPColumnId,\n AxisFilterByIdx,\n AxisFilterValue,\n SUniversalPColumnId,\n} from \"@platforma-sdk/model\";\nimport {\n isFilteredPColumn,\n parseColumnId,\n stringifyColumnId,\n type ListOptionBase,\n} from \"@platforma-sdk/model\";\nimport { computed } from \"vue\";\nimport type { SUPPORTED_FILTER_TYPES } from \"./constants\";\nimport { DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from \"./constants\";\nimport OperandButton from \"./OperandButton.vue\";\nimport type { EditableFilter, Operand, PlAdvancedFilterColumnId, SourceOptionInfo } from \"./types\";\nimport {\n getFilterInfo,\n getNormalizedSpec,\n isNumericFilter,\n isPositionFilter,\n mergeFilterForTypeChange,\n} from \"./utils\";\nimport { isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n filter: EditableFilter;\n isLast: boolean;\n operand: Operand;\n enableDnd: boolean;\n columnOptions: SourceOptionInfo[];\n supportedFilters: typeof SUPPORTED_FILTER_TYPES;\n getSuggestOptions: (params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchType: \"value\" | \"label\";\n searchStr: string;\n }) => ListOptionBase<string | number>[] | Promise<ListOptionBase<string | number>[]>;\n onDelete: (columnId: PlAdvancedFilterColumnId) => void;\n onUpdateFilter: (filter: EditableFilter) => void;\n onChangeOperand: (op: Operand) => void;\n}>();\n\ntype AllKeys<U> = U extends unknown ? keyof U : never;\ntype ValueOf<U, K extends string> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;\n\nfunction updateFilterProp<K extends AllKeys<EditableFilter> & string>(\n key: K,\n v: ValueOf<EditableFilter, K>,\n) {\n props.onUpdateFilter({ ...props.filter, [key]: v } as EditableFilter);\n}\n\nasync function getSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string,\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n return props.getSuggestOptions({\n columnId: id,\n axisIdx,\n searchType: type,\n searchStr: str,\n }) as Promise<ListOptionBase<string>[]>;\n}\n\nasync function getMultiSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string | string[],\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n if (type === \"label\" && typeof str === \"string\") {\n return getSuggestOptionsFn(id, type, str, axisIdx);\n }\n if (type === \"value\" && Array.isArray(str)) {\n const results = await Promise.all(str.map((s) => getSuggestOptionsFn(id, type, s, axisIdx)));\n return results.map((x) => x[0]);\n }\n throw new Error(\"Invalid arguments combination\");\n}\n\nfunction changeFilterType(newType?: EditableFilter[\"type\"]) {\n if (isNil(newType)) return;\n props.onUpdateFilter(mergeFilterForTypeChange(props.filter, newType));\n}\n\nfunction changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {\n if (!newSourceId) {\n return;\n }\n const newSourceInfo = props.columnOptions.find((v) => v.id === getSourceId(newSourceId));\n if (!newSourceInfo) {\n return;\n }\n const filterInfo = getFilterInfo(props.filter.type);\n const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);\n if (filterInfo.supportedFor(newSourceSpec)) {\n props.onUpdateFilter({ ...props.filter, column: newSourceId });\n } else {\n props.onUpdateFilter({\n ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],\n column: newSourceId,\n });\n }\n}\n\nconst inconsistentSourceSelected = computed(() => {\n const selectedOption = props.columnOptions.find(\n (op) => op.id === getSourceId(props.filter.column),\n );\n return selectedOption === undefined;\n});\nconst sourceOptions = computed(() => {\n const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));\n if (inconsistentSourceSelected.value) {\n options.unshift({ value: props.filter.column, label: \"Inconsistent value\" });\n }\n return options;\n});\n\nfunction getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return stringifyColumnId(parsedColumnId.source);\n } else {\n return column;\n }\n } catch {\n return column;\n }\n}\n\n// similar to FilteredPColumnId but source is stringified and axis filters can be undefined\ntype ColumnAsSourceAndFixedAxes = {\n source: PlAdvancedFilterColumnId;\n axisFiltersByIndex: Record<number, AxisFilterValue | undefined>;\n};\nfunction getColumnAsSourceAndFixedAxes(\n column: PlAdvancedFilterColumnId,\n): ColumnAsSourceAndFixedAxes {\n const sourceId = getSourceId(column);\n const option = props.columnOptions.find((op) => op.id === sourceId);\n const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce(\n (res, item) => {\n res[item.idx] = undefined;\n return res;\n },\n {} as Record<number, AxisFilterValue | undefined>,\n );\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return {\n source: sourceId,\n axisFiltersByIndex: parsedColumnId.axisFilters.reduce((res, item) => {\n res[item[0]] = item[1];\n return res;\n }, axesToBeFixed),\n };\n }\n } catch {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n}\n\nfunction stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {\n if (Object.keys(value.axisFiltersByIndex).length === 0) {\n return value.source;\n }\n return stringifyColumnId({\n source: parseColumnId(value.source as SUniversalPColumnId) as AnchoredPColumnId,\n axisFilters: Object.entries(value.axisFiltersByIndex).map(\n ([idx, value]) => [Number(idx), value] as AxisFilterByIdx,\n ),\n });\n}\n\nconst columnAsSourceAndFixedAxes = computed({\n get: () => {\n return getColumnAsSourceAndFixedAxes(props.filter.column);\n },\n set: (value) => {\n props.onUpdateFilter({ ...props.filter, column: stringifyColumn(value) });\n },\n});\nfunction updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {\n columnAsSourceAndFixedAxes.value = {\n ...columnAsSourceAndFixedAxes.value,\n axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value },\n };\n}\n\nconst currentOption = computed(() =>\n props.columnOptions.find((op) => op.id === columnAsSourceAndFixedAxes.value.source),\n);\nconst currentSpec = computed(() =>\n currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null,\n);\nconst currentType = computed(() => currentSpec.value?.valueType);\nconst currentError = computed(\n () => Boolean(currentOption.value?.error) || inconsistentSourceSelected.value,\n);\n\nconst filterTypesOptions = computed(() =>\n props.supportedFilters\n .filter(\n (v) =>\n props.filter.type === v ||\n (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),\n )\n .map((v) => ({ value: v, label: getFilterInfo(v).label })),\n);\n\nconst wildcardOptions = computed(() => {\n if (props.filter.type !== \"patternFuzzyContainSubsequence\") {\n return [];\n }\n if (currentOption.value?.alphabet === \"nucleotide\") {\n return [{ label: \"N\", value: \"N\" }];\n }\n if (currentOption.value?.alphabet === \"aminoacid\") {\n return [{ label: \"X\", value: \"X\" }];\n }\n return [...new Set(props.filter.value.split(\"\"))].sort().map((v) => ({ value: v, label: v }));\n});\n\nconst stringMatchesError = computed(() => {\n if (props.filter.type !== \"patternMatchesRegularExpression\") {\n return false;\n }\n try {\n new RegExp(props.filter.value);\n return false;\n } catch {\n return true;\n }\n});\n</script>\n<template>\n <div :class=\"$style.filterWrapper\">\n <!-- top element - column selector / column label - for all filter types-->\n <div\n v-if=\"enableDnd\"\n :class=\"[$style.top, $style.columnChip, { [$style.error]: currentError }]\"\n >\n <div :class=\"[$style.typeIcon, { [$style.error]: currentError }]\">\n <PlIcon16 v-if=\"currentError\" name=\"warning\" />\n <PlIcon16\n v-else\n :name=\"\n currentType === 'String' || currentType === undefined\n ? 'cell-type-txt'\n : 'cell-type-num'\n \"\n />\n </div>\n <div :class=\"$style.titleWrapper\" :title=\"currentOption?.label ?? ''\">\n <div :class=\"$style.title\">\n {{\n inconsistentSourceSelected\n ? \"Inconsistent value\"\n : (currentOption?.label ?? props.filter.column)\n }}\n </div>\n </div>\n <div :class=\"$style.closeIcon\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n <div v-else :class=\"$style.top\">\n <PlDropdown\n :model-value=\"columnAsSourceAndFixedAxes.source\"\n :errorStatus=\"currentError\"\n :options=\"sourceOptions\"\n :style=\"{ width: '100%' }\"\n group-position=\"top-left\"\n @update:model-value=\"changeSourceId\"\n />\n <div :class=\"$style.closeButton\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n\n <div v-if=\"currentOption?.axesToBeFixed?.length\" :class=\"$style.fixedAxesBlock\">\n <template v-for=\"value in currentOption?.axesToBeFixed\" :key=\"value.idx\">\n <PlAutocomplete\n :model-value=\"columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]\"\n :label=\"value.label\"\n :options-search=\"\n (str, type) =>\n getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str, value.idx)\n \"\n :disabled=\"inconsistentSourceSelected\"\n :clearable=\"true\"\n @update:model-value=\"(v) => updateAxisFilterValue(value.idx, v)\"\n />\n </template>\n </div>\n\n <!-- middle - filter type selector - for all filter types -->\n <div\n :class=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA'\n ? $style.bottom\n : $style.middle\n \"\n >\n <PlDropdown\n :model-value=\"props.filter.type\"\n :options=\"filterTypesOptions\"\n :group-position=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA' ? 'bottom' : 'middle'\n \"\n @update:model-value=\"changeFilterType\"\n />\n </div>\n\n <!-- middle - for fuzzy contains filter -->\n <template v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\">\n <div :class=\"$style.middle\">\n <PlTextField\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"middle\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n </div>\n <div :class=\"$style.innerSection\">\n <Slider\n :model-value=\"props.filter.maxEdits\"\n :max=\"5\"\n breakpoints\n label=\"Maximum number of substitutions and indels\"\n @update:model-value=\"(v) => updateFilterProp('maxEdits', v)\"\n />\n <PlToggleSwitch\n :model-value=\"props.filter.substitutionsOnly\"\n label=\"Substitutions only\"\n @update:model-value=\"(v) => updateFilterProp('substitutionsOnly', v)\"\n />\n </div>\n </template>\n\n <!-- bottom element - individual settings for every filter type -->\n <div :class=\"$style.bottom\">\n <PlAutocomplete\n v-if=\"props.filter.type === 'patternEquals' || props.filter.type === 'patternNotEquals'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :clearable=\"true\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlAutocompleteMulti\n v-if=\"props.filter.type === 'inSet' || props.filter.type === 'notInSet'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getMultiSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :disabled=\"inconsistentSourceSelected\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlNumberField\n v-if=\"isNumericFilter(props.filter)\"\n :model-value=\"props.filter.x\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('x', v)\"\n />\n <PlNumberField\n v-if=\"isPositionFilter(props.filter)\"\n :model-value=\"props.filter.n\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('n', v)\"\n />\n <PlTextField\n v-if=\"\n props.filter.type === 'patternContainSubsequence' ||\n props.filter.type === 'patternNotContainSubsequence'\n \"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlTextField\n v-if=\"props.filter.type === 'patternMatchesRegularExpression'\"\n :model-value=\"props.filter.value\"\n :error=\"stringMatchesError ? 'Regular expression is not valid' : undefined\"\n placeholder=\"Regular expression\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlDropdown\n v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\"\n :model-value=\"props.filter.wildcard\"\n clearable\n placeholder=\"Wildcard value\"\n :options=\"wildcardOptions\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('wildcard', v)\"\n />\n </div>\n </div>\n <OperandButton :active=\"operand\" :disabled=\"isLast\" @select=\"onChangeOperand\" />\n</template>\n\n<style module>\n.filterWrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n cursor: default;\n}\n\n.typeIcon {\n display: inline-flex;\n margin-right: 8px;\n}\n\n.typeIcon.error {\n --icon-color: var(--txt-error);\n}\n\n.closeIcon {\n display: inline-flex;\n margin-left: 12px;\n cursor: pointer;\n}\n\n.titleWrapper {\n flex-grow: 1;\n overflow: hidden;\n}\n.title {\n overflow: hidden;\n color: var(--txt-01);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.columnChip {\n width: 100%;\n display: flex;\n padding: 10px 12px;\n align-items: center;\n border-radius: 6px;\n border: 1px solid var(--txt-01);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &.error {\n border-color: var(--txt-error);\n }\n}\n\n.innerSection {\n border: 1px solid var(--txt-01);\n border-top: none;\n padding: 16px 12px;\n}\n\n.closeButton {\n border: 1px solid var(--txt-01);\n border-top-right-radius: 6px;\n border-left: none;\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.top {\n position: relative;\n display: flex;\n width: 100%;\n z-index: 1;\n background: #fff;\n}\n\n.fixedAxesBlock {\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 8px;\n gap: 12px;\n border-left: 1px solid var(--txt-01);\n border-right: 1px solid var(--txt-01);\n}\n\n.fixedAxesBlock > * {\n background: #fff;\n}\n\n.middle,\n.bottom {\n position: relative;\n margin-top: -1px;\n background: #fff;\n}\n</style>\n"],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterEditor.test.d.ts","sourceRoot":"","sources":["../../../src/components/PlAdvancedFilter/FilterEditor.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterEditor.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FilterEditor.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"names":[],"mappings":"AAgiBA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAG1D,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAUnG,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,gBAAgB,EAAE,OAAO,sBAAsB,CAAC;IAChD,iBAAiB,EAAE,CAAC,MAAM,EAAE;QAC1B,QAAQ,EAAE,wBAAwB,CAAC;QACnC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC;QAC9B,SAAS,EAAE,MAAM,CAAC;KACnB,KAAK,cAAc,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IACrF,QAAQ,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACvD,cAAc,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IACjD,eAAe,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC,CAAC;;AAktBF,wBAMG"}
|