@platforma-sdk/ui-vue 1.45.34 → 1.45.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.turbo/turbo-build.log +31 -6
  2. package/.turbo/turbo-type-check.log +1 -1
  3. package/CHANGELOG.md +10 -0
  4. package/dist/components/PlAdvancedFilter/OperandButton.vue.d.ts +8 -0
  5. package/dist/components/PlAdvancedFilter/OperandButton.vue.js +10 -0
  6. package/dist/components/PlAdvancedFilter/OperandButton.vue.js.map +1 -0
  7. package/dist/components/PlAdvancedFilter/OperandButton.vue2.js +25 -0
  8. package/dist/components/PlAdvancedFilter/OperandButton.vue2.js.map +1 -0
  9. package/dist/components/PlAdvancedFilter/OperandButton.vue3.js +13 -0
  10. package/dist/components/PlAdvancedFilter/OperandButton.vue3.js.map +1 -0
  11. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.d.ts +39 -0
  12. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.js +10 -0
  13. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.js.map +1 -0
  14. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue2.js +199 -0
  15. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue2.js.map +1 -0
  16. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue3.js +17 -0
  17. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue3.js.map +1 -0
  18. package/dist/components/PlAdvancedFilter/SingleFilter.vue.d.ts +37 -0
  19. package/dist/components/PlAdvancedFilter/SingleFilter.vue.js +10 -0
  20. package/dist/components/PlAdvancedFilter/SingleFilter.vue.js.map +1 -0
  21. package/dist/components/PlAdvancedFilter/SingleFilter.vue2.js +306 -0
  22. package/dist/components/PlAdvancedFilter/SingleFilter.vue2.js.map +1 -0
  23. package/dist/components/PlAdvancedFilter/SingleFilter.vue3.js +35 -0
  24. package/dist/components/PlAdvancedFilter/SingleFilter.vue3.js.map +1 -0
  25. package/dist/components/PlAdvancedFilter/constants.d.ts +4 -0
  26. package/dist/components/PlAdvancedFilter/constants.js +41 -0
  27. package/dist/components/PlAdvancedFilter/constants.js.map +1 -0
  28. package/dist/components/PlAdvancedFilter/index.d.ts +1 -0
  29. package/dist/components/PlAdvancedFilter/types.d.ts +57 -0
  30. package/dist/components/PlAdvancedFilter/types.js +8 -0
  31. package/dist/components/PlAdvancedFilter/types.js.map +1 -0
  32. package/dist/components/PlAdvancedFilter/utils.js +150 -0
  33. package/dist/components/PlAdvancedFilter/utils.js.map +1 -0
  34. package/dist/index.js +33 -31
  35. package/dist/index.js.map +1 -1
  36. package/dist/lib.d.ts +1 -0
  37. package/package.json +7 -7
  38. package/src/components/PlAdvancedFilter/OperandButton.vue +53 -0
  39. package/src/components/PlAdvancedFilter/PlAdvancedFilter.vue +209 -0
  40. package/src/components/PlAdvancedFilter/SingleFilter.vue +425 -0
  41. package/src/components/PlAdvancedFilter/constants.ts +42 -0
  42. package/src/components/PlAdvancedFilter/index.ts +1 -0
  43. package/src/components/PlAdvancedFilter/types.ts +77 -0
  44. package/src/components/PlAdvancedFilter/utils.ts +215 -0
  45. package/src/lib.ts +2 -0
@@ -0,0 +1,425 @@
1
+ <script lang="ts" setup>
2
+ import type { PlAdvancedFilterColumnId, Filter, Operand, SourceOptionInfo } from './types';
3
+ import { PlIcon16, PlDropdown, PlAutocomplete, PlAutocompleteMulti, PlTextField, PlNumberField, Slider, PlToggleSwitch } from '@milaboratories/uikit';
4
+ import { computed } from 'vue';
5
+ import { SUPPORTED_FILTER_TYPES, DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from './constants';
6
+ import type { AnchoredPColumnId, AxisFilterByIdx, AxisFilterValue, SUniversalPColumnId } from '@platforma-sdk/model';
7
+ import { isFilteredPColumn, parseColumnId, stringifyColumnId, type ListOptionBase } from '@platforma-sdk/model';
8
+ import OperandButton from './OperandButton.vue';
9
+ import { getFilterInfo, getNormalizedSpec, isNumericFilter, isStringFilter } from './utils';
10
+
11
+ const props = defineProps<{
12
+ operand: Operand;
13
+ columnOptions: SourceOptionInfo[];
14
+ enableDnd: boolean;
15
+ isLast: boolean;
16
+ getSuggestOptions: (params: { columnId: PlAdvancedFilterColumnId; searchStr: string; axisIdx?: number }) => (Promise<ListOptionBase<string | number>[]>) |
17
+ ((params: { columnId: SUniversalPColumnId; searchStr: string; axisIdx?: number }) => ListOptionBase<string | number>[]);
18
+ getSuggestModel: (params: { columnId: PlAdvancedFilterColumnId; searchStr: string; axisIdx?: number }) => (Promise<ListOptionBase<string | number>>) |
19
+ ((params: { columnId: PlAdvancedFilterColumnId; searchStr: string; axisIdx?: number }) => ListOptionBase<string | number>);
20
+ onDelete: (columnId: PlAdvancedFilterColumnId) => void;
21
+ onChangeOperand: (op: Operand) => void;
22
+ }>();
23
+
24
+ const filter = defineModel<Filter>({ required: true });
25
+
26
+ async function getSuggestModelMultiFn(id: PlAdvancedFilterColumnId, v: string[], axisIdx?: number): Promise<ListOptionBase<string>[]> {
27
+ return Promise.all(v.map((v) => props.getSuggestModel({ columnId: id, searchStr: v, axisIdx }) as Promise<ListOptionBase<string>>));
28
+ }
29
+ async function getSuggestModelSingleFn(id: PlAdvancedFilterColumnId, v: string, axisIdx?: number): Promise<ListOptionBase<string>> {
30
+ return props.getSuggestModel({ columnId: id, searchStr: v, axisIdx }) as Promise<ListOptionBase<string>>;
31
+ }
32
+ async function getSuggestOptionsFn(id: PlAdvancedFilterColumnId, str: string, axisIdx?: number): Promise<ListOptionBase<string>[]> {
33
+ return props.getSuggestOptions({ columnId: id, searchStr: str, axisIdx }) as Promise<ListOptionBase<string>[]>;
34
+ }
35
+
36
+ function changeFilterType() {
37
+ const nextFilterInfo = getFilterInfo(filter.value.type);
38
+ if (currentSpec.value && nextFilterInfo.supportedFor(currentSpec.value) && isNumericFilter(filter.value)) {
39
+ // no extra changes, previous filter is compatible with new filter type
40
+ return;
41
+ } else if (currentSpec.value && nextFilterInfo.supportedFor(currentSpec.value) && isStringFilter(filter.value)) {
42
+ // erase extra settings for string filter types, save only value and column (for example regex)
43
+ filter.value = {
44
+ ...DEFAULT_FILTERS[filter.value.type],
45
+ value: filter.value.value,
46
+ column: filter.value.column,
47
+ } as Filter;
48
+ } else {
49
+ filter.value = {
50
+ ...DEFAULT_FILTERS[filter.value.type],
51
+ column: filter.value.column,
52
+ };
53
+ }
54
+ }
55
+
56
+ function changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {
57
+ if (!newSourceId) {
58
+ return;
59
+ }
60
+ const newSourceInfo = props.columnOptions.find((v) => v.id === getSourceId(newSourceId));
61
+ if (!newSourceInfo) {
62
+ return;
63
+ }
64
+ const filterInfo = getFilterInfo(filter.value.type);
65
+ const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);
66
+ if (filterInfo.supportedFor(newSourceSpec)) { // don't do anything except update source id
67
+ filter.value.column = newSourceId;
68
+ } else { // reset to default filter which fits to any column
69
+ filter.value = {
70
+ ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],
71
+ column: newSourceId,
72
+ };
73
+ }
74
+ }
75
+
76
+ const inconsistentSourceSelected = computed(() => {
77
+ const selectedOption = props.columnOptions.find((op) => op.id === getSourceId(filter.value.column));
78
+ return selectedOption === undefined;
79
+ });
80
+ const sourceOptions = computed(() => {
81
+ const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));
82
+ if (inconsistentSourceSelected.value) {
83
+ options.unshift({ value: filter.value.column, label: 'Inconsistent value' });
84
+ }
85
+ return options;
86
+ });
87
+
88
+ function getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {
89
+ try {
90
+ const parsedColumnId = parseColumnId(column as SUniversalPColumnId);
91
+ if (isFilteredPColumn(parsedColumnId)) {
92
+ return stringifyColumnId(parsedColumnId.source);
93
+ } else {
94
+ return column;
95
+ }
96
+ } catch {
97
+ return column;
98
+ }
99
+ }
100
+
101
+ // similar to FilteredPColumnId but source is stringified and axis filters can be undefined
102
+ type ColumnAsSourceAndFixedAxes = { source: PlAdvancedFilterColumnId; axisFiltersByIndex: Record<number, AxisFilterValue | undefined> };
103
+ function getColumnAsSourceAndFixedAxes(column: PlAdvancedFilterColumnId): ColumnAsSourceAndFixedAxes {
104
+ const sourceId = getSourceId(column);
105
+ const option = props.columnOptions.find((op) => op.id === sourceId);
106
+ const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce((res, item) => {
107
+ res[item.idx] = undefined;
108
+ return res;
109
+ }, {} as Record<number, AxisFilterValue | undefined>);
110
+ try {
111
+ const parsedColumnId = parseColumnId(column as SUniversalPColumnId);
112
+ if (isFilteredPColumn(parsedColumnId)) {
113
+ return {
114
+ source: sourceId,
115
+ axisFiltersByIndex: parsedColumnId.axisFilters.reduce((res, item) => {
116
+ res[item[0]] = item[1];
117
+ return res;
118
+ }, axesToBeFixed),
119
+ };
120
+ }
121
+ } catch {
122
+ return { source: column, axisFiltersByIndex: axesToBeFixed };
123
+ }
124
+ return { source: column, axisFiltersByIndex: axesToBeFixed };
125
+ }
126
+
127
+ function stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {
128
+ if (Object.keys(value.axisFiltersByIndex).length === 0) {
129
+ return value.source;
130
+ }
131
+ return stringifyColumnId({
132
+ source: parseColumnId(value.source as SUniversalPColumnId) as AnchoredPColumnId,
133
+ axisFilters: Object.entries(value.axisFiltersByIndex).map(([idx, value]) => [Number(idx), value] as AxisFilterByIdx),
134
+ });
135
+ }
136
+
137
+ const columnAsSourceAndFixedAxes = computed({
138
+ get: () => {
139
+ return getColumnAsSourceAndFixedAxes(filter.value.column);
140
+ },
141
+ set: (value) => {
142
+ filter.value.column = stringifyColumn(value);
143
+ },
144
+ });
145
+ function updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {
146
+ columnAsSourceAndFixedAxes.value = {
147
+ ...columnAsSourceAndFixedAxes.value,
148
+ axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value } };
149
+ }
150
+
151
+ const currentOption = computed(() => props.columnOptions.find((op) => op.id === columnAsSourceAndFixedAxes.value.source));
152
+ const currentSpec = computed(() => currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null);
153
+ const currentType = computed(() => currentSpec.value?.valueType);
154
+ const currentError = computed(() => currentOption.value?.error || inconsistentSourceSelected.value);
155
+
156
+ const filterTypesOptions = computed(() => [...SUPPORTED_FILTER_TYPES].filter((v) =>
157
+ filter.value.type === v || (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),
158
+ ).map((v) => ({ value: v, label: getFilterInfo(v).label })),
159
+ );
160
+
161
+ const wildcardOptions = computed(() => {
162
+ if (filter.value.type === 'patternFuzzyContainSubsequence') {
163
+ if (currentOption.value?.alphabet === 'nucleotide') {
164
+ return [{ label: 'N', value: 'N' }];
165
+ }
166
+ if (currentOption.value?.alphabet === 'aminoacid') {
167
+ return [{ label: 'X', value: 'X' }];
168
+ }
169
+ return [...new Set(filter.value.value.split(''))].sort().map((v) => ({ value: v, label: v }));
170
+ }
171
+ return [];
172
+ });
173
+
174
+ const stringMatchesError = computed(() => {
175
+ if (filter.value.type !== 'patternMatchesRegularExpression') {
176
+ return false;
177
+ }
178
+ try {
179
+ new RegExp(filter.value.value);
180
+ return false;
181
+ } catch {
182
+ return true;
183
+ }
184
+ });
185
+
186
+ </script>
187
+ <template>
188
+ <div :class="$style.filterWrapper">
189
+ <!-- top element - column selector / column label - for all filter types-->
190
+ <div v-if="enableDnd" :class="[$style.top, $style.columnChip, {[$style.error]: currentError}]">
191
+ <div :class="[$style.typeIcon, {[$style.error]: currentError}]">
192
+ <PlIcon16 v-if="currentError" name="warning"/>
193
+ <PlIcon16 v-else :name="currentType === 'String' || currentType === undefined ? 'cell-type-txt' : 'cell-type-num'"/>
194
+ </div>
195
+ <div :class="$style.titleWrapper" :title="currentOption?.label ?? ''">
196
+ <div :class="$style.title">
197
+ {{ inconsistentSourceSelected ? 'Inconsistent value' : currentOption?.label ?? filter.column }}
198
+ </div>
199
+ </div>
200
+ <div :class="$style.closeIcon" @click="onDelete(filter.column)">
201
+ <PlIcon16 name="close"/>
202
+ </div>
203
+ </div>
204
+ <div v-else :class="$style.top" >
205
+ <PlDropdown
206
+ v-model="columnAsSourceAndFixedAxes.source"
207
+ :errorStatus="currentError"
208
+ :options="sourceOptions"
209
+ :style="{width: '100%'}"
210
+ group-position="top-left"
211
+ @update:model-value="changeSourceId"
212
+ />
213
+ <div :class="$style.closeButton" @click="onDelete(filter.column)">
214
+ <PlIcon16 name="close"/>
215
+ </div>
216
+ </div>
217
+
218
+ <div v-if="currentOption?.axesToBeFixed?.length" :class="$style.fixedAxesBlock">
219
+ <template v-for="value in currentOption?.axesToBeFixed" :key="value.idx">
220
+ <PlAutocomplete
221
+ v-model="columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]"
222
+ :label="value.label"
223
+ :options-search="(str) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, str, value.idx)"
224
+ :model-search="(v) => getSuggestModelSingleFn(columnAsSourceAndFixedAxes.source, v as string, value.idx)"
225
+ :disabled="inconsistentSourceSelected"
226
+ :clearable="true"
227
+ @update:model-value="(v) => updateAxisFilterValue(value.idx, v)"
228
+ />
229
+ </template>
230
+ </div>
231
+
232
+ <!-- middle - filter type selector - for all filter types -->
233
+ <div :class="filter.type === 'isNA' || filter.type === 'isNotNA' ? $style.bottom : $style.middle">
234
+ <PlDropdown
235
+ v-model="filter.type"
236
+ :options="filterTypesOptions"
237
+ :group-position="filter.type === 'isNA' || filter.type === 'isNotNA' ? 'bottom' : 'middle'"
238
+ @update:model-value="changeFilterType"
239
+ />
240
+ </div>
241
+
242
+ <!-- middle - for fuzzy contains filter -->
243
+ <template v-if="filter.type === 'patternFuzzyContainSubsequence'">
244
+ <div :class="$style.middle">
245
+ <PlTextField
246
+ v-model="filter.value"
247
+ placeholder="Substring"
248
+ group-position="middle"
249
+ />
250
+ </div>
251
+ <div :class="$style.innerSection">
252
+ <Slider
253
+ v-model="filter.maxEdits"
254
+ :max="5"
255
+ breakpoints label="Maximum number of substitutions and indels"
256
+ />
257
+ <PlToggleSwitch
258
+ v-model="filter.substitutionsOnly"
259
+ label="Substitutions only"
260
+ />
261
+ </div>
262
+ </template>
263
+
264
+ <!-- bottom element - individual settings for every filter type -->
265
+ <div :class="$style.bottom">
266
+ <template v-if="filter.type === 'patternEquals' || filter.type === 'patternNotEquals'" >
267
+ <PlAutocomplete
268
+ v-model="filter.value"
269
+ :options-search="(str) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, str)"
270
+ :model-search="(v) => getSuggestModelSingleFn(columnAsSourceAndFixedAxes.source, v as string)"
271
+ :disabled="inconsistentSourceSelected"
272
+ :clearable="true"
273
+ group-position="bottom"
274
+ />
275
+ </template>
276
+ <template v-if="filter.type === 'inSet' || filter.type === 'notInSet'" >
277
+ <PlAutocompleteMulti
278
+ v-model="filter.value"
279
+ :options-search="(str) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, str)"
280
+ :model-search="(v) => getSuggestModelMultiFn(columnAsSourceAndFixedAxes.source, v as string[])"
281
+ :disabled="inconsistentSourceSelected"
282
+ group-position="bottom"
283
+ />
284
+ </template>
285
+ <PlNumberField
286
+ v-if="isNumericFilter(filter)"
287
+ v-model="filter.x"
288
+ group-position="bottom"
289
+ />
290
+ <PlTextField
291
+ v-if="filter.type === 'patternContainSubsequence' || filter.type === 'patternNotContainSubsequence'"
292
+ v-model="filter.value"
293
+ placeholder="Substring"
294
+ group-position="bottom"
295
+ />
296
+ <PlTextField
297
+ v-if="filter.type === 'patternMatchesRegularExpression'"
298
+ v-model="filter.value"
299
+ :error="stringMatchesError ? 'Regular expression is not valid' : undefined"
300
+ placeholder="Regular expression"
301
+ group-position="bottom"
302
+ />
303
+ <PlDropdown
304
+ v-if="filter.type === 'patternFuzzyContainSubsequence'"
305
+ v-model="filter.wildcard"
306
+ clearable
307
+ placeholder="Wildcard value"
308
+ :options="wildcardOptions"
309
+ group-position="bottom"
310
+ />
311
+ </div>
312
+ </div>
313
+ <OperandButton
314
+ :class="$style.buttonWrapper"
315
+ :active="operand"
316
+ :disabled="isLast"
317
+ :on-select="onChangeOperand"
318
+ />
319
+ </template>
320
+
321
+ <style module>
322
+ .filterWrapper {
323
+ position: relative;
324
+ display: flex;
325
+ flex-direction: column;
326
+ margin-bottom: 8px;
327
+ width: 100%;
328
+ cursor: default;
329
+ }
330
+
331
+ .typeIcon {
332
+ display: inline-flex;
333
+ margin-right: 8px;
334
+ }
335
+
336
+ .typeIcon.error {
337
+ --icon-color: var(--txt-error);
338
+ }
339
+
340
+ .closeIcon {
341
+ display: inline-flex;
342
+ margin-left: 12px;
343
+ cursor: pointer;
344
+ }
345
+
346
+ .titleWrapper {
347
+ flex-grow: 1;
348
+ overflow: hidden;
349
+ }
350
+ .title {
351
+ overflow: hidden;
352
+ color: var(--txt-01);
353
+ text-overflow: ellipsis;
354
+ white-space: nowrap;
355
+ font-size: 14px;
356
+ font-weight: 500;
357
+ line-height: 20px;
358
+ }
359
+
360
+ .columnChip {
361
+ width: 100%;
362
+ display: flex;
363
+ padding: 10px 12px;
364
+ align-items: center;
365
+ border-radius: 6px;
366
+ border: 1px solid var(--txt-01);
367
+ border-bottom-right-radius: 0;
368
+ border-bottom-left-radius: 0;
369
+
370
+ &.error {
371
+ border-color: var(--txt-error);
372
+ }
373
+ }
374
+
375
+ .innerSection {
376
+ border: 1px solid var(--txt-01);
377
+ border-top: none;
378
+ padding: 16px 12px;
379
+ }
380
+
381
+ .closeButton {
382
+ border: 1px solid var(--txt-01);
383
+ border-top-right-radius: 6px;
384
+ border-left: none;
385
+ width: 40px;
386
+ height: 40px;
387
+ display: flex;
388
+ justify-content: center;
389
+ align-items: center;
390
+ flex-shrink: 0;
391
+ cursor: pointer;
392
+ }
393
+
394
+ .top {
395
+ position: relative;
396
+ display: flex;
397
+ width: 100%;
398
+ z-index: 1;
399
+ background: #fff;
400
+ }
401
+
402
+ .fixedAxesBlock {
403
+ position: relative;
404
+ display: flex;
405
+ flex-direction: column;
406
+ padding: 12px 8px;
407
+ gap: 12px;
408
+ border-left: 1px solid var(--txt-01);
409
+ border-right: 1px solid var(--txt-01);
410
+ }
411
+
412
+ .fixedAxesBlock > * {
413
+ background: #fff;
414
+ }
415
+
416
+ .middle, .bottom {
417
+ position: relative;
418
+ margin-top: -1px;
419
+ background: #fff;
420
+ }
421
+
422
+ .buttonWrapper {
423
+ margin-bottom: 8px;
424
+ }
425
+ </style>
@@ -0,0 +1,42 @@
1
+ import type { PlAdvancedFilterColumnId, Filter, FilterType, SupportedFilterTypes } from './types';
2
+
3
+ export const SUPPORTED_FILTER_TYPES = new Set<SupportedFilterTypes>([
4
+ 'isNA',
5
+ 'isNotNA',
6
+ 'greaterThan',
7
+ 'greaterThanOrEqual',
8
+ 'lessThan',
9
+ 'lessThanOrEqual',
10
+ 'patternEquals',
11
+ 'patternNotEquals',
12
+ 'patternContainSubsequence',
13
+ 'patternNotContainSubsequence',
14
+ 'equal',
15
+ 'notEqual',
16
+ 'patternFuzzyContainSubsequence',
17
+ 'patternMatchesRegularExpression',
18
+ 'inSet',
19
+ 'notInSet',
20
+ ]);
21
+
22
+ export const DEFAULT_FILTER_TYPE: FilterType = 'isNA';
23
+
24
+ const emptyCommonPart = { column: '' as PlAdvancedFilterColumnId };
25
+ export const DEFAULT_FILTERS: Record<SupportedFilterTypes, Filter> = {
26
+ isNA: { type: 'isNA', ...emptyCommonPart },
27
+ isNotNA: { type: 'isNotNA', ...emptyCommonPart },
28
+ lessThan: { type: 'lessThan', x: undefined, ...emptyCommonPart },
29
+ lessThanOrEqual: { type: 'lessThanOrEqual', x: undefined, ...emptyCommonPart },
30
+ patternEquals: { type: 'patternEquals', value: undefined, ...emptyCommonPart },
31
+ patternNotEquals: { type: 'patternNotEquals', value: undefined, ...emptyCommonPart },
32
+ greaterThan: { type: 'greaterThan', x: undefined, ...emptyCommonPart },
33
+ greaterThanOrEqual: { type: 'greaterThanOrEqual', x: undefined, ...emptyCommonPart },
34
+ patternContainSubsequence: { type: 'patternContainSubsequence', value: '', ...emptyCommonPart },
35
+ patternNotContainSubsequence: { type: 'patternNotContainSubsequence', value: '', ...emptyCommonPart },
36
+ patternFuzzyContainSubsequence: { type: 'patternFuzzyContainSubsequence', maxEdits: 2, substitutionsOnly: false, wildcard: undefined, value: '', ...emptyCommonPart },
37
+ patternMatchesRegularExpression: { type: 'patternMatchesRegularExpression', value: '', ...emptyCommonPart },
38
+ equal: { type: 'equal', x: undefined, ...emptyCommonPart },
39
+ notEqual: { type: 'notEqual', x: undefined, ...emptyCommonPart },
40
+ inSet: { type: 'inSet', value: [], ...emptyCommonPart },
41
+ notInSet: { type: 'notInSet', value: [], ...emptyCommonPart },
42
+ };
@@ -0,0 +1 @@
1
+ export { default as PlAdvancedFilter } from './PlAdvancedFilter.vue';
@@ -0,0 +1,77 @@
1
+ import type { AxisSpec, FilterSpec, FilterSpecLeaf, FilterSpecType, ListOptionBase, PColumnSpec, SUniversalPColumnId } from '@platforma-sdk/model';
2
+ import { SUPPORTED_FILTER_TYPES } from './constants';
3
+ import type { CanonicalizedJson } from '@platforma-sdk/model';
4
+ import type { AxisId } from '@platforma-sdk/model';
5
+
6
+ export type PlAdvancedFilterColumnId = SUniversalPColumnId | CanonicalizedJson<AxisId>;
7
+ export type CommonFilterSpec = FilterSpec<FilterSpecLeaf<PlAdvancedFilterColumnId>, { expanded?: boolean }>;
8
+
9
+ // Not supported: topN, bottomN, lessThanColumn, lessThanColumnOrEqual
10
+ // or, and, not - in groups
11
+ export type SupportedFilterTypes = FilterSpecType &
12
+ 'isNA' | 'isNotNA' |
13
+ 'patternEquals' | 'patternNotEquals' |
14
+ 'patternContainSubsequence' | 'patternNotContainSubsequence' |
15
+ 'patternMatchesRegularExpression' |
16
+ 'patternFuzzyContainSubsequence' |
17
+ 'inSet' | 'notInSet' |
18
+ 'equal' | 'notEqual' |
19
+ 'lessThan' | 'lessThanOrEqual' |
20
+ 'greaterThan' | 'greaterThanOrEqual';
21
+
22
+ export type FilterType = SupportedFilterTypes;
23
+
24
+ export function isSupportedFilterType(type: FilterSpecType | undefined): type is SupportedFilterTypes {
25
+ if (!type) {
26
+ return false;
27
+ }
28
+ return SUPPORTED_FILTER_TYPES.has(type as SupportedFilterTypes);
29
+ }
30
+
31
+ export type Operand = 'or' | 'and';
32
+
33
+ type FilterUiBase = FilterSpecLeaf<PlAdvancedFilterColumnId> & {
34
+ type: SupportedFilterTypes;
35
+ column: PlAdvancedFilterColumnId;
36
+ };
37
+
38
+ type RequireFields<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
39
+ type OptionalFields<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
40
+
41
+ type NumericalWithOptionalX = 'lessThan' | 'lessThanOrEqual' | 'greaterThan' | 'greaterThanOrEqual' | 'equal' | 'notEqual';
42
+ type StringWithOptionalValue = 'patternEquals' | 'patternNotEquals';
43
+ type EditedTypes = SupportedFilterTypes & ('patternFuzzyContainSubsequence' | NumericalWithOptionalX | StringWithOptionalValue); // types from ui with some changed by optionality fields
44
+ export type Filter = Exclude<FilterUiBase, { type: EditedTypes }> |
45
+ RequireFields<Extract<FilterUiBase, { type: 'patternFuzzyContainSubsequence' }>, 'maxEdits' | 'substitutionsOnly'> |
46
+ OptionalFields<Extract<FilterUiBase, { type: NumericalWithOptionalX }>, 'x'> |
47
+ OptionalFields<Extract<FilterUiBase, { type: StringWithOptionalValue }>, 'value'>
48
+ ;
49
+
50
+ export type Group = {
51
+ id: string;
52
+ not: boolean;
53
+ filters: Filter[];
54
+ operand: Operand;
55
+ expanded: boolean;
56
+ };
57
+
58
+ export type PlAdvancedFilterUI = {
59
+ groups: Group[];
60
+ operand: Operand;
61
+ };
62
+
63
+ export type UniqueValuesList = ListOptionBase<string | number>[];
64
+ export type OptionInfo = { error: boolean; label: string; spec: PColumnSpec | AxisSpec };
65
+ export type FixedAxisInfo = {
66
+ idx: number;
67
+ label: string;
68
+ };
69
+
70
+ export type SourceOptionInfo = {
71
+ id: PlAdvancedFilterColumnId;
72
+ label: string;
73
+ error: boolean;
74
+ spec: PColumnSpec | AxisSpec;
75
+ axesToBeFixed?: FixedAxisInfo[];
76
+ alphabet?: 'nucleotide' | 'aminoacid' | string;
77
+ };