@platforma-sdk/ui-vue 1.14.0 → 1.14.2
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/CHANGELOG.md +15 -0
- package/dist/lib.js +11921 -10921
- package/dist/lib.umd.cjs +37 -37
- package/dist/src/components/PlTableFilters/PlTableAddFilter.vue.d.ts +20 -0
- package/dist/src/components/PlTableFilters/PlTableAddFilter.vue.d.ts.map +1 -0
- package/dist/src/components/PlTableFilters/PlTableFilterEntry.vue.d.ts +16 -0
- package/dist/src/components/PlTableFilters/PlTableFilterEntry.vue.d.ts.map +1 -0
- package/dist/src/components/{PlAgDataTable → PlTableFilters}/PlTableFilters.vue.d.ts +2 -1
- package/dist/src/components/PlTableFilters/PlTableFilters.vue.d.ts.map +1 -0
- package/dist/src/components/PlTableFilters/filters_logic.d.ts +18 -0
- package/dist/src/components/PlTableFilters/filters_logic.d.ts.map +1 -0
- package/dist/src/components/PlTableFilters/index.d.ts +2 -0
- package/dist/src/components/PlTableFilters/index.d.ts.map +1 -0
- package/dist/src/lib.d.ts +2 -2
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +5 -3
- package/src/components/PlAgDataTable/pl-table-filters.scss +98 -0
- package/src/components/PlAgGridColumnManager/PlAgGridColumnManager.vue +1 -1
- package/src/components/PlTableFilters/PlTableAddFilter.vue +60 -0
- package/src/components/PlTableFilters/PlTableFilterEntry.vue +61 -0
- package/src/components/PlTableFilters/PlTableFilters.vue +249 -0
- package/src/components/PlTableFilters/filters_logic.ts +347 -0
- package/src/components/PlTableFilters/index.ts +1 -0
- package/src/components/PlTableFilters/pl-table-filters.scss +98 -0
- package/src/lib.ts +3 -2
- package/dist/src/components/PlAgDataTable/PlTableFilters.vue.d.ts.map +0 -1
- package/src/components/PlAgDataTable/PlTableFilters.vue +0 -559
|
@@ -1,559 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
import type { ListOption } from '@milaboratories/uikit';
|
|
3
|
-
import { PlCheckbox, PlDropdown, PlTextField, PlToggleSwitch, Slider } from '@milaboratories/uikit';
|
|
4
|
-
import { computed, reactive, toRefs, watch } from 'vue';
|
|
5
|
-
import canonicalize from 'canonicalize';
|
|
6
|
-
import type {
|
|
7
|
-
PlTableFiltersModel,
|
|
8
|
-
PTableRecordFilter,
|
|
9
|
-
SingleValuePredicateV2,
|
|
10
|
-
PlTableFilterType,
|
|
11
|
-
PlTableFilterNumberType,
|
|
12
|
-
PlTableFilterStringType,
|
|
13
|
-
PlTableFilter,
|
|
14
|
-
PTableColumnSpec,
|
|
15
|
-
PTableColumnId,
|
|
16
|
-
} from '@platforma-sdk/model';
|
|
17
|
-
import * as lodash from 'lodash';
|
|
18
|
-
import type { PlTableFiltersDefault, PlTableFiltersRestriction } from './types';
|
|
19
|
-
|
|
20
|
-
const model = defineModel<PlTableFiltersModel>({ required: true });
|
|
21
|
-
const props = defineProps<{
|
|
22
|
-
columns: Readonly<PTableColumnSpec[]>;
|
|
23
|
-
restrictions?: Readonly<PlTableFiltersRestriction[]>;
|
|
24
|
-
defaults?: Readonly<PlTableFiltersDefault[]>;
|
|
25
|
-
}>();
|
|
26
|
-
const { columns, restrictions, defaults } = toRefs(props);
|
|
27
|
-
|
|
28
|
-
const makeColumnId = (column: PTableColumnId | PTableColumnSpec): string => canonicalize(column.id)!;
|
|
29
|
-
const columnsWithIds = computed(() => {
|
|
30
|
-
return columns.value
|
|
31
|
-
.filter((column) => {
|
|
32
|
-
const type = column.type;
|
|
33
|
-
switch (type) {
|
|
34
|
-
case 'axis':
|
|
35
|
-
return column.spec.type !== 'Bytes';
|
|
36
|
-
case 'column':
|
|
37
|
-
return column.spec.valueType !== 'Bytes';
|
|
38
|
-
default:
|
|
39
|
-
throw Error(`unsupported data type: ${type satisfies never}`);
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
.map((column) => ({
|
|
43
|
-
column,
|
|
44
|
-
id: makeColumnId(column),
|
|
45
|
-
}));
|
|
46
|
-
});
|
|
47
|
-
const restrictionsMap = computed(() => {
|
|
48
|
-
const restrictionsValue = restrictions.value ?? [];
|
|
49
|
-
const map: Record<string, PlTableFilterType[]> = {};
|
|
50
|
-
for (const { column, id } of columnsWithIds.value) {
|
|
51
|
-
const entry = lodash.find(restrictionsValue, (entry) => lodash.isEqual(entry.column.id, column.id));
|
|
52
|
-
if (entry !== undefined) {
|
|
53
|
-
map[id] = entry.allowedFilterTypes;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return map;
|
|
57
|
-
});
|
|
58
|
-
const defaultsMap = computed(() => {
|
|
59
|
-
const defaultsValue = defaults.value ?? [];
|
|
60
|
-
const map: Record<string, PlTableFilter> = {};
|
|
61
|
-
for (const { column, id } of columnsWithIds.value) {
|
|
62
|
-
const entry = lodash.find(defaultsValue, (entry) => lodash.isEqual(entry.column.id, column.id));
|
|
63
|
-
if (entry !== undefined) {
|
|
64
|
-
map[id] = entry.default;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return map;
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const makeState = (state?: Record<string, PlTableFilter>): Record<string, PlTableFilter> => {
|
|
71
|
-
if (state !== undefined) return state;
|
|
72
|
-
return defaultsMap.value;
|
|
73
|
-
};
|
|
74
|
-
const reactiveModel = reactive({ state: makeState(model.value.state) });
|
|
75
|
-
watch(
|
|
76
|
-
() => model.value,
|
|
77
|
-
(model) => {
|
|
78
|
-
if (!lodash.isEqual(reactiveModel.state, model.state)) {
|
|
79
|
-
reactiveModel.state = makeState(model.state);
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
watch(
|
|
85
|
-
() => columnsWithIds.value,
|
|
86
|
-
(columnsWithIds) => {
|
|
87
|
-
if (reactiveModel.state !== undefined && columnsWithIds.length === 0) return;
|
|
88
|
-
|
|
89
|
-
const currentState = reactiveModel.state ?? {};
|
|
90
|
-
const newState: Record<string, PlTableFilter> = {};
|
|
91
|
-
for (const { id } of columnsWithIds) {
|
|
92
|
-
if (currentState[id] !== undefined) newState[id] = currentState[id];
|
|
93
|
-
}
|
|
94
|
-
reactiveModel.state = newState;
|
|
95
|
-
},
|
|
96
|
-
{ immediate: true },
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
const getFilterLabel = (type: PlTableFilterType): string => {
|
|
100
|
-
switch (type) {
|
|
101
|
-
case 'isNotNA':
|
|
102
|
-
return 'Is not NA';
|
|
103
|
-
case 'isNA':
|
|
104
|
-
return 'Is NA';
|
|
105
|
-
case 'number_equals':
|
|
106
|
-
case 'string_equals':
|
|
107
|
-
return 'Equals';
|
|
108
|
-
case 'number_notEquals':
|
|
109
|
-
case 'string_notEquals':
|
|
110
|
-
return 'Not equals';
|
|
111
|
-
case 'number_greaterThan':
|
|
112
|
-
return 'Greater than';
|
|
113
|
-
case 'number_greaterThanOrEqualTo':
|
|
114
|
-
return 'Greater than or equal to';
|
|
115
|
-
case 'number_lessThan':
|
|
116
|
-
return 'Less than';
|
|
117
|
-
case 'number_lessThanOrEqualTo':
|
|
118
|
-
return 'Less than or equal to';
|
|
119
|
-
case 'number_between':
|
|
120
|
-
return 'Between';
|
|
121
|
-
case 'string_contains':
|
|
122
|
-
return 'Contains';
|
|
123
|
-
case 'string_doesNotContain':
|
|
124
|
-
return 'Does not contain';
|
|
125
|
-
case 'string_matches':
|
|
126
|
-
return 'Matches';
|
|
127
|
-
case 'string_doesNotMatch':
|
|
128
|
-
return 'Does not match';
|
|
129
|
-
case 'string_containsFuzzyMatch':
|
|
130
|
-
return 'Contains fuzzy match';
|
|
131
|
-
default:
|
|
132
|
-
throw Error(`unsupported filter type: ${type satisfies never}`);
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const filterTypesNumber: PlTableFilterNumberType[] = [
|
|
137
|
-
'isNotNA',
|
|
138
|
-
'isNA',
|
|
139
|
-
'number_equals',
|
|
140
|
-
'number_notEquals',
|
|
141
|
-
'number_greaterThan',
|
|
142
|
-
'number_greaterThanOrEqualTo',
|
|
143
|
-
'number_lessThan',
|
|
144
|
-
'number_lessThanOrEqualTo',
|
|
145
|
-
'number_between',
|
|
146
|
-
] as const;
|
|
147
|
-
const filterTypesString: PlTableFilterStringType[] = [
|
|
148
|
-
'isNotNA',
|
|
149
|
-
'isNA',
|
|
150
|
-
'string_equals',
|
|
151
|
-
'string_notEquals',
|
|
152
|
-
'string_contains',
|
|
153
|
-
'string_doesNotContain',
|
|
154
|
-
'string_matches',
|
|
155
|
-
'string_doesNotMatch',
|
|
156
|
-
'string_containsFuzzyMatch',
|
|
157
|
-
] as const;
|
|
158
|
-
const filterOptions = computed(() => {
|
|
159
|
-
const restrictionsMapValue = restrictionsMap.value;
|
|
160
|
-
const map: Record<string, ListOption<PlTableFilterType>[]> = {};
|
|
161
|
-
for (const { column, id } of columnsWithIds.value) {
|
|
162
|
-
const valueType = column.type === 'column' ? column.spec.valueType : column.spec.type;
|
|
163
|
-
let types: PlTableFilterType[] = valueType === 'String' ? filterTypesString : filterTypesNumber;
|
|
164
|
-
|
|
165
|
-
const restrictionsEntry = restrictionsMapValue[id];
|
|
166
|
-
if (restrictionsEntry !== undefined) {
|
|
167
|
-
types = types.filter((type) => restrictionsEntry.includes(type));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
map[id] = types.map((type) => ({
|
|
171
|
-
value: type,
|
|
172
|
-
text: getFilterLabel(type),
|
|
173
|
-
}));
|
|
174
|
-
}
|
|
175
|
-
return map;
|
|
176
|
-
});
|
|
177
|
-
const filterOptionsPresent = computed(() => {
|
|
178
|
-
return lodash.some(Object.values(filterOptions.value), (options) => options.length > 0);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const getFilterReference = (filter: PlTableFilter): undefined | number | string => {
|
|
182
|
-
const type = filter.type;
|
|
183
|
-
switch (type) {
|
|
184
|
-
case 'isNotNA':
|
|
185
|
-
case 'isNA':
|
|
186
|
-
return undefined;
|
|
187
|
-
case 'number_equals':
|
|
188
|
-
case 'number_notEquals':
|
|
189
|
-
case 'number_greaterThan':
|
|
190
|
-
case 'number_greaterThanOrEqualTo':
|
|
191
|
-
case 'number_lessThan':
|
|
192
|
-
case 'number_lessThanOrEqualTo':
|
|
193
|
-
return filter.reference;
|
|
194
|
-
case 'number_between':
|
|
195
|
-
return filter.lowerBound;
|
|
196
|
-
case 'string_equals':
|
|
197
|
-
case 'string_notEquals':
|
|
198
|
-
case 'string_contains':
|
|
199
|
-
case 'string_doesNotContain':
|
|
200
|
-
case 'string_matches':
|
|
201
|
-
case 'string_doesNotMatch':
|
|
202
|
-
case 'string_containsFuzzyMatch':
|
|
203
|
-
return filter.reference;
|
|
204
|
-
default:
|
|
205
|
-
throw Error(`unsupported filter type: ${type satisfies never}`);
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
const getFilterDefault = (type: PlTableFilterType, reference?: undefined | number | string): PlTableFilter => {
|
|
209
|
-
switch (type) {
|
|
210
|
-
case 'isNotNA':
|
|
211
|
-
case 'isNA':
|
|
212
|
-
return { type };
|
|
213
|
-
case 'number_equals':
|
|
214
|
-
case 'number_notEquals':
|
|
215
|
-
case 'number_greaterThan':
|
|
216
|
-
case 'number_greaterThanOrEqualTo':
|
|
217
|
-
case 'number_lessThan':
|
|
218
|
-
case 'number_lessThanOrEqualTo':
|
|
219
|
-
return { type, reference: typeof reference === 'number' ? reference : 0 };
|
|
220
|
-
case 'number_between':
|
|
221
|
-
return {
|
|
222
|
-
type,
|
|
223
|
-
lowerBound: typeof reference === 'number' ? reference : 0,
|
|
224
|
-
includeLowerBound: true,
|
|
225
|
-
upperBound: 100,
|
|
226
|
-
includeUpperBound: false,
|
|
227
|
-
};
|
|
228
|
-
case 'string_equals':
|
|
229
|
-
case 'string_notEquals':
|
|
230
|
-
case 'string_contains':
|
|
231
|
-
case 'string_doesNotContain':
|
|
232
|
-
case 'string_matches':
|
|
233
|
-
case 'string_doesNotMatch':
|
|
234
|
-
return { type, reference: typeof reference === 'string' ? reference : '' };
|
|
235
|
-
case 'string_containsFuzzyMatch':
|
|
236
|
-
return {
|
|
237
|
-
type,
|
|
238
|
-
reference: typeof reference === 'string' ? reference : '',
|
|
239
|
-
maxEdits: 2,
|
|
240
|
-
substitutionsOnly: false,
|
|
241
|
-
wildcard: undefined,
|
|
242
|
-
};
|
|
243
|
-
default:
|
|
244
|
-
throw Error(`unsupported filter type: ${type satisfies never}`);
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
const updateColumnFilter = (columnId: string, type: PlTableFilterType): void => {
|
|
248
|
-
const prevFilter = reactiveModel.state![columnId];
|
|
249
|
-
reactiveModel.state![columnId] = getFilterDefault(type, getFilterReference(prevFilter));
|
|
250
|
-
};
|
|
251
|
-
const resetColumnFilter = (columnId: string) => {
|
|
252
|
-
reactiveModel.state![columnId] = defaultsMap.value[columnId] ?? getFilterDefault(filterOptions.value[columnId][0].value);
|
|
253
|
-
};
|
|
254
|
-
const onFilterActiveChanged = (columnId: string, checked: boolean) => {
|
|
255
|
-
if (checked) {
|
|
256
|
-
resetColumnFilter(columnId);
|
|
257
|
-
} else {
|
|
258
|
-
delete reactiveModel.state![columnId];
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
const parseNumber = (column: PTableColumnSpec, value: string): number => {
|
|
263
|
-
const parsed = Number(value);
|
|
264
|
-
if (!Number.isFinite(parsed)) throw Error('Model value is not a number.');
|
|
265
|
-
|
|
266
|
-
const type = column.type === 'column' ? column.spec.valueType : column.spec.type;
|
|
267
|
-
if ((type === 'Int' || type === 'Long') && !Number.isInteger(parsed)) throw Error('Model value is not an integer.');
|
|
268
|
-
|
|
269
|
-
const min = column.spec.annotations?.['pl7.app/min'];
|
|
270
|
-
if (min !== undefined) {
|
|
271
|
-
const minValue = Number(min);
|
|
272
|
-
if (Number.isFinite(minValue) && parsed < Number(min)) {
|
|
273
|
-
throw Error('Model value is too low.');
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const max = column.spec.annotations?.['pl7.app/max'];
|
|
278
|
-
if (max !== undefined) {
|
|
279
|
-
const maxValue = Number(max);
|
|
280
|
-
if (Number.isFinite(maxValue) && parsed > Number(max)) {
|
|
281
|
-
throw Error('Model value is too high.');
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return parsed;
|
|
286
|
-
};
|
|
287
|
-
const parseString = (column: PTableColumnSpec, value: string): string => {
|
|
288
|
-
const alphabet = column.spec.domain?.['pl7.app/alphabet'] ?? column.spec.annotations?.['pl7.app/alphabet'];
|
|
289
|
-
if (alphabet === 'nucleotide' && !/^[AaTtGgCcNn]+$/.test(value)) throw Error('Model value is not a nucleotide.');
|
|
290
|
-
if (alphabet === 'aminoacid' && !/^[AaCcDdEeFfGgHhIiKkLlMmNnPpQqRrSsTtVvWwYyXx]+$/.test(value)) throw Error('Model value is not an aminoacid.');
|
|
291
|
-
|
|
292
|
-
return value;
|
|
293
|
-
};
|
|
294
|
-
const parseRegex = (value: string): string => {
|
|
295
|
-
try {
|
|
296
|
-
new RegExp(value);
|
|
297
|
-
return value;
|
|
298
|
-
} catch (err: unknown) {
|
|
299
|
-
if (err instanceof SyntaxError) throw Error('Model value is not a regexp.');
|
|
300
|
-
throw err;
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
const makeWildcardOptions = (column: PTableColumnSpec, reference: string) => {
|
|
304
|
-
const alphabet = column.spec.domain?.['pl7.app/alphabet'] ?? column.spec.annotations?.['pl7.app/alphabet'];
|
|
305
|
-
if (alphabet === 'nucleotide') {
|
|
306
|
-
return [
|
|
307
|
-
{
|
|
308
|
-
value: 'N',
|
|
309
|
-
text: 'N',
|
|
310
|
-
},
|
|
311
|
-
];
|
|
312
|
-
}
|
|
313
|
-
if (alphabet === 'aminoacid') {
|
|
314
|
-
return [
|
|
315
|
-
{
|
|
316
|
-
value: 'X',
|
|
317
|
-
text: 'X',
|
|
318
|
-
},
|
|
319
|
-
];
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const chars = lodash.uniq(reference);
|
|
323
|
-
chars.sort();
|
|
324
|
-
return chars.map((char) => ({
|
|
325
|
-
value: char,
|
|
326
|
-
text: char,
|
|
327
|
-
}));
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
const makePredicate = (filter: PlTableFilter): SingleValuePredicateV2 => {
|
|
331
|
-
const type = filter.type;
|
|
332
|
-
switch (type) {
|
|
333
|
-
case 'isNotNA':
|
|
334
|
-
return {
|
|
335
|
-
operator: 'Not',
|
|
336
|
-
operand: {
|
|
337
|
-
operator: 'IsNA',
|
|
338
|
-
},
|
|
339
|
-
};
|
|
340
|
-
case 'isNA':
|
|
341
|
-
return {
|
|
342
|
-
operator: 'IsNA',
|
|
343
|
-
};
|
|
344
|
-
case 'number_equals':
|
|
345
|
-
case 'string_equals':
|
|
346
|
-
return {
|
|
347
|
-
operator: 'Equal',
|
|
348
|
-
reference: filter.reference,
|
|
349
|
-
};
|
|
350
|
-
case 'number_notEquals':
|
|
351
|
-
case 'string_notEquals':
|
|
352
|
-
return {
|
|
353
|
-
operator: 'Not',
|
|
354
|
-
operand: {
|
|
355
|
-
operator: 'Equal',
|
|
356
|
-
reference: filter.reference,
|
|
357
|
-
},
|
|
358
|
-
};
|
|
359
|
-
case 'number_greaterThan':
|
|
360
|
-
return {
|
|
361
|
-
operator: 'Greater',
|
|
362
|
-
reference: filter.reference,
|
|
363
|
-
};
|
|
364
|
-
case 'number_greaterThanOrEqualTo':
|
|
365
|
-
return {
|
|
366
|
-
operator: 'GreaterOrEqual',
|
|
367
|
-
reference: filter.reference,
|
|
368
|
-
};
|
|
369
|
-
case 'number_lessThan':
|
|
370
|
-
return {
|
|
371
|
-
operator: 'Less',
|
|
372
|
-
reference: filter.reference,
|
|
373
|
-
};
|
|
374
|
-
case 'number_lessThanOrEqualTo':
|
|
375
|
-
return {
|
|
376
|
-
operator: 'LessOrEqual',
|
|
377
|
-
reference: filter.reference,
|
|
378
|
-
};
|
|
379
|
-
case 'number_between':
|
|
380
|
-
return {
|
|
381
|
-
operator: 'And',
|
|
382
|
-
operands: [
|
|
383
|
-
{
|
|
384
|
-
operator: filter.includeLowerBound ? 'GreaterOrEqual' : 'Greater',
|
|
385
|
-
reference: filter.lowerBound,
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
operator: filter.includeUpperBound ? 'LessOrEqual' : 'Less',
|
|
389
|
-
reference: filter.upperBound,
|
|
390
|
-
},
|
|
391
|
-
],
|
|
392
|
-
};
|
|
393
|
-
case 'string_contains':
|
|
394
|
-
return {
|
|
395
|
-
operator: 'StringContains',
|
|
396
|
-
substring: filter.reference,
|
|
397
|
-
};
|
|
398
|
-
case 'string_doesNotContain':
|
|
399
|
-
return {
|
|
400
|
-
operator: 'Not',
|
|
401
|
-
operand: {
|
|
402
|
-
operator: 'StringContains',
|
|
403
|
-
substring: filter.reference,
|
|
404
|
-
},
|
|
405
|
-
};
|
|
406
|
-
case 'string_matches':
|
|
407
|
-
return {
|
|
408
|
-
operator: 'Matches',
|
|
409
|
-
regex: filter.reference,
|
|
410
|
-
};
|
|
411
|
-
case 'string_doesNotMatch':
|
|
412
|
-
return {
|
|
413
|
-
operator: 'Not',
|
|
414
|
-
operand: {
|
|
415
|
-
operator: 'Matches',
|
|
416
|
-
regex: filter.reference,
|
|
417
|
-
},
|
|
418
|
-
};
|
|
419
|
-
case 'string_containsFuzzyMatch':
|
|
420
|
-
return {
|
|
421
|
-
operator: 'StringContainsFuzzy',
|
|
422
|
-
reference: filter.reference,
|
|
423
|
-
maxEdits: filter.maxEdits,
|
|
424
|
-
substitutionsOnly: filter.substitutionsOnly,
|
|
425
|
-
wildcard: filter.wildcard,
|
|
426
|
-
};
|
|
427
|
-
default:
|
|
428
|
-
throw Error(`unsupported filter type: ${type satisfies never}`);
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
const makeFilters = (state: Record<string, PlTableFilter>): PTableRecordFilter[] => {
|
|
432
|
-
return columnsWithIds.value
|
|
433
|
-
.map(({ column, id }) => {
|
|
434
|
-
if (!(id in state)) return undefined;
|
|
435
|
-
|
|
436
|
-
const predicate = makePredicate(state[id]);
|
|
437
|
-
const { spec, ...columnId } = column;
|
|
438
|
-
const _ = spec;
|
|
439
|
-
|
|
440
|
-
return {
|
|
441
|
-
type: 'bySingleColumnV2',
|
|
442
|
-
column: columnId,
|
|
443
|
-
predicate,
|
|
444
|
-
} satisfies PTableRecordFilter;
|
|
445
|
-
})
|
|
446
|
-
.filter((entry) => entry !== undefined);
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
watch(
|
|
450
|
-
() => reactiveModel,
|
|
451
|
-
(reactiveModel) => {
|
|
452
|
-
if (!lodash.isEqual(reactiveModel.state, model.value.state)) {
|
|
453
|
-
model.value = {
|
|
454
|
-
state: lodash.cloneDeep(reactiveModel.state),
|
|
455
|
-
filters: makeFilters(reactiveModel.state),
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
},
|
|
459
|
-
{
|
|
460
|
-
immediate: true,
|
|
461
|
-
deep: true,
|
|
462
|
-
},
|
|
463
|
-
);
|
|
464
|
-
</script>
|
|
465
|
-
|
|
466
|
-
<template>
|
|
467
|
-
<div v-for="({ column, id }, i) in columnsWithIds" :key="id">
|
|
468
|
-
<form v-if="filterOptions[id].length > 0" class="d-flex gap-10 flex-column">
|
|
469
|
-
<PlCheckbox :model-value="!!reactiveModel.state[id]" @update:model-value="(checked) => onFilterActiveChanged(id, checked)">
|
|
470
|
-
{{ column.spec.annotations?.['pl7.app/label']?.trim() ?? 'Unlabeled ' + column.type + ' ' + i.toString() }}
|
|
471
|
-
</PlCheckbox>
|
|
472
|
-
<div class="controls d-flex gap-10 flex-column" :class="{ open: !!reactiveModel.state[id] }">
|
|
473
|
-
<PlDropdown
|
|
474
|
-
v-if="reactiveModel.state[id]"
|
|
475
|
-
:model-value="reactiveModel.state[id]!.type"
|
|
476
|
-
:options="filterOptions[id]"
|
|
477
|
-
label="Predicate"
|
|
478
|
-
@update:model-value="(type) => updateColumnFilter(id, type!)"
|
|
479
|
-
/>
|
|
480
|
-
<template
|
|
481
|
-
v-if="
|
|
482
|
-
reactiveModel.state[id]?.type === 'number_equals' ||
|
|
483
|
-
reactiveModel.state[id]?.type === 'number_notEquals' ||
|
|
484
|
-
reactiveModel.state[id]?.type === 'number_lessThan' ||
|
|
485
|
-
reactiveModel.state[id]?.type === 'number_lessThanOrEqualTo' ||
|
|
486
|
-
reactiveModel.state[id]?.type === 'number_greaterThan' ||
|
|
487
|
-
reactiveModel.state[id]?.type === 'number_greaterThanOrEqualTo'
|
|
488
|
-
"
|
|
489
|
-
>
|
|
490
|
-
<PlTextField
|
|
491
|
-
v-model="reactiveModel.state[id].reference"
|
|
492
|
-
:parse="(value: string): number => parseNumber(column, value)"
|
|
493
|
-
label="Reference value"
|
|
494
|
-
/>
|
|
495
|
-
</template>
|
|
496
|
-
<template v-if="reactiveModel.state[id]?.type === 'number_between'">
|
|
497
|
-
<PlTextField
|
|
498
|
-
v-model="reactiveModel.state[id].lowerBound"
|
|
499
|
-
:parse="(value: string): number => parseNumber(column, value)"
|
|
500
|
-
label="Lower bound"
|
|
501
|
-
/>
|
|
502
|
-
<PlToggleSwitch v-model="reactiveModel.state[id].includeLowerBound" label="Include lower bound" />
|
|
503
|
-
<PlTextField
|
|
504
|
-
v-model="reactiveModel.state[id].upperBound"
|
|
505
|
-
:parse="(value: string): number => parseNumber(column, value)"
|
|
506
|
-
label="Upper bound"
|
|
507
|
-
/>
|
|
508
|
-
<PlToggleSwitch v-model="reactiveModel.state[id].includeUpperBound" label="Include upper bound" />
|
|
509
|
-
</template>
|
|
510
|
-
<template
|
|
511
|
-
v-if="
|
|
512
|
-
reactiveModel.state[id]?.type === 'string_equals' ||
|
|
513
|
-
reactiveModel.state[id]?.type === 'string_notEquals' ||
|
|
514
|
-
reactiveModel.state[id]?.type === 'string_contains' ||
|
|
515
|
-
reactiveModel.state[id]?.type === 'string_doesNotContain'
|
|
516
|
-
"
|
|
517
|
-
>
|
|
518
|
-
<PlTextField
|
|
519
|
-
v-model="reactiveModel.state[id].reference"
|
|
520
|
-
:parse="(value: string): string => parseString(column, value)"
|
|
521
|
-
label="Reference value"
|
|
522
|
-
/>
|
|
523
|
-
</template>
|
|
524
|
-
<template v-if="reactiveModel.state[id]?.type === 'string_matches' || reactiveModel.state[id]?.type === 'string_doesNotMatch'">
|
|
525
|
-
<PlTextField v-model="reactiveModel.state[id].reference" :parse="parseRegex" label="Reference value" />
|
|
526
|
-
</template>
|
|
527
|
-
<template v-if="reactiveModel.state[id]?.type === 'string_containsFuzzyMatch'">
|
|
528
|
-
<PlTextField
|
|
529
|
-
v-model="reactiveModel.state[id].reference"
|
|
530
|
-
:parse="(value: string): string => parseString(column, value)"
|
|
531
|
-
label="Reference value"
|
|
532
|
-
/>
|
|
533
|
-
<Slider v-model="reactiveModel.state[id].maxEdits" :max="5" breakpoints label="Maximum nuber of substitutions and indels" />
|
|
534
|
-
<PlToggleSwitch v-model="reactiveModel.state[id].substitutionsOnly" label="Substitutions only" />
|
|
535
|
-
<PlDropdown
|
|
536
|
-
v-model="reactiveModel.state[id].wildcard"
|
|
537
|
-
:options="makeWildcardOptions(column, reactiveModel.state[id].reference)"
|
|
538
|
-
clearable
|
|
539
|
-
label="Wildcard symbol"
|
|
540
|
-
/>
|
|
541
|
-
</template>
|
|
542
|
-
</div>
|
|
543
|
-
</form>
|
|
544
|
-
</div>
|
|
545
|
-
<div v-if="!filterOptionsPresent">No filters applicable</div>
|
|
546
|
-
</template>
|
|
547
|
-
|
|
548
|
-
<style lang="css" scoped>
|
|
549
|
-
.controls {
|
|
550
|
-
max-height: 0;
|
|
551
|
-
transition: max-height 0.15s ease-out;
|
|
552
|
-
overflow: hidden;
|
|
553
|
-
}
|
|
554
|
-
.controls.open {
|
|
555
|
-
max-height: 500px;
|
|
556
|
-
transition: max-height 0.25s ease-in;
|
|
557
|
-
overflow: visible;
|
|
558
|
-
}
|
|
559
|
-
</style>
|