From 0fb208aa05207f5867ba6e5e2583e312c41568f0 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Fri, 9 Jan 2026 16:19:52 +0000 Subject: [PATCH] Handle bulk deletes --- .../sidebar/tabs/AssetsSidebarTab.vue | 29 +++++++++++++++++-- .../assets/components/MediaAssetCard.vue | 10 ++++--- .../composables/useMediaAssetActions.ts | 9 +++++- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/components/sidebar/tabs/AssetsSidebarTab.vue b/src/components/sidebar/tabs/AssetsSidebarTab.vue index 032645b4c..0901fd4f4 100644 --- a/src/components/sidebar/tabs/AssetsSidebarTab.vue +++ b/src/components/sidebar/tabs/AssetsSidebarTab.vue @@ -116,10 +116,12 @@ :open-context-menu-id="openContextMenuId" :selected-assets="getSelectedAssets(displayAssets)" :has-selection="hasSelection" + :is-deleting="deletingAssetIds.has(item.id)" @click="handleAssetSelect(item)" @zoom="handleZoomClick(item)" @output-count-click="enterFolderView(item)" @asset-deleted="refreshAssets" + @asset-deleting="handleAssetDeleting(item.id, $event)" @context-menu-opened="openContextMenuId = item.id" @bulk-download="handleBulkDownload" @bulk-delete="handleBulkDelete" @@ -239,6 +241,9 @@ const isQueuePanelV2Enabled = computed(() => // Track which asset's context menu is open (for single-instance context menu management) const openContextMenuId = ref(null) +// Track which assets are currently being deleted (for showing loading state) +const deletingAssetIds = ref(new Set()) + // Determine if delete button should be shown // Hide delete button when in input tab and not in cloud (OSS mode - files are from local folders) const shouldShowDeleteButton = computed(() => { @@ -531,9 +536,21 @@ const handleDownloadSelected = () => { clearSelection() } +const setAssetsDeletingState = (assetIds: string[], isDeleting: boolean) => { + assetIds.forEach((id) => + isDeleting + ? deletingAssetIds.value.add(id) + : deletingAssetIds.value.delete(id) + ) +} + const handleDeleteSelected = async () => { const selectedAssets = getSelectedAssets(displayAssets.value) - await deleteMultipleAssets(selectedAssets) + const assetIds = selectedAssets.map((a) => a.id) + + await deleteMultipleAssets(selectedAssets, (isDeleting) => + setAssetsDeletingState(assetIds, isDeleting) + ) clearSelection() } @@ -542,8 +559,16 @@ const handleBulkDownload = (assets: AssetItem[]) => { clearSelection() } +const handleAssetDeleting = (assetId: string, isDeleting: boolean) => { + setAssetsDeletingState([assetId], isDeleting) +} + const handleBulkDelete = async (assets: AssetItem[]) => { - await deleteMultipleAssets(assets) + const assetIds = assets.map((a) => a.id) + + await deleteMultipleAssets(assets, (isDeleting) => + setAssetsDeletingState(assetIds, isDeleting) + ) clearSelection() } diff --git a/src/platform/assets/components/MediaAssetCard.vue b/src/platform/assets/components/MediaAssetCard.vue index b61410ca7..6aa0bd97b 100644 --- a/src/platform/assets/components/MediaAssetCard.vue +++ b/src/platform/assets/components/MediaAssetCard.vue @@ -143,7 +143,7 @@ :is-bulk-mode="hasSelection && (selectedAssets?.length ?? 0) > 1" @zoom="handleZoomClick" @asset-deleted="emit('asset-deleted')" - @asset-deleting="isDeleting = $event" + @asset-deleting="emit('asset-deleting', $event)" @bulk-download="emit('bulk-download', $event)" @bulk-delete="emit('bulk-delete', $event)" /> @@ -193,7 +193,8 @@ const { showDeleteButton, openContextMenuId, selectedAssets, - hasSelection + hasSelection, + isDeleting } = defineProps<{ asset?: AssetItem loading?: boolean @@ -204,6 +205,7 @@ const { openContextMenuId?: string | null selectedAssets?: AssetItem[] hasSelection?: boolean + isDeleting?: boolean }>() const emit = defineEmits<{ @@ -211,6 +213,7 @@ const emit = defineEmits<{ zoom: [asset: AssetItem] 'output-count-click': [] 'asset-deleted': [] + 'asset-deleting': [isDeleting: boolean] 'context-menu-opened': [] 'bulk-download': [assets: AssetItem[]] 'bulk-delete': [assets: AssetItem[]] @@ -221,7 +224,6 @@ const contextMenu = ref>() const isVideoPlaying = ref(false) const showVideoControls = ref(false) -const isDeleting = ref(false) // Store actual image dimensions const imageDimensions = ref<{ width: number; height: number } | undefined>() @@ -296,7 +298,7 @@ const metaInfo = computed(() => { }) const showActionsOverlay = computed(() => { - if (loading || !asset || isDeleting.value) return false + if (loading || !asset || isDeleting) return false return isHovered.value || selected || isVideoPlaying.value }) diff --git a/src/platform/assets/composables/useMediaAssetActions.ts b/src/platform/assets/composables/useMediaAssetActions.ts index 24ac98dfb..ff30c9700 100644 --- a/src/platform/assets/composables/useMediaAssetActions.ts +++ b/src/platform/assets/composables/useMediaAssetActions.ts @@ -376,8 +376,12 @@ export function useMediaAssetActions() { /** * Delete multiple assets with confirmation dialog * @param assets Array of assets to delete + * @param onDeleting Optional callback called with true when deletion starts (after confirmation) and false when complete */ - const deleteMultipleAssets = async (assets: AssetItem[]) => { + const deleteMultipleAssets = async ( + assets: AssetItem[], + onDeleting?: (deleting: boolean) => void + ) => { if (!assets || assets.length === 0) return const assetsStore = useAssetsStore() @@ -394,6 +398,7 @@ export function useMediaAssetActions() { type: 'delete', itemList: assets.map((asset) => asset.name), onConfirm: async () => { + onDeleting?.(true) try { // Delete all assets using Promise.allSettled to track individual results const results = await Promise.allSettled( @@ -470,6 +475,8 @@ export function useMediaAssetActions() { detail: t('mediaAsset.selection.failedToDeleteAssets'), life: 3000 }) + } finally { + onDeleting?.(false) } resolve()