@platforma-sdk/ui-vue 1.10.0 → 1.10.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 +9 -0
- package/dist/lib.js +7496 -7082
- package/dist/lib.umd.cjs +34 -34
- package/dist/src/components/PlAgDataTable/PlAgDataTable.vue.d.ts +3 -1
- package/dist/src/components/PlAgDataTable/PlAgDataTable.vue.d.ts.map +1 -1
- package/dist/src/components/PlAgDataTable/PlTableFilters.vue.d.ts +17 -0
- package/dist/src/components/PlAgDataTable/PlTableFilters.vue.d.ts.map +1 -0
- package/dist/src/components/PlAgDataTable/sources/table-source.d.ts +3 -3
- package/dist/src/components/PlAgDataTable/sources/table-source.d.ts.map +1 -1
- package/dist/src/components/PlAgDataTable/types.d.ts +15 -1
- package/dist/src/components/PlAgDataTable/types.d.ts.map +1 -1
- package/dist/src/lib.d.ts +2 -1
- 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 +2 -2
- package/src/components/PlAgDataTable/PlAgDataTable.vue +49 -26
- package/src/components/PlAgDataTable/PlTableFilters.vue +471 -0
- package/src/components/PlAgDataTable/sources/table-source.ts +20 -23
- package/src/components/PlAgDataTable/types.ts +19 -0
- package/src/lib.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platforma-sdk/ui-vue",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/lib.umd.cjs",
|
|
6
6
|
"module": "dist/lib.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@ag-grid-enterprise/side-bar": "^32.3.3",
|
|
39
39
|
"@ag-grid-enterprise/column-tool-panel": "^32.3.3",
|
|
40
40
|
"@milaboratories/uikit": "^2.2.3",
|
|
41
|
-
"@platforma-sdk/model": "^1.10.
|
|
41
|
+
"@platforma-sdk/model": "^1.10.2"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@faker-js/faker": "^8.4.1",
|
|
@@ -9,6 +9,8 @@ import type {
|
|
|
9
9
|
ManagedGridOptions,
|
|
10
10
|
SortState,
|
|
11
11
|
StateUpdatedEvent,
|
|
12
|
+
ColDef,
|
|
13
|
+
ColGroupDef,
|
|
12
14
|
} from '@ag-grid-community/core';
|
|
13
15
|
import { ModuleRegistry } from '@ag-grid-community/core';
|
|
14
16
|
import { AgGridVue } from '@ag-grid-community/vue3';
|
|
@@ -18,7 +20,7 @@ import { RangeSelectionModule } from '@ag-grid-enterprise/range-selection';
|
|
|
18
20
|
import { ServerSideRowModelModule } from '@ag-grid-enterprise/server-side-row-model';
|
|
19
21
|
import { SideBarModule } from '@ag-grid-enterprise/side-bar';
|
|
20
22
|
import { PlDropdownLine } from '@milaboratories/uikit';
|
|
21
|
-
import type { AxisId, PlDataTableState, PTableRecordFilter, PTableSorting } from '@platforma-sdk/model';
|
|
23
|
+
import type { AxisId, PlDataTableState, PTableColumnSpec, PTableRecordFilter, PTableSorting } from '@platforma-sdk/model';
|
|
22
24
|
import canonicalize from 'canonicalize';
|
|
23
25
|
import * as lodash from 'lodash';
|
|
24
26
|
import { computed, ref, shallowRef, toRefs, watch } from 'vue';
|
|
@@ -26,7 +28,8 @@ import { AgGridTheme, useWatchFetch } from '../../lib';
|
|
|
26
28
|
import PlOverlayLoading from './PlAgOverlayLoading.vue';
|
|
27
29
|
import PlOverlayNoRows from './PlAgOverlayNoRows.vue';
|
|
28
30
|
import { updateXsvGridOptions } from './sources/file-source';
|
|
29
|
-
import {
|
|
31
|
+
import type { PlAgDataTableRow } from './sources/table-source';
|
|
32
|
+
import { enrichJoinWithLabelColumns, makeSheets, parseColId, updatePFrameGridOptions } from './sources/table-source';
|
|
30
33
|
import type { PlDataTableSettings, PlDataTableSheet } from './types';
|
|
31
34
|
|
|
32
35
|
ModuleRegistry.registerModules([
|
|
@@ -38,15 +41,15 @@ ModuleRegistry.registerModules([
|
|
|
38
41
|
ColumnsToolPanelModule,
|
|
39
42
|
]);
|
|
40
43
|
|
|
41
|
-
const emit = defineEmits<{
|
|
42
|
-
onRowDoubleClicked: [key: unknown[]]
|
|
43
|
-
}>()
|
|
44
|
-
|
|
45
44
|
const tableState = defineModel<PlDataTableState>({ default: { gridState: {} } });
|
|
46
45
|
const props = defineProps<{
|
|
47
46
|
settings: Readonly<PlDataTableSettings>;
|
|
48
47
|
}>();
|
|
49
48
|
const { settings } = toRefs(props);
|
|
49
|
+
const emit = defineEmits<{
|
|
50
|
+
onRowDoubleClicked: [key: unknown[]];
|
|
51
|
+
columnsChanged: [columns: PTableColumnSpec[]];
|
|
52
|
+
}>();
|
|
50
53
|
|
|
51
54
|
watch(
|
|
52
55
|
() => settings.value,
|
|
@@ -118,14 +121,15 @@ const sheets = useWatchFetch<PlDataTableSettings['sourceType'], PlDataTableSheet
|
|
|
118
121
|
function makeSorting(state?: SortState): PTableSorting[] | undefined {
|
|
119
122
|
if (settings.value.sourceType !== 'ptable' && settings.value.sourceType !== 'pframe') return undefined;
|
|
120
123
|
return (
|
|
121
|
-
state?.sortModel.map(
|
|
122
|
-
(item)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
state?.sortModel.map((item) => {
|
|
125
|
+
const { spec, ...column } = parseColId(item.colId);
|
|
126
|
+
const _ = spec;
|
|
127
|
+
return {
|
|
128
|
+
column,
|
|
129
|
+
ascending: item.sort === 'asc',
|
|
130
|
+
naAndAbsentAreLeastValues: true,
|
|
131
|
+
} as PTableSorting;
|
|
132
|
+
}) ?? []
|
|
129
133
|
);
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -169,13 +173,13 @@ function makeFilters(sheetsState: Record<string, string | number>): PTableRecord
|
|
|
169
173
|
type: 'bySingleColumn',
|
|
170
174
|
column: sheet.column
|
|
171
175
|
? {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
type: 'column',
|
|
177
|
+
id: sheet.column,
|
|
178
|
+
}
|
|
175
179
|
: {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
type: 'axis',
|
|
181
|
+
id: sheet.axis,
|
|
182
|
+
},
|
|
179
183
|
predicate: {
|
|
180
184
|
operator: 'Equal',
|
|
181
185
|
reference: sheetsState[makeSheetId(sheet.axis)],
|
|
@@ -252,8 +256,7 @@ const gridOptions = ref<GridOptions<PlAgDataTableRow>>({
|
|
|
252
256
|
event.api.autoSizeAllColumns();
|
|
253
257
|
},
|
|
254
258
|
onRowDoubleClicked: (value) => {
|
|
255
|
-
if (value.data)
|
|
256
|
-
emit("onRowDoubleClicked", value.data!.key)
|
|
259
|
+
if (value.data) emit('onRowDoubleClicked', value.data.key);
|
|
257
260
|
},
|
|
258
261
|
defaultColDef: {
|
|
259
262
|
suppressHeaderMenuButton: true,
|
|
@@ -358,6 +361,15 @@ watch(
|
|
|
358
361
|
(options, oldOptions) => {
|
|
359
362
|
if (!oldOptions) return;
|
|
360
363
|
if (options.rowModelType != oldOptions.rowModelType) ++reloadKey.value;
|
|
364
|
+
if (!lodash.isEqual(options.columnDefs, oldOptions.columnDefs) && options.columnDefs) {
|
|
365
|
+
const idColDef = (def: ColDef | ColGroupDef): def is ColDef => !('children' in def);
|
|
366
|
+
const colDefs: ColDef[] | undefined = options.columnDefs?.filter(idColDef);
|
|
367
|
+
const columns = colDefs
|
|
368
|
+
?.map((def) => def.colId)
|
|
369
|
+
.filter((colId) => colId !== undefined)
|
|
370
|
+
.map((colId) => parseColId(colId));
|
|
371
|
+
emit('columnsChanged', columns ?? []);
|
|
372
|
+
}
|
|
361
373
|
},
|
|
362
374
|
);
|
|
363
375
|
|
|
@@ -438,13 +450,24 @@ watch(
|
|
|
438
450
|
<div class="ap-ag-data-table-container">
|
|
439
451
|
<Transition name="ap-ag-data-table-sheets-transition">
|
|
440
452
|
<div v-if="sheets.value && sheets.value.length > 0" class="ap-ag-data-table-sheets">
|
|
441
|
-
<PlDropdownLine
|
|
453
|
+
<PlDropdownLine
|
|
454
|
+
v-for="(sheet, i) in sheets.value"
|
|
455
|
+
:key="i"
|
|
456
|
+
:model-value="sheetsState[makeSheetId(sheet.axis)]"
|
|
442
457
|
:options="sheet.options"
|
|
443
|
-
@update:model-value="(newValue) => onSheetChanged(makeSheetId(sheet.axis), newValue)"
|
|
458
|
+
@update:model-value="(newValue) => onSheetChanged(makeSheetId(sheet.axis), newValue)"
|
|
459
|
+
/>
|
|
444
460
|
</div>
|
|
445
461
|
</Transition>
|
|
446
|
-
<AgGridVue
|
|
447
|
-
|
|
462
|
+
<AgGridVue
|
|
463
|
+
:key="reloadKey"
|
|
464
|
+
:theme="AgGridTheme"
|
|
465
|
+
class="ap-ag-data-table-grid"
|
|
466
|
+
:grid-options="gridOptions"
|
|
467
|
+
@grid-ready="onGridReady"
|
|
468
|
+
@state-updated="onStateUpdated"
|
|
469
|
+
@grid-pre-destroyed="onGridPreDestroyed"
|
|
470
|
+
/>
|
|
448
471
|
</div>
|
|
449
472
|
</template>
|
|
450
473
|
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { ListOption } from '@milaboratories/uikit';
|
|
3
|
+
import { PlCheckbox, PlDropdown, PlNumberField, 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
|
+
SingleValuePredicate,
|
|
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
|
+
const currentState = reactiveModel.state ?? {};
|
|
88
|
+
const newState: Record<string, PlTableFilter> = {};
|
|
89
|
+
for (const { id } of columnsWithIds) {
|
|
90
|
+
if (currentState[id] !== undefined) newState[id] = currentState[id];
|
|
91
|
+
}
|
|
92
|
+
reactiveModel.state = newState;
|
|
93
|
+
},
|
|
94
|
+
{ immediate: true },
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const getFilterLabel = (type: PlTableFilterType): string => {
|
|
98
|
+
switch (type) {
|
|
99
|
+
case 'isNotNA':
|
|
100
|
+
return 'Is not NA';
|
|
101
|
+
case 'isNA':
|
|
102
|
+
return 'Is NA';
|
|
103
|
+
case 'number_equals':
|
|
104
|
+
case 'string_equals':
|
|
105
|
+
return 'Equals';
|
|
106
|
+
case 'number_notEquals':
|
|
107
|
+
case 'string_notEquals':
|
|
108
|
+
return 'Not equals';
|
|
109
|
+
case 'number_greaterThen':
|
|
110
|
+
return 'Greater then';
|
|
111
|
+
case 'number_greaterThenOrEqualTo':
|
|
112
|
+
return 'Greater then or equal to';
|
|
113
|
+
case 'number_lessThen':
|
|
114
|
+
return 'Less then';
|
|
115
|
+
case 'number_lessThenOrEqualTo':
|
|
116
|
+
return 'Less then or equal to';
|
|
117
|
+
case 'number_between':
|
|
118
|
+
return 'Between';
|
|
119
|
+
case 'string_contains':
|
|
120
|
+
return 'Contains';
|
|
121
|
+
case 'string_doesNotContain':
|
|
122
|
+
return 'Does not contain';
|
|
123
|
+
case 'string_matches':
|
|
124
|
+
return 'Matches';
|
|
125
|
+
case 'string_doesNotMatch':
|
|
126
|
+
return 'Does not match';
|
|
127
|
+
case 'string_containsFuzzyMatch':
|
|
128
|
+
return 'Contains fuzzy match';
|
|
129
|
+
default:
|
|
130
|
+
throw Error(`unsupported filter type: ${type satisfies never}`);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const filterTypesNumber: PlTableFilterNumberType[] = [
|
|
135
|
+
'isNotNA',
|
|
136
|
+
'isNA',
|
|
137
|
+
'number_equals',
|
|
138
|
+
'number_notEquals',
|
|
139
|
+
'number_greaterThen',
|
|
140
|
+
'number_greaterThenOrEqualTo',
|
|
141
|
+
'number_lessThen',
|
|
142
|
+
'number_lessThenOrEqualTo',
|
|
143
|
+
'number_between',
|
|
144
|
+
] as const;
|
|
145
|
+
const filterTypesString: PlTableFilterStringType[] = [
|
|
146
|
+
'isNotNA',
|
|
147
|
+
'isNA',
|
|
148
|
+
'string_equals',
|
|
149
|
+
// 'string_notEquals',
|
|
150
|
+
'string_contains',
|
|
151
|
+
// 'string_doesNotContain',
|
|
152
|
+
'string_matches',
|
|
153
|
+
// 'string_doesNotMatch',
|
|
154
|
+
'string_containsFuzzyMatch',
|
|
155
|
+
] as const;
|
|
156
|
+
const filterOptions = computed(() => {
|
|
157
|
+
const restrictionsMapValue = restrictionsMap.value;
|
|
158
|
+
const map: Record<string, ListOption<PlTableFilterType>[]> = {};
|
|
159
|
+
for (const { column, id } of columnsWithIds.value) {
|
|
160
|
+
const valueType = column.type === 'column' ? column.spec.valueType : column.spec.type;
|
|
161
|
+
let types: PlTableFilterType[] = valueType === 'String' ? filterTypesString : filterTypesNumber;
|
|
162
|
+
|
|
163
|
+
const restrictionsEntry = restrictionsMapValue[id];
|
|
164
|
+
if (restrictionsEntry !== undefined) {
|
|
165
|
+
types = types.filter((type) => restrictionsEntry.includes(type));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
map[id] = types.map((type) => ({
|
|
169
|
+
value: type,
|
|
170
|
+
text: getFilterLabel(type),
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
return map;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const getFilterReference = (filter: PlTableFilter): undefined | number | string => {
|
|
177
|
+
const type = filter.type;
|
|
178
|
+
switch (type) {
|
|
179
|
+
case 'isNotNA':
|
|
180
|
+
case 'isNA':
|
|
181
|
+
return undefined;
|
|
182
|
+
case 'number_equals':
|
|
183
|
+
case 'number_notEquals':
|
|
184
|
+
case 'number_greaterThen':
|
|
185
|
+
case 'number_greaterThenOrEqualTo':
|
|
186
|
+
case 'number_lessThen':
|
|
187
|
+
case 'number_lessThenOrEqualTo':
|
|
188
|
+
return filter.reference;
|
|
189
|
+
case 'number_between':
|
|
190
|
+
return filter.lowerBound;
|
|
191
|
+
case 'string_equals':
|
|
192
|
+
case 'string_notEquals':
|
|
193
|
+
case 'string_contains':
|
|
194
|
+
case 'string_doesNotContain':
|
|
195
|
+
case 'string_matches':
|
|
196
|
+
case 'string_doesNotMatch':
|
|
197
|
+
case 'string_containsFuzzyMatch':
|
|
198
|
+
return filter.reference;
|
|
199
|
+
default:
|
|
200
|
+
throw Error(`unsupported filter type: ${type satisfies never}`);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const getFilterDefault = (type: PlTableFilterType, reference?: undefined | number | string): PlTableFilter => {
|
|
204
|
+
switch (type) {
|
|
205
|
+
case 'isNotNA':
|
|
206
|
+
case 'isNA':
|
|
207
|
+
return { type };
|
|
208
|
+
case 'number_equals':
|
|
209
|
+
case 'number_notEquals':
|
|
210
|
+
case 'number_greaterThen':
|
|
211
|
+
case 'number_greaterThenOrEqualTo':
|
|
212
|
+
case 'number_lessThen':
|
|
213
|
+
case 'number_lessThenOrEqualTo':
|
|
214
|
+
return { type, reference: typeof reference === 'number' ? reference : 0 };
|
|
215
|
+
case 'number_between':
|
|
216
|
+
return {
|
|
217
|
+
type,
|
|
218
|
+
lowerBound: typeof reference === 'number' ? reference : 0,
|
|
219
|
+
includeLowerBound: true,
|
|
220
|
+
upperBound: 100,
|
|
221
|
+
includeUpperBound: false,
|
|
222
|
+
};
|
|
223
|
+
case 'string_equals':
|
|
224
|
+
case 'string_notEquals':
|
|
225
|
+
case 'string_contains':
|
|
226
|
+
case 'string_doesNotContain':
|
|
227
|
+
case 'string_matches':
|
|
228
|
+
case 'string_doesNotMatch':
|
|
229
|
+
return { type, reference: typeof reference === 'string' ? reference : '' };
|
|
230
|
+
case 'string_containsFuzzyMatch':
|
|
231
|
+
return {
|
|
232
|
+
type,
|
|
233
|
+
reference: typeof reference === 'string' ? reference : '',
|
|
234
|
+
maxEdits: 2,
|
|
235
|
+
substitutionsOnly: false,
|
|
236
|
+
wildcard: undefined,
|
|
237
|
+
};
|
|
238
|
+
default:
|
|
239
|
+
throw Error(`unsupported filter type: ${type satisfies never}`);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
const updateColumnFilter = (columnId: string, type: PlTableFilterType): void => {
|
|
243
|
+
const prevFilter = reactiveModel.state![columnId];
|
|
244
|
+
reactiveModel.state![columnId] = getFilterDefault(type, getFilterReference(prevFilter));
|
|
245
|
+
};
|
|
246
|
+
const onFilterActiveChanged = (columnId: string, checked: boolean) => {
|
|
247
|
+
if (checked) {
|
|
248
|
+
reactiveModel.state![columnId] = defaultsMap.value[columnId] ?? getFilterDefault(filterOptions.value[columnId][0].value);
|
|
249
|
+
} else {
|
|
250
|
+
delete reactiveModel.state![columnId];
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const makeWildcardOptions = (reference: string) => {
|
|
255
|
+
const chars = lodash.uniq(reference);
|
|
256
|
+
chars.sort();
|
|
257
|
+
return chars.map((char) => ({
|
|
258
|
+
value: char,
|
|
259
|
+
text: char,
|
|
260
|
+
}));
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const makePredicate = (filter: PlTableFilter): SingleValuePredicate => {
|
|
264
|
+
const type = filter.type;
|
|
265
|
+
switch (type) {
|
|
266
|
+
case 'isNotNA':
|
|
267
|
+
return {
|
|
268
|
+
operator: 'Not',
|
|
269
|
+
operand: {
|
|
270
|
+
operator: 'IsNA',
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
case 'isNA':
|
|
274
|
+
return {
|
|
275
|
+
operator: 'IsNA',
|
|
276
|
+
};
|
|
277
|
+
case 'number_equals':
|
|
278
|
+
case 'string_equals':
|
|
279
|
+
return {
|
|
280
|
+
operator: 'Equal',
|
|
281
|
+
reference: filter.reference,
|
|
282
|
+
};
|
|
283
|
+
case 'number_notEquals':
|
|
284
|
+
case 'string_notEquals':
|
|
285
|
+
return {
|
|
286
|
+
operator: 'Not',
|
|
287
|
+
operand: {
|
|
288
|
+
operator: 'Equal',
|
|
289
|
+
reference: filter.reference,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
case 'number_greaterThen':
|
|
293
|
+
return {
|
|
294
|
+
operator: 'Greater',
|
|
295
|
+
reference: filter.reference,
|
|
296
|
+
};
|
|
297
|
+
case 'number_greaterThenOrEqualTo':
|
|
298
|
+
return {
|
|
299
|
+
operator: 'GreaterOrEqual',
|
|
300
|
+
reference: filter.reference,
|
|
301
|
+
};
|
|
302
|
+
case 'number_lessThen':
|
|
303
|
+
return {
|
|
304
|
+
operator: 'Less',
|
|
305
|
+
reference: filter.reference,
|
|
306
|
+
};
|
|
307
|
+
case 'number_lessThenOrEqualTo':
|
|
308
|
+
return {
|
|
309
|
+
operator: 'LessOrEqual',
|
|
310
|
+
reference: filter.reference,
|
|
311
|
+
};
|
|
312
|
+
case 'number_between':
|
|
313
|
+
return {
|
|
314
|
+
operator: 'And',
|
|
315
|
+
operands: [
|
|
316
|
+
{
|
|
317
|
+
operator: filter.includeLowerBound ? 'GreaterOrEqual' : 'Greater',
|
|
318
|
+
reference: filter.lowerBound,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
operator: filter.includeUpperBound ? 'LessOrEqual' : 'Less',
|
|
322
|
+
reference: filter.upperBound,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
};
|
|
326
|
+
case 'string_contains':
|
|
327
|
+
return {
|
|
328
|
+
operator: 'StringContains',
|
|
329
|
+
substring: filter.reference,
|
|
330
|
+
};
|
|
331
|
+
case 'string_doesNotContain':
|
|
332
|
+
return {
|
|
333
|
+
operator: 'Not',
|
|
334
|
+
operand: {
|
|
335
|
+
operator: 'StringContains',
|
|
336
|
+
substring: filter.reference,
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
case 'string_matches':
|
|
340
|
+
return {
|
|
341
|
+
operator: 'Matches',
|
|
342
|
+
regex: filter.reference,
|
|
343
|
+
};
|
|
344
|
+
case 'string_doesNotMatch':
|
|
345
|
+
return {
|
|
346
|
+
operator: 'Not',
|
|
347
|
+
operand: {
|
|
348
|
+
operator: 'Matches',
|
|
349
|
+
regex: filter.reference,
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
case 'string_containsFuzzyMatch':
|
|
353
|
+
return {
|
|
354
|
+
operator: 'StringContainsFuzzy',
|
|
355
|
+
reference: filter.reference,
|
|
356
|
+
maxEdits: filter.maxEdits,
|
|
357
|
+
substitutionsOnly: filter.substitutionsOnly,
|
|
358
|
+
wildcard: filter.wildcard,
|
|
359
|
+
};
|
|
360
|
+
default:
|
|
361
|
+
throw Error(`unsupported filter type: ${type satisfies never}`);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
const makeFilters = (state: Record<string, PlTableFilter>): PTableRecordFilter[] => {
|
|
365
|
+
return columnsWithIds.value
|
|
366
|
+
.map(({ column, id }) => {
|
|
367
|
+
if (!(id in state)) return undefined;
|
|
368
|
+
|
|
369
|
+
const predicate = makePredicate(state[id]);
|
|
370
|
+
const { spec, ...columnId } = column;
|
|
371
|
+
const _ = spec;
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
type: 'bySingleColumn',
|
|
375
|
+
column: columnId,
|
|
376
|
+
predicate,
|
|
377
|
+
} satisfies PTableRecordFilter;
|
|
378
|
+
})
|
|
379
|
+
.filter((entry) => entry !== undefined);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Should this happen on Apply button click instead?
|
|
383
|
+
watch(
|
|
384
|
+
() => reactiveModel,
|
|
385
|
+
(reactiveModel) => {
|
|
386
|
+
if (!lodash.isEqual(reactiveModel.state, model.value.state)) {
|
|
387
|
+
model.value = {
|
|
388
|
+
state: lodash.cloneDeep(reactiveModel.state),
|
|
389
|
+
filters: makeFilters(reactiveModel.state),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
immediate: true,
|
|
395
|
+
deep: true,
|
|
396
|
+
},
|
|
397
|
+
);
|
|
398
|
+
</script>
|
|
399
|
+
|
|
400
|
+
<template>
|
|
401
|
+
<div v-for="({ column, id }, i) in columnsWithIds" :key="id">
|
|
402
|
+
<form v-if="filterOptions[id].length > 0" class="d-flex gap-10 flex-column">
|
|
403
|
+
<PlCheckbox :model-value="!!reactiveModel.state[id]" @update:model-value="(checked) => onFilterActiveChanged(id, checked)">
|
|
404
|
+
{{ column.spec.annotations?.['pl7.app/label']?.trim() ?? 'Unlabeled ' + column.type + ' ' + i.toString() }}
|
|
405
|
+
</PlCheckbox>
|
|
406
|
+
<div class="controls d-flex gap-10 flex-column" :class="{ open: !!reactiveModel.state[id] }">
|
|
407
|
+
<PlDropdown
|
|
408
|
+
v-if="reactiveModel.state[id]"
|
|
409
|
+
:model-value="reactiveModel.state[id]!.type"
|
|
410
|
+
:options="filterOptions[id]"
|
|
411
|
+
label="Predicate"
|
|
412
|
+
@update:model-value="(type) => updateColumnFilter(id, type!)"
|
|
413
|
+
/>
|
|
414
|
+
<template
|
|
415
|
+
v-if="
|
|
416
|
+
reactiveModel.state[id]?.type === 'number_equals' ||
|
|
417
|
+
reactiveModel.state[id]?.type === 'number_notEquals' ||
|
|
418
|
+
reactiveModel.state[id]?.type === 'number_lessThen' ||
|
|
419
|
+
reactiveModel.state[id]?.type === 'number_lessThenOrEqualTo' ||
|
|
420
|
+
reactiveModel.state[id]?.type === 'number_greaterThen' ||
|
|
421
|
+
reactiveModel.state[id]?.type === 'number_greaterThenOrEqualTo'
|
|
422
|
+
"
|
|
423
|
+
>
|
|
424
|
+
<PlNumberField v-model="reactiveModel.state[id].reference" label="Reference value" />
|
|
425
|
+
</template>
|
|
426
|
+
<template v-if="reactiveModel.state[id]?.type === 'number_between'">
|
|
427
|
+
<PlNumberField v-model="reactiveModel.state[id].lowerBound" label="Lower bound" />
|
|
428
|
+
<PlToggleSwitch v-model="reactiveModel.state[id].includeLowerBound" label="Include lower bound" />
|
|
429
|
+
<PlNumberField v-model="reactiveModel.state[id].upperBound" label="Upper bound" />
|
|
430
|
+
<PlToggleSwitch v-model="reactiveModel.state[id].includeUpperBound" label="Include upper bound" />
|
|
431
|
+
</template>
|
|
432
|
+
<template
|
|
433
|
+
v-if="
|
|
434
|
+
reactiveModel.state[id]?.type === 'string_equals' ||
|
|
435
|
+
reactiveModel.state[id]?.type === 'string_notEquals' ||
|
|
436
|
+
reactiveModel.state[id]?.type === 'string_contains' ||
|
|
437
|
+
reactiveModel.state[id]?.type === 'string_doesNotContain' ||
|
|
438
|
+
reactiveModel.state[id]?.type === 'string_matches' ||
|
|
439
|
+
reactiveModel.state[id]?.type === 'string_doesNotMatch'
|
|
440
|
+
"
|
|
441
|
+
>
|
|
442
|
+
<PlTextField v-model="reactiveModel.state[id].reference" label="Reference value" />
|
|
443
|
+
</template>
|
|
444
|
+
<template v-if="reactiveModel.state[id]?.type === 'string_containsFuzzyMatch'">
|
|
445
|
+
<PlTextField v-model="reactiveModel.state[id].reference" label="Reference value" />
|
|
446
|
+
<Slider v-model="reactiveModel.state[id].maxEdits" :max="5" breakpoints label="Maximum nuber of substitutions and indels" />
|
|
447
|
+
<PlToggleSwitch v-model="reactiveModel.state[id].substitutionsOnly" label="Substitutions only" />
|
|
448
|
+
<PlDropdown
|
|
449
|
+
v-model="reactiveModel.state[id].wildcard"
|
|
450
|
+
:options="makeWildcardOptions(reactiveModel.state[id].reference)"
|
|
451
|
+
clearable
|
|
452
|
+
label="Wildcard symbol"
|
|
453
|
+
/>
|
|
454
|
+
</template>
|
|
455
|
+
</div>
|
|
456
|
+
</form>
|
|
457
|
+
</div>
|
|
458
|
+
</template>
|
|
459
|
+
|
|
460
|
+
<style lang="css" scoped>
|
|
461
|
+
.controls {
|
|
462
|
+
max-height: 0;
|
|
463
|
+
transition: max-height 0.15s ease-out;
|
|
464
|
+
overflow: hidden;
|
|
465
|
+
}
|
|
466
|
+
.controls.open {
|
|
467
|
+
max-height: 500px;
|
|
468
|
+
transition: max-height 0.25s ease-in;
|
|
469
|
+
overflow: visible;
|
|
470
|
+
}
|
|
471
|
+
</style>
|