Skip to content

Commit 84dd721

Browse files
committed
Refactors history selection to use reusable composable
Replaces manual selection state management with a centralized composable that provides consistent selection behavior across components. Adds keyboard navigation support and range selection functionality for improved user experience when managing multiple histories. Simplifies bulk operations by leveraging standardized selection methods and automatic cleanup.
1 parent 3db6628 commit 84dd721

File tree

1 file changed

+59
-26
lines changed

1 file changed

+59
-26
lines changed

client/src/components/History/HistoryList.vue

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ import {
3232
getPublishedHistories,
3333
getSharedHistories,
3434
} from "@/api/histories";
35+
import type HistoryCard from "@/components/History/HistoryCard.vue";
36+
import { useHistoryCardActions } from "@/components/History/useHistoryCardActions";
3537
import { useConfirmDialog } from "@/composables/confirmDialog";
38+
import { useSelectedItems } from "@/composables/selectedItems/selectedItems";
3639
import { Toast } from "@/composables/toast";
3740
import { useHistoryStore } from "@/stores/historyStore";
3841
import { updateHistoryFields } from "@/stores/services/history.services";
@@ -98,7 +101,6 @@ const bulkTagsLoading = ref(false);
98101
const bulkDeleteOrRestoreLoading = ref(false);
99102
const bulkPurgeLoading = ref(false);
100103
const historiesLoaded = ref<AnyHistoryEntry[]>([]);
101-
const selectedHistories = ref<SelectedHistory[]>([]);
102104
103105
/** Computed property that determines if the current view is "My Histories" */
104106
const myView = computed(() => props.activeList === "my");
@@ -150,6 +152,49 @@ const invalidFilters = computed(() => historyListFilters.value.getValidFilters(r
150152
const isSurroundedByQuotes = computed(() => /^["'].*["']$/.test(filterText.value));
151153
const hasInvalidFilters = computed(() => !isSurroundedByQuotes.value && Object.keys(invalidFilters.value).length > 0);
152154
155+
const selectedHistories = computed<SelectedHistory[]>(() => {
156+
const ids = Array.from(selectedItems.value.keys());
157+
const matchingHistories = historiesLoaded.value.filter((h) => ids.includes(h.id));
158+
return matchingHistories.map((h) => ({
159+
id: h.id,
160+
name: h.name,
161+
published: h.published,
162+
purged: h.purged,
163+
}));
164+
});
165+
166+
const {
167+
selectedItems,
168+
selectAllInCurrentQuery,
169+
isSelected,
170+
setSelected,
171+
resetSelection,
172+
itemRefs,
173+
initSelectedItem,
174+
onClick,
175+
onKeyDown,
176+
} = useSelectedItems<AnyHistoryEntry, typeof HistoryCard>({
177+
scopeKey: computed(() => `${props.activeList}-histories-${filterText.value}`),
178+
getItemKey: (item) => item.id,
179+
filterText: filterText,
180+
totalItemsInQuery: computed(() => totalHistories.value ?? 0),
181+
allItems: historiesLoaded,
182+
filterClass: historyListFilters.value,
183+
selectable: computed(() => myView.value),
184+
onDelete: async (item) => {
185+
const { onDeleteHistory } = useHistoryCardActions(
186+
computed(() => item),
187+
false,
188+
() => load(true),
189+
);
190+
await onDeleteHistory();
191+
},
192+
expectedKeyDownClass: "history-card",
193+
getAttributeForRangeSelection(item) {
194+
return `g-card-${item.id}`;
195+
},
196+
});
197+
153198
/**
154199
* Updates a specific filter value in the current filter text
155200
* @param {string} filterKey - The key of the filter to update
@@ -252,16 +297,10 @@ function validatedFilterText(): string {
252297
253298
/**
254299
* Toggles selection of a specific history item
255-
* @param {SelectedHistory} h - The history object to toggle selection for
300+
* @param {AnyHistoryEntry} h - The history object to toggle selection for
256301
*/
257-
function onSelectHistory(h: SelectedHistory) {
258-
const index = selectedHistories.value.findIndex((selected) => selected.id === h.id);
259-
260-
if (index === -1) {
261-
selectedHistories.value.push(h);
262-
} else {
263-
selectedHistories.value.splice(index, 1);
264-
}
302+
function onSelectHistory(h: AnyHistoryEntry) {
303+
setSelected(h, !isSelected(h));
265304
}
266305
267306
/**
@@ -270,14 +309,9 @@ function onSelectHistory(h: SelectedHistory) {
270309
*/
271310
function onSelectAllHistories() {
272311
if (selectedHistories.value.length === historiesLoaded.value.length) {
273-
selectedHistories.value = [];
312+
resetSelection();
274313
} else {
275-
selectedHistories.value = historiesLoaded.value.map((h) => ({
276-
id: h.id,
277-
name: h.name,
278-
published: h.published,
279-
purged: h.purged,
280-
}));
314+
selectAllInCurrentQuery();
281315
}
282316
}
283317
@@ -326,15 +360,13 @@ async function onBulkDeleteOrPurge(purge: boolean = false) {
326360
327361
Toast.success(`${purge ? "Purged" : "Deleted"} ${totalSelected} histories.`);
328362
329-
selectedHistories.value = [];
363+
resetSelection();
330364
} catch (e) {
331365
Toast.error(`Failed to ${purge ? "purge" : "delete"} some histories.`);
332366
} finally {
333367
bulkPurgeLoading.value = false;
334368
bulkDeleteOrRestoreLoading.value = false;
335369
336-
selectedHistories.value = tmpSelected;
337-
338370
await load(true);
339371
}
340372
}
@@ -374,14 +406,12 @@ async function onBulkRestore() {
374406
375407
Toast.success(`Restored ${totalSelected} histories.`);
376408
377-
selectedHistories.value = [];
409+
resetSelection();
378410
} catch (e) {
379411
Toast.error(`Failed to restore some histories.`);
380412
} finally {
381413
bulkDeleteOrRestoreLoading.value = false;
382414
383-
selectedHistories.value = tmpSelected;
384-
385415
await load(true);
386416
}
387417
}
@@ -423,8 +453,6 @@ async function onBulkTagsAdd(tags: string[]) {
423453
} finally {
424454
bulkTagsLoading.value = false;
425455
426-
selectedHistories.value = tmpSelected;
427-
428456
await load(true);
429457
}
430458
}
@@ -436,7 +464,7 @@ async function onBulkTagsAdd(tags: string[]) {
436464
watch([filterText, sortBy, sortDesc], async () => {
437465
offset.value = 0;
438466
439-
selectedHistories.value = [];
467+
resetSelection();
440468
441469
await load(true);
442470
});
@@ -597,8 +625,13 @@ onMounted(async () => {
597625
:grid-view="currentListViewMode === 'grid'"
598626
:selectable="myView"
599627
:selected-history-ids="selectedHistories"
628+
:item-refs="itemRefs"
629+
:range-select-anchor="initSelectedItem"
630+
:clickable="true"
600631
@refreshList="load"
601632
@select="onSelectHistory"
633+
@on-key-down="onKeyDown"
634+
@on-history-card-click="onClick"
602635
@updateFilter="updateFilterValue"
603636
@tagClick="(tag) => updateFilterValue('tag', `'${tag}'`)" />
604637
</BOverlay>

0 commit comments

Comments
 (0)