@@ -15,6 +15,7 @@ import storeRoms, { type SimpleRom } from "@/stores/roms";
1515import type { Events } from " @/types/emitter" ;
1616import { useSnackbar } from " @/v2/composables/useSnackbar" ;
1717import storeGalleryRoms from " @/v2/stores/galleryRoms" ;
18+ import storeGallerySelection from " @/v2/stores/gallerySelection" ;
1819
1920defineOptions ({ inheritAttrs: false });
2021
@@ -24,6 +25,7 @@ const route = useRoute();
2425const show = ref (false );
2526const romsStore = storeRoms ();
2627const galleryRomsStore = storeGalleryRoms ();
28+ const gallerySelectionStore = storeGallerySelection ();
2729const roms = ref <SimpleRom []>([]);
2830const romsToDeleteFromFs = ref <number []>([]);
2931const excludeOnDelete = ref (false );
@@ -73,13 +75,27 @@ async function deleteRoms() {
7375 if (deleting .value ) return ;
7476 deleting .value = true ;
7577
78+ // Snapshot the dialog state up front: the dialog is a singleton, so a
79+ // fresh `showDeleteRomDialog` event could replace these refs while the
80+ // request is in flight. Acting on the snapshot keeps the response tied
81+ // to the ROMs it actually processed.
82+ const targetRoms = roms .value ;
83+ const targetPlatformId = platformId .value ;
84+ const deleteFromFs = romsToDeleteFromFs .value ;
85+ const exclude = excludeOnDelete .value ;
86+
7687 try {
7788 const response = await romApi .deleteRoms ({
78- roms: roms . value ,
79- deleteFromFs: romsToDeleteFromFs . value ,
89+ roms: targetRoms ,
90+ deleteFromFs ,
8091 });
92+ // The backend deletes per-ROM and can partially fail; only prune the
93+ // ROMs it actually removed so a failed subset stays visible and
94+ // selected for the user to retry.
95+ const failedIds = new Set (response .data .failed_ids );
96+ const deletedRoms = targetRoms .filter ((rom ) => ! failedIds .has (rom .id ));
8197 snackbar .success (
82- fsCount . value > 0
98+ deleteFromFs . length > 0
8399 ? t (" rom.deleted-from-filesystem" , {
84100 count: response .data .successful_items ,
85101 })
@@ -88,8 +104,8 @@ async function deleteRoms() {
88104 }),
89105 { icon: " mdi-check-bold" },
90106 );
91- if (excludeOnDelete . value ) {
92- for (const rom of roms . value ) {
107+ if (exclude ) {
108+ for (const rom of deletedRoms ) {
93109 const type = rom .has_simple_single_file
94110 ? " EXCLUDED_SINGLE_FILES"
95111 : " EXCLUDED_MULTI_FILES" ;
@@ -101,24 +117,27 @@ async function deleteRoms() {
101117 }
102118 }
103119 romsStore .resetSelection ();
104- romsStore .remove (roms .value );
105- galleryRomsStore .remove (roms .value );
120+ // Drop the deleted ROMs from the gallery selection
121+ gallerySelectionStore .removeIds (deletedRoms .map ((rom ) => rom .id ));
122+ romsStore .remove (deletedRoms );
123+ galleryRomsStore .remove (deletedRoms );
106124 romsStore .setRecentRoms (
107125 romsStore .recentRoms .filter (
108- (r ) => ! roms . value .some ((rom ) => rom .id === r .id ),
126+ (r ) => ! deletedRoms .some ((rom ) => rom .id === r .id ),
109127 ),
110128 );
111129 romsStore .setContinuePlayingRoms (
112130 romsStore .continuePlayingRoms .filter (
113- (r ) => ! roms . value .some ((rom ) => rom .id === r .id ),
131+ (r ) => ! deletedRoms .some ((rom ) => rom .id === r .id ),
114132 ),
115133 );
116134 emitter ?.emit (" refreshDrawer" , null );
117135 closeDialog ();
118- if (route .name === " rom" ) {
136+ // Only leave the single-ROM route when that ROM was actually deleted.
137+ if (route .name === " rom" && deletedRoms .length > 0 ) {
119138 router .push ({
120139 name: ROUTES .PLATFORM ,
121- params: { platform: platformId . value },
140+ params: { platform: targetPlatformId },
122141 });
123142 }
124143 } catch (error : unknown ) {
0 commit comments