@webitel/ui-sdk 24.12.84 → 24.12.86
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 +29 -8
- package/dist/img/sprite/add-filter.svg +8 -0
- package/dist/img/sprite/index.js +2 -0
- package/dist/ui-sdk.css +1 -1
- package/dist/ui-sdk.js +2759 -2752
- package/dist/ui-sdk.umd.cjs +15 -15
- package/package.json +1 -1
- package/src/assets/icons/sprite/add-filter.svg +8 -0
- package/src/assets/icons/sprite/index.js +2 -0
- package/src/components/index.js +71 -0
- package/src/components/wt-action-bar/wt-action-bar.vue +9 -4
- package/src/components/wt-datepicker/wt-datepicker.vue +11 -1
- package/src/components/wt-icon-action/iconMappings.js +1 -0
- package/src/components/wt-loader/_internals/wt-loader--md.vue +3 -2
- package/src/components/wt-tooltip/_internals/useTooltipTriggerSubscriptions.js +0 -1
- package/src/components/wt-tooltip/wt-tooltip.vue +4 -1
- package/src/css/main.scss +0 -1
- package/src/css/pages/table-page.scss +0 -1
- package/src/css/styleguide/placeholder/_placeholder.scss +2 -2
- package/src/enums/IconAction/IconAction.enum.js +1 -0
- package/src/locale/en/en.js +19 -0
- package/src/locale/ru/ru.js +1 -0
- package/src/locale/ua/ua.js +1 -0
- package/src/modules/Filters/v2/filters/classes/Filter.ts +30 -0
- package/src/modules/Filters/v2/filters/classes/FilterStorage.ts +34 -0
- package/src/modules/Filters/v2/filters/classes/FilterStorageOptions.d.ts +6 -0
- package/src/modules/Filters/v2/filters/classes/FiltersManager.ts +189 -0
- package/src/modules/Filters/v2/filters/components/dynamic/config/dynamic-filter-config-form.vue +146 -0
- package/src/modules/Filters/v2/filters/components/dynamic/config/dynamic-filter-config-view.vue +29 -0
- package/src/modules/Filters/v2/filters/components/dynamic/dynamic-filter-add-action.vue +41 -0
- package/src/modules/Filters/v2/filters/components/dynamic/preview/dynamic-filter-preview-info.vue +48 -0
- package/src/modules/Filters/v2/filters/components/dynamic/preview/dynamic-filter-preview.vue +61 -0
- package/src/modules/Filters/v2/filters/components/static/search-filter.vue +14 -0
- package/src/modules/Filters/v2/filters/components/table-filters-panel.vue +87 -0
- package/src/modules/Filters/v2/filters/createTableFiltersStore.ts +81 -0
- package/src/modules/Filters/v2/filters/index.ts +27 -0
- package/src/modules/Filters/v2/filters/scripts/utils.ts +31 -0
- package/src/modules/Filters/v2/filters/types/Filter.d.ts +41 -0
- package/src/modules/Filters/v2/filters/types/FiltersManager.d.ts +76 -0
- package/src/modules/Filters/v2/headers/createTableHeadersStore.ts +89 -0
- package/src/modules/Filters/v2/index.ts +0 -0
- package/src/modules/Filters/v2/pagination/createTablePaginationStore.ts +54 -0
- package/src/modules/Filters/v2/persist/PersistedStorage.types.ts +51 -0
- package/src/modules/Filters/v2/persist/useLocalStoragePersistedStorage.ts +31 -0
- package/src/modules/Filters/v2/persist/usePersistedStorage.ts +150 -0
- package/src/modules/Filters/v2/persist/useRoutePersistedStorage.ts +41 -0
- package/src/modules/Filters/v2/table/createTableStore.store.ts +180 -0
- package/src/modules/Filters/v2/types/tableStore.types.ts +60 -0
package/src/modules/Filters/v2/filters/components/dynamic/config/dynamic-filter-config-form.vue
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<form class="dynamic-filter-config-form">
|
|
3
|
+
<wt-select
|
|
4
|
+
:disabled="editMode"
|
|
5
|
+
:label="t('webitelUI.filters.filterName')"
|
|
6
|
+
:options="options"
|
|
7
|
+
:value="filterName"
|
|
8
|
+
use-value-from-options-by-prop="id"
|
|
9
|
+
@input="filterName = $event"
|
|
10
|
+
/>
|
|
11
|
+
<slot
|
|
12
|
+
name="value-input"
|
|
13
|
+
v-bind="{
|
|
14
|
+
filterName,
|
|
15
|
+
filterValue,
|
|
16
|
+
inputLabel: t('webitelUI.filters.filterValue'),
|
|
17
|
+
onValueChange: (v) => (filterValue = v),
|
|
18
|
+
onValueInvalidChange: (v) => (invalid = v),
|
|
19
|
+
}"
|
|
20
|
+
/>
|
|
21
|
+
<wt-input
|
|
22
|
+
v-model="filterLabel"
|
|
23
|
+
:label="t('webitelUI.filters.filterLabel')"
|
|
24
|
+
@input="touchedLabel = true"
|
|
25
|
+
/>
|
|
26
|
+
<footer class="dynamic-filter-config-form-footer">
|
|
27
|
+
<wt-button
|
|
28
|
+
:disabled="invalid"
|
|
29
|
+
wide
|
|
30
|
+
@click="submit"
|
|
31
|
+
>
|
|
32
|
+
{{ t('reusable.save') }}
|
|
33
|
+
</wt-button>
|
|
34
|
+
<wt-button
|
|
35
|
+
color="secondary"
|
|
36
|
+
wide
|
|
37
|
+
@click="emit('cancel')"
|
|
38
|
+
>
|
|
39
|
+
{{ t('reusable.cancel') }}
|
|
40
|
+
</wt-button>
|
|
41
|
+
</footer>
|
|
42
|
+
</form>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script lang="ts" setup>
|
|
46
|
+
import deepcopy from 'deep-copy';
|
|
47
|
+
import { ref, watch } from 'vue';
|
|
48
|
+
import { useI18n } from 'vue-i18n';
|
|
49
|
+
|
|
50
|
+
import WtButton from '../../../../../../../components/wt-button/wt-button.vue';
|
|
51
|
+
import WtInput from '../../../../../../../components/wt-input/wt-input.vue';
|
|
52
|
+
import WtSelect from '../../../../../../../components/wt-select/wt-select.vue';
|
|
53
|
+
import type {
|
|
54
|
+
FilterInitParams,
|
|
55
|
+
FilterName,
|
|
56
|
+
IFilter,
|
|
57
|
+
} from '../../../types/Filter';
|
|
58
|
+
|
|
59
|
+
interface FilterNameSelectRepresentation {
|
|
60
|
+
name: string;
|
|
61
|
+
value: FilterName;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface AddModeProps {
|
|
65
|
+
options: Array<FilterNameSelectRepresentation>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface EditModeProps {
|
|
69
|
+
filter: IFilter;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type Props = AddModeProps | EditModeProps;
|
|
73
|
+
|
|
74
|
+
const props = defineProps<Props>();
|
|
75
|
+
|
|
76
|
+
const emit = defineEmits<{
|
|
77
|
+
submit: [FilterInitParams];
|
|
78
|
+
cancel: [];
|
|
79
|
+
}>();
|
|
80
|
+
|
|
81
|
+
const { t } = useI18n();
|
|
82
|
+
|
|
83
|
+
const filterName = ref();
|
|
84
|
+
const filterLabel = ref('');
|
|
85
|
+
const filterValue = ref();
|
|
86
|
+
|
|
87
|
+
// if user have not changed label yet, it will be changed with selected filterName
|
|
88
|
+
const touchedLabel = ref(false);
|
|
89
|
+
|
|
90
|
+
const editMode = !!props.filter;
|
|
91
|
+
|
|
92
|
+
const invalid = ref(false);
|
|
93
|
+
|
|
94
|
+
const submit = () => {
|
|
95
|
+
emit('submit', {
|
|
96
|
+
name: filterName.value,
|
|
97
|
+
label: filterLabel.value,
|
|
98
|
+
value: filterValue.value,
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (props.options) {
|
|
103
|
+
watch(
|
|
104
|
+
props.options,
|
|
105
|
+
() => {
|
|
106
|
+
filterName.value = props.options[0]?.value;
|
|
107
|
+
filterValue.value = null;
|
|
108
|
+
|
|
109
|
+
if (!touchedLabel.value) {
|
|
110
|
+
filterLabel.value = filterName.value;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{ immediate: true },
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (props.filter) {
|
|
118
|
+
watch(
|
|
119
|
+
props.filter,
|
|
120
|
+
() => {
|
|
121
|
+
filterName.value = props.filter.name;
|
|
122
|
+
filterValue.value = deepcopy(props.filter.value);
|
|
123
|
+
filterLabel.value = props.filter.label;
|
|
124
|
+
},
|
|
125
|
+
{ immediate: true },
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
</script>
|
|
129
|
+
|
|
130
|
+
<style lang="scss" scoped>
|
|
131
|
+
$form-width: 380px;
|
|
132
|
+
|
|
133
|
+
.dynamic-filter-config-form {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-direction: column;
|
|
136
|
+
box-sizing: border-box;
|
|
137
|
+
width: $form-width;
|
|
138
|
+
padding: var(--spacing-sm);
|
|
139
|
+
gap: var(--spacing-sm);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.dynamic-filter-config-form-footer {
|
|
143
|
+
display: flex;
|
|
144
|
+
gap: var(--spacing-xs);
|
|
145
|
+
}
|
|
146
|
+
</style>
|
package/src/modules/Filters/v2/filters/components/dynamic/config/dynamic-filter-config-view.vue
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="dynamic-filter-config-view">
|
|
3
|
+
<wt-tooltip :triggers="['click']">
|
|
4
|
+
<template #activator="slotScope">
|
|
5
|
+
<slot
|
|
6
|
+
name="activator"
|
|
7
|
+
v-bind="slotScope"
|
|
8
|
+
/>
|
|
9
|
+
</template>
|
|
10
|
+
<template #default="slotScope">
|
|
11
|
+
<slot
|
|
12
|
+
name="content"
|
|
13
|
+
v-bind="slotScope"
|
|
14
|
+
/>
|
|
15
|
+
</template>
|
|
16
|
+
</wt-tooltip>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script lang="ts" setup>
|
|
21
|
+
/**
|
|
22
|
+
* this component should only be used for config view representation: tooltip/popup/etc,
|
|
23
|
+
* and their styling
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import WtTooltip from '../../../../../../../components/wt-tooltip/wt-tooltip.vue';
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<style lang="scss" scoped></style>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<dynamic-filter-config-view class="dynamic-filter-add-action">
|
|
3
|
+
<template #activator>
|
|
4
|
+
<div class="dynamic-filter-add-action-wrapper">
|
|
5
|
+
<p v-if="props.showLabel">
|
|
6
|
+
{{ t('webitelUI.filters.addFilter') }}
|
|
7
|
+
</p>
|
|
8
|
+
<wt-icon-btn icon="add-filter" />
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<template #content>
|
|
13
|
+
<slot name="form"> filter form should be here</slot>
|
|
14
|
+
</template>
|
|
15
|
+
</dynamic-filter-config-view>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script lang="ts" setup>
|
|
19
|
+
import { useI18n } from 'vue-i18n';
|
|
20
|
+
|
|
21
|
+
import DynamicFilterConfigView from './config/dynamic-filter-config-view.vue';
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
showLabel?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const props = defineProps<Props>();
|
|
28
|
+
|
|
29
|
+
const { t } = useI18n();
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<style lang="scss" scoped>
|
|
33
|
+
.dynamic-filter-add-action-wrapper {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
width: fit-content;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
gap: var(--spacing-2xs);
|
|
40
|
+
}
|
|
41
|
+
</style>
|
package/src/modules/Filters/v2/filters/components/dynamic/preview/dynamic-filter-preview-info.vue
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="dynamic-filter-preview-info">
|
|
3
|
+
<header class="dynamic-filter-preview-info-header">
|
|
4
|
+
{{ props.name }}
|
|
5
|
+
</header>
|
|
6
|
+
|
|
7
|
+
<wt-divider />
|
|
8
|
+
|
|
9
|
+
<!-- TODO: how to visually represent id/value types? -->
|
|
10
|
+
<ul v-if="isArrayValue">
|
|
11
|
+
<li
|
|
12
|
+
v-for="(el, index) of value"
|
|
13
|
+
:key="index"
|
|
14
|
+
>
|
|
15
|
+
{{ el }}
|
|
16
|
+
</li>
|
|
17
|
+
</ul>
|
|
18
|
+
|
|
19
|
+
<article v-else>
|
|
20
|
+
{{ props.value }}
|
|
21
|
+
</article>
|
|
22
|
+
</section>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script lang="ts" setup>
|
|
26
|
+
import type { FilterName, FilterValue } from '../../../types/Filter';
|
|
27
|
+
|
|
28
|
+
interface Props {
|
|
29
|
+
name: FilterName;
|
|
30
|
+
value: FilterValue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const props = defineProps<Props>();
|
|
34
|
+
|
|
35
|
+
const isArrayValue = Array.isArray(props.value);
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<style lang="scss" scoped>
|
|
39
|
+
.dynamic-filter-preview-info {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
gap: var(--spacing-xs);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.dynamic-filter-preview-info-header {
|
|
46
|
+
@extend %typo-subtitle-1;
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<dynamic-filter-config-view>
|
|
3
|
+
<template #activator="{ visible: configFormVisible }">
|
|
4
|
+
<wt-tooltip :disabled="configFormVisible">
|
|
5
|
+
<template #activator>
|
|
6
|
+
<wt-chip color="primary">
|
|
7
|
+
{{ props.filter.label || props.filter.name }}
|
|
8
|
+
<wt-icon-btn
|
|
9
|
+
icon="close--filled"
|
|
10
|
+
size="sm"
|
|
11
|
+
@mousedown.stop="deleteFilter"
|
|
12
|
+
/>
|
|
13
|
+
</wt-chip>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<template #default>
|
|
17
|
+
<dynamic-filter-preview-info
|
|
18
|
+
:name="props.filter.name"
|
|
19
|
+
:value="props.filter.value"
|
|
20
|
+
/>
|
|
21
|
+
</template>
|
|
22
|
+
</wt-tooltip>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<template #content>
|
|
26
|
+
<slot name="form" />
|
|
27
|
+
</template>
|
|
28
|
+
</dynamic-filter-config-view>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script lang="ts" setup>
|
|
32
|
+
import WtChip from '../../../../../../../components/wt-chip/wt-chip.vue';
|
|
33
|
+
import WtIconBtn from '../../../../../../../components/wt-icon-btn/wt-icon-btn.vue';
|
|
34
|
+
import WtTooltip from '../../../../../../../components/wt-tooltip/wt-tooltip.vue';
|
|
35
|
+
import type { IFilter } from '../../../types/Filter';
|
|
36
|
+
import DynamicFilterConfigView from '../config/dynamic-filter-config-view.vue';
|
|
37
|
+
import DynamicFilterPreviewInfo from './dynamic-filter-preview-info.vue';
|
|
38
|
+
|
|
39
|
+
interface Props {
|
|
40
|
+
filter: IFilter;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const props = defineProps<Props>();
|
|
44
|
+
|
|
45
|
+
const emit = defineEmits<{
|
|
46
|
+
'delete:filter': [IFilter];
|
|
47
|
+
}>();
|
|
48
|
+
|
|
49
|
+
const deleteFilter = () => {
|
|
50
|
+
emit('delete:filter', props.filter);
|
|
51
|
+
};
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style lang="scss" scoped>
|
|
55
|
+
.wt-chip {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
justify-content: center;
|
|
59
|
+
gap: var(--spacing-2xs);
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="table-filters-panel">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
<div class="table-filters-panel-filters">
|
|
5
|
+
<dynamic-filter-add-action>
|
|
6
|
+
<template #form>
|
|
7
|
+
<dynamic-filter-config-form
|
|
8
|
+
:options="unappliedFilters"
|
|
9
|
+
@submit="applyFilter"
|
|
10
|
+
>
|
|
11
|
+
<template
|
|
12
|
+
#value-input="{
|
|
13
|
+
filterName,
|
|
14
|
+
filterValue,
|
|
15
|
+
onValueChange,
|
|
16
|
+
onValueInvalidChange,
|
|
17
|
+
}"
|
|
18
|
+
>
|
|
19
|
+
<component
|
|
20
|
+
:is="getFilterValueComponent(filterName)"
|
|
21
|
+
:key="filterName"
|
|
22
|
+
:model-value="filterValue"
|
|
23
|
+
@update:model-value="onValueChange"
|
|
24
|
+
@undate:invalid="onValueInvalidChange"
|
|
25
|
+
/>
|
|
26
|
+
</template>
|
|
27
|
+
</dynamic-filter-config-form>
|
|
28
|
+
</template>
|
|
29
|
+
</dynamic-filter-add-action>
|
|
30
|
+
|
|
31
|
+
<dynamic-filter-preview
|
|
32
|
+
v-for="filter of appliedFilters"
|
|
33
|
+
:key="filter.name"
|
|
34
|
+
:filter="filter"
|
|
35
|
+
@delete:filter="deleteAppliedFilter($event.name)"
|
|
36
|
+
>
|
|
37
|
+
<template #form>
|
|
38
|
+
<dynamic-filter-config-form
|
|
39
|
+
:filter="filter"
|
|
40
|
+
@submit="updateAppliedFilter"
|
|
41
|
+
>
|
|
42
|
+
<template
|
|
43
|
+
#value-input="{
|
|
44
|
+
filterName,
|
|
45
|
+
filterValue,
|
|
46
|
+
onValueChange,
|
|
47
|
+
onValueInvalidChange,
|
|
48
|
+
}"
|
|
49
|
+
>
|
|
50
|
+
<component
|
|
51
|
+
:is="getFilterValueComponent(filterName)"
|
|
52
|
+
:key="filterName"
|
|
53
|
+
:model-value="filterValue"
|
|
54
|
+
@update:model-value="onValueChange"
|
|
55
|
+
@undate:invalid="onValueInvalidChange"
|
|
56
|
+
/>
|
|
57
|
+
</template>
|
|
58
|
+
</dynamic-filter-config-form>
|
|
59
|
+
</template>
|
|
60
|
+
</dynamic-filter-preview>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="table-filters-panel-actions"></div>
|
|
63
|
+
</section>
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
<script lang="ts" setup>
|
|
67
|
+
import DynamicFilterConfigForm from './dynamic/config/dynamic-filter-config-form.vue';
|
|
68
|
+
import DynamicFilterAddAction from './dynamic/dynamic-filter-add-action.vue';
|
|
69
|
+
import DynamicFilterPreview from './dynamic/preview/dynamic-filter-preview.vue';
|
|
70
|
+
|
|
71
|
+
interface Props {}
|
|
72
|
+
|
|
73
|
+
const props = defineProps<Props>();
|
|
74
|
+
|
|
75
|
+
const emit = defineEmits<{
|
|
76
|
+
'add:filter': [];
|
|
77
|
+
'update:filter': [];
|
|
78
|
+
'delete:filter': [];
|
|
79
|
+
'reset:filters': [];
|
|
80
|
+
hide: [];
|
|
81
|
+
}>();
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<style lang="scss" scoped>
|
|
85
|
+
.table-filters-panel {
|
|
86
|
+
}
|
|
87
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { defineStore } from 'pinia';
|
|
2
|
+
import { computed, reactive, ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
import { PersistedStorageType } from '../persist/PersistedStorage.types.ts';
|
|
5
|
+
import { usePersistedStorage } from '../persist/usePersistedStorage.ts';
|
|
6
|
+
import { createFiltersManager } from './classes/FiltersManager.ts';
|
|
7
|
+
import { FiltersManagerConfig } from './types/FiltersManager';
|
|
8
|
+
|
|
9
|
+
export const createTableFiltersStore = (
|
|
10
|
+
namespace: string,
|
|
11
|
+
config?: { filtersManagerConfig: FiltersManagerConfig },
|
|
12
|
+
) => {
|
|
13
|
+
const id = `${namespace}/filters`;
|
|
14
|
+
|
|
15
|
+
return defineStore(id, () => {
|
|
16
|
+
const filtersManager = reactive(
|
|
17
|
+
createFiltersManager(config?.filtersManagerConfig),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
/* for watchers in filter components */
|
|
21
|
+
const isRestoring = ref(false);
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
wrapping filtersManager methods to extend their functionality
|
|
25
|
+
if it will be needed in future
|
|
26
|
+
*/
|
|
27
|
+
const hasFilter = filtersManager.hasFilter.bind(filtersManager);
|
|
28
|
+
const addFilter = filtersManager.addFilter.bind(filtersManager);
|
|
29
|
+
const updateFilter = filtersManager.updateFilter.bind(filtersManager);
|
|
30
|
+
const deleteFilter = filtersManager.deleteFilter.bind(filtersManager);
|
|
31
|
+
|
|
32
|
+
const filtersList = computed(() => filtersManager.getFiltersList());
|
|
33
|
+
|
|
34
|
+
const setupPersistence = () => {
|
|
35
|
+
const { restore: restoreFilters } = usePersistedStorage({
|
|
36
|
+
name: 'filters',
|
|
37
|
+
|
|
38
|
+
value: computed(
|
|
39
|
+
() => filtersManager,
|
|
40
|
+
) /* computed is used to provide value as ref(), not reactive() – as per usePersistedStorage interface */,
|
|
41
|
+
|
|
42
|
+
storages: [PersistedStorageType.Route],
|
|
43
|
+
|
|
44
|
+
/* use custom .toString() logic, provided by FiltersManager */
|
|
45
|
+
onStore: async (save, { name }) => {
|
|
46
|
+
const snapshotStr = filtersManager.toString();
|
|
47
|
+
return save({ name, value: snapshotStr });
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/* use custom .fromString() logic, provided by FiltersManager */
|
|
51
|
+
onRestore: async (restore, name) => {
|
|
52
|
+
isRestoring.value = true;
|
|
53
|
+
const snapshotStr = await restore(name);
|
|
54
|
+
/*
|
|
55
|
+
snapshot as string because we know that filtersManager.toString() returns string,
|
|
56
|
+
not string[]
|
|
57
|
+
*/
|
|
58
|
+
if (snapshotStr) filtersManager.fromString(snapshotStr as string);
|
|
59
|
+
|
|
60
|
+
isRestoring.value = false;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return restoreFilters();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
filtersManager,
|
|
69
|
+
isRestoring,
|
|
70
|
+
|
|
71
|
+
filtersList,
|
|
72
|
+
|
|
73
|
+
hasFilter,
|
|
74
|
+
addFilter,
|
|
75
|
+
updateFilter,
|
|
76
|
+
deleteFilter,
|
|
77
|
+
|
|
78
|
+
setupPersistence,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Filter } from './classes/Filter.ts';
|
|
2
|
+
import { createFiltersManager } from './classes/FiltersManager';
|
|
3
|
+
import type {
|
|
4
|
+
FilterConfig,
|
|
5
|
+
FilterInitParams,
|
|
6
|
+
FilterLabel,
|
|
7
|
+
FilterName,
|
|
8
|
+
FilterValue,
|
|
9
|
+
IFilter,
|
|
10
|
+
} from './types/Filter.d.ts';
|
|
11
|
+
import type {
|
|
12
|
+
FiltersManagerConfig,
|
|
13
|
+
IFiltersManager,
|
|
14
|
+
} from './types/FiltersManager.d.ts';
|
|
15
|
+
|
|
16
|
+
export { createFiltersManager, Filter };
|
|
17
|
+
|
|
18
|
+
export type {
|
|
19
|
+
FilterConfig,
|
|
20
|
+
FilterInitParams,
|
|
21
|
+
FilterLabel,
|
|
22
|
+
FilterName,
|
|
23
|
+
FiltersManagerConfig,
|
|
24
|
+
FilterValue,
|
|
25
|
+
IFilter,
|
|
26
|
+
IFiltersManager,
|
|
27
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FilterName } from '../types/Filter';
|
|
2
|
+
|
|
3
|
+
export const filterLabelToSnapshotKey = (name: FilterName): string =>
|
|
4
|
+
`${name}_lbl`;
|
|
5
|
+
|
|
6
|
+
export const filterValueToSnapshotKey = (name: FilterName): string =>
|
|
7
|
+
`${name}_val`;
|
|
8
|
+
|
|
9
|
+
const filterLabelFromSnapshotKey = (snapshotKey: string): FilterName =>
|
|
10
|
+
snapshotKey.replace('_lbl', '');
|
|
11
|
+
|
|
12
|
+
const filterValueFromSnapshotKey = (snapshotKey: string): FilterName =>
|
|
13
|
+
snapshotKey.replace('_val', '');
|
|
14
|
+
|
|
15
|
+
const isLabelSnapshotKey = (snapshotKey: string): boolean =>
|
|
16
|
+
snapshotKey.includes('_lbl');
|
|
17
|
+
|
|
18
|
+
const isValueSnapshotKey = (snapshotKey: string): boolean =>
|
|
19
|
+
snapshotKey.includes('_val');
|
|
20
|
+
|
|
21
|
+
export const filterNameFromSnapshotKey = (snapshotKey: string): FilterName => {
|
|
22
|
+
if (isLabelSnapshotKey(snapshotKey))
|
|
23
|
+
return filterLabelFromSnapshotKey(snapshotKey);
|
|
24
|
+
if (isValueSnapshotKey(snapshotKey))
|
|
25
|
+
return filterValueFromSnapshotKey(snapshotKey);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const filterValuePropFromSnapshotKey = (snapshotKey: string): string => {
|
|
29
|
+
if (isLabelSnapshotKey(snapshotKey)) return 'label';
|
|
30
|
+
if (isValueSnapshotKey(snapshotKey)) return 'value';
|
|
31
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type FilterName = string;
|
|
2
|
+
export type FilterLabel = string;
|
|
3
|
+
export type FilterValue =
|
|
4
|
+
| object
|
|
5
|
+
| []
|
|
6
|
+
| string
|
|
7
|
+
| number
|
|
8
|
+
| boolean
|
|
9
|
+
| undefined
|
|
10
|
+
| null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* represents user-input data, that should be (re)stored
|
|
14
|
+
*/
|
|
15
|
+
export interface FilterData {
|
|
16
|
+
value: FilterValue;
|
|
17
|
+
label?: FilterLabel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FilterInitParams extends FilterData {
|
|
21
|
+
name: FilterName;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface FilterConfig {
|
|
25
|
+
/**
|
|
26
|
+
* Perform simple type conversion on store/restore,
|
|
27
|
+
* without need to provide custom store/restore functions
|
|
28
|
+
*/
|
|
29
|
+
storableType?: string;
|
|
30
|
+
/**
|
|
31
|
+
* list of persistence storages that should be used for this filter
|
|
32
|
+
*/
|
|
33
|
+
storage?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface IFilter {
|
|
37
|
+
name: FilterName;
|
|
38
|
+
value: FilterValue;
|
|
39
|
+
label?: FilterLabel;
|
|
40
|
+
set: (data: FilterData) => IFilter;
|
|
41
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FilterConfig,
|
|
3
|
+
FilterInitParams,
|
|
4
|
+
FilterLabel,
|
|
5
|
+
FilterName,
|
|
6
|
+
FilterValue,
|
|
7
|
+
IFilter,
|
|
8
|
+
} from './Filter.types.ts';
|
|
9
|
+
|
|
10
|
+
export interface IFiltersManager {
|
|
11
|
+
filters: Map<FilterName, IFilter>;
|
|
12
|
+
|
|
13
|
+
hasFilter: (name: FilterName) => boolean;
|
|
14
|
+
getFilter: (name: FilterName) => IFilter;
|
|
15
|
+
addFilter: (
|
|
16
|
+
params: FilterInitParams,
|
|
17
|
+
payload?: object,
|
|
18
|
+
config?: FilterConfig,
|
|
19
|
+
) => IFilter;
|
|
20
|
+
updateFilter: ({
|
|
21
|
+
name,
|
|
22
|
+
}: {
|
|
23
|
+
name: FilterName;
|
|
24
|
+
value?: FilterValue;
|
|
25
|
+
label?: FilterLabel;
|
|
26
|
+
}) => IFilter;
|
|
27
|
+
deleteFilter: (name: FilterName) => IFilter;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Converts filters data to String, that can be stored
|
|
31
|
+
*/
|
|
32
|
+
toString: () => string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Restores filters from string
|
|
36
|
+
*/
|
|
37
|
+
fromString: (snapshotStr: string) => void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* deletes filters
|
|
41
|
+
* If include/exclude are not provided, all filters will be deleted
|
|
42
|
+
*/
|
|
43
|
+
reset: ({
|
|
44
|
+
include,
|
|
45
|
+
exclude,
|
|
46
|
+
}?: {
|
|
47
|
+
include?: FilterName[];
|
|
48
|
+
exclude?: FilterName[];
|
|
49
|
+
}) => void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @returns Array<FilterName>
|
|
53
|
+
*/
|
|
54
|
+
getAllKeys: () => FilterName[];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @returns { FilterName: FilterValue }
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
getAllValues: () => { [name: FilterName]: FilterValue };
|
|
61
|
+
/**
|
|
62
|
+
* @returns Array<IFilter>
|
|
63
|
+
* @param include
|
|
64
|
+
* @param exclude
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
getFiltersList: ({
|
|
68
|
+
include,
|
|
69
|
+
exclude,
|
|
70
|
+
}?: {
|
|
71
|
+
include?: FilterName[];
|
|
72
|
+
exclude?: FilterName[];
|
|
73
|
+
}) => IFilter[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type FiltersManagerConfig = FilterConfig;
|