Skip to content

Commit 453047e

Browse files
[6.x] Ability to save/reset changes to filter presets (#13956)
Co-authored-by: Jason Varga <jason@pixelfear.com>
1 parent ec212fe commit 453047e

1 file changed

Lines changed: 92 additions & 14 deletions

File tree

resources/js/components/ui/Listing/Presets.vue

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,30 @@ import {
1212
} from '@ui';
1313
import { injectListingContext } from '../Listing/Listing.vue';
1414
import { computed, ref, watch } from 'vue';
15+
import { deepClone } from '@/util/clone.js';
1516
1617
const { preferencesPrefix, activeFilters, searchQuery, setFilters, clearFilters, setSearchQuery, clearSearchQuery } =
1718
injectListingContext();
1819
const preferencesKey = ref(`${preferencesPrefix.value}.filters`);
1920
const presets = ref(getPresets());
2021
const activePreset = ref(getPresetFromActiveFilters());
2122
const activePresetPayload = computed(() => presets.value[activePreset.value]);
23+
const selectedPreset = ref(activePreset.value);
24+
const selectedPresetPayload = computed(() => presets.value[selectedPreset.value]);
2225
const savingPresetName = ref(null);
2326
const savingPresetHandle = computed(() => snake_case(savingPresetName.value));
2427
const isCreating = ref(false);
2528
const isRenaming = ref(false);
2629
const isConfirmingDeletion = ref(false);
2730
28-
watch([activeFilters, searchQuery], () => (activePreset.value = getPresetFromActiveFilters()), { deep: true });
31+
watch(
32+
[activeFilters, searchQuery],
33+
() => {
34+
activePreset.value = getPresetFromActiveFilters();
35+
selectedPreset.value ??= activePreset.value;
36+
},
37+
{ deep: true },
38+
);
2939
3040
function getPresets() {
3141
return Statamic.$preferences.get(preferencesKey.value, {});
@@ -37,13 +47,15 @@ function refreshPresets() {
3747
3848
function viewAll() {
3949
activePreset.value = null;
50+
selectedPreset.value = null;
4051
clearFilters();
4152
clearSearchQuery();
4253
}
4354
4455
function selectPreset(handle) {
4556
activePreset.value = handle;
46-
setFilters(activePresetPayload.value.filters);
57+
selectedPreset.value = handle;
58+
setFilters(deepClone(activePresetPayload.value.filters ?? {}));
4759
setSearchQuery(activePresetPayload.value.query);
4860
}
4961
@@ -52,23 +64,70 @@ function createPreset() {
5264
isCreating.value = true;
5365
}
5466
55-
function canRenamePreset(handle) {
67+
function canSavePreset(handle) {
5668
return !Statamic.$preferences.hasDefault(`${preferencesKey.value}.${handle}`);
5769
}
5870
5971
function canDeletePreset(handle) {
60-
return canRenamePreset(handle);
72+
return canSavePreset(handle);
6173
}
6274
6375
function renamePreset() {
64-
savingPresetName.value = activePresetPayload.value.display;
76+
savingPresetName.value = selectedPresetPayload.value.display;
6577
isRenaming.value = true;
6678
}
6779
6880
const canSaveNewPreset = computed(() => {
69-
return !activePreset.value && (Object.keys(activeFilters.value).length > 0 || (searchQuery.value ?? '') !== '');
81+
if (selectedPreset.value) return isDirty.value;
82+
83+
return (Object.keys(activeFilters.value).length > 0 || (searchQuery.value ?? '') !== '');
84+
});
85+
86+
const isDirty = computed(() => {
87+
if (!selectedPreset.value || !selectedPresetPayload.value) return false;
88+
89+
const savedFilters = selectedPresetPayload.value.filters ?? {};
90+
const savedQuery = selectedPresetPayload.value.query ?? '';
91+
const currentQuery = searchQuery.value ?? '';
92+
93+
if (savedQuery !== currentQuery) return true;
94+
95+
const savedKeys = Object.keys(savedFilters);
96+
const currentKeys = Object.keys(activeFilters.value);
97+
98+
if (savedKeys.length !== currentKeys.length) return true;
99+
100+
for (const key of savedKeys) {
101+
if (JSON.stringify(savedFilters[key]) !== JSON.stringify(activeFilters.value[key])) {
102+
return true;
103+
}
104+
}
105+
106+
return false;
70107
});
71108
109+
function savePreset() {
110+
const payload = { display: selectedPresetPayload.value.display };
111+
112+
if (searchQuery.value) payload.query = searchQuery.value;
113+
if (Object.entries(activeFilters.value).length) payload.filters = deepClone(activeFilters.value);
114+
115+
Statamic.$preferences
116+
.set(`${preferencesKey.value}.${selectedPreset.value}`, payload)
117+
.then(() => {
118+
Statamic.$toast.success(__('View saved'));
119+
refreshPresets();
120+
})
121+
.catch(() => {
122+
Statamic.$toast.error(__('Unable to save view'));
123+
});
124+
}
125+
126+
function resetPreset() {
127+
setFilters(deepClone(selectedPresetPayload.value.filters ?? {}));
128+
setSearchQuery(selectedPresetPayload.value.query ?? '');
129+
}
130+
72131
function getPresetFromActiveFilters() {
73132
for (const [handle, preset] of Object.entries(presets.value)) {
74133
const a = {
@@ -88,7 +147,7 @@ function getPresetFromActiveFilters() {
88147
}
89148
90149
const currentTab = computed({
91-
get: () => activePreset.value || 'all',
150+
get: () => selectedPreset.value || 'all',
92151
set: (value) => {
93152
if (value === 'all') {
94153
viewAll();
@@ -100,7 +159,7 @@ const currentTab = computed({
100159
101160
const presetPreferencesPayload = computed(() => {
102161
let payload = {
103-
display: savingPresetName.value || activePresetPayload.value.display || '',
162+
display: savingPresetName.value || selectedPresetPayload.value?.display || '',
104163
};
105164
106165
if (searchQuery.value) payload.query = searchQuery.value;
@@ -133,7 +192,7 @@ function saveExisting() {
133192
134193
preference = Object.fromEntries(
135194
Object.entries(preference).map(([key, value]) => {
136-
if (key === activePreset.value) {
195+
if (key === selectedPreset.value) {
137196
return [savingPresetHandle.value, presetPreferencesPayload.value];
138197
}
139198
@@ -157,7 +216,7 @@ function saveExisting() {
157216
158217
function deletePreset() {
159218
Statamic.$preferences
160-
.remove(`${preferencesKey.value}.${activePreset.value}`)
219+
.remove(`${preferencesKey.value}.${selectedPreset.value}`)
161220
.then((response) => {
162221
Statamic.$toast.success(__('View deleted'));
163222
isConfirmingDeletion.value = false;
@@ -182,15 +241,15 @@ function deletePreset() {
182241
:name="handle"
183242
>
184243
{{ preset.display }}
185-
<template v-if="handle === activePreset">
244+
<template v-if="handle === selectedPreset">
186245
<Dropdown class="w-48!">
187246
<template #trigger>
188247
<Button class="absolute! top-0.25 -right-4 starting-style-transition starting-style-transition--slow" variant="ghost" size="xs" icon="chevron-down" />
189248
</template>
190249
<DropdownMenu>
191250
<DropdownItem :text="__('Duplicate')" icon="duplicate" @click="createPreset" />
192251
<DropdownItem
193-
v-if="canRenamePreset(handle)"
252+
v-if="canSavePreset(handle)"
194253
:text="__('Rename')"
195254
icon="rename"
196255
@click="renamePreset"
@@ -208,8 +267,27 @@ function deletePreset() {
208267
</template>
209268
</PresetTrigger>
210269
</TabList>
211-
<div v-if="canSaveNewPreset" class="border-b border-gray-200 dark:border-gray-700 relative -top-[2px] hover:border-transparent ps-2">
270+
<div v-if="isDirty || canSaveNewPreset" class="border-b border-gray-200 dark:border-gray-700 relative -top-[2px] hover:border-transparent ps-2 flex gap-1">
271+
<Button
272+
v-if="isDirty && canSavePreset(selectedPreset)"
273+
@click="savePreset"
274+
variant="ghost"
275+
size="sm"
276+
:text="__('Save')"
277+
icon="save"
278+
class="[&_svg]:size-4"
279+
/>
280+
<Button
281+
v-if="isDirty"
282+
@click="resetPreset"
283+
variant="ghost"
284+
size="sm"
285+
:text="__('Reset')"
286+
icon="sync"
287+
class="[&_svg]:size-4"
288+
/>
212289
<Button
290+
v-if="canSaveNewPreset"
213291
@click="createPreset"
214292
variant="ghost"
215293
size="sm"
@@ -248,7 +326,7 @@ function deletePreset() {
248326
<div
249327
v-if="
250328
Object.keys(presets)
251-
.filter((preset) => preset !== activePreset)
329+
.filter((preset) => preset !== selectedPreset)
252330
.includes(savingPresetHandle)
253331
"
254332
>

0 commit comments

Comments
 (0)