mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-22 07:44:11 +00:00
Enhance MediaAssetCard selection UI and interaction (#6729)
This commit is contained in:
@@ -67,7 +67,7 @@
|
||||
/>
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div v-else class="relative size-full">
|
||||
<div v-else class="relative size-full" @click="handleEmptySpaceClick">
|
||||
<VirtualGrid
|
||||
:items="mediaAssetsWithKey"
|
||||
:grid-style="{
|
||||
@@ -97,32 +97,22 @@
|
||||
<template #footer>
|
||||
<div
|
||||
v-if="hasSelection"
|
||||
class="flex h-18 w-full items-center justify-between px-4"
|
||||
class="flex gap-1 h-18 w-full items-center justify-between"
|
||||
>
|
||||
<div>
|
||||
<div ref="selectionCountButtonRef" class="flex-1 pl-4">
|
||||
<TextButton
|
||||
v-if="isHoveringSelectionCount"
|
||||
:label="$t('mediaAsset.selection.deselectAll')"
|
||||
:label="
|
||||
isHoveringSelectionCount
|
||||
? $t('mediaAsset.selection.deselectAll')
|
||||
: $t('mediaAsset.selection.selectedCount', {
|
||||
count: totalOutputCount
|
||||
})
|
||||
"
|
||||
type="transparent"
|
||||
@click="handleDeselectAll"
|
||||
@mouseleave="isHoveringSelectionCount = false"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
role="button"
|
||||
tabindex="0"
|
||||
:aria-label="$t('mediaAsset.selection.deselectAll')"
|
||||
class="cursor-pointer px-3 text-sm focus:ring-2 focus:ring-primary focus:outline-none"
|
||||
@mouseenter="isHoveringSelectionCount = true"
|
||||
@keydown.enter="handleDeselectAll"
|
||||
@keydown.space.prevent="handleDeselectAll"
|
||||
>
|
||||
{{
|
||||
$t('mediaAsset.selection.selectedCount', { count: selectedCount })
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex gap-2 pr-4">
|
||||
<IconTextButton
|
||||
v-if="shouldShowDeleteButton"
|
||||
:label="$t('mediaAsset.selection.deleteSelected')"
|
||||
@@ -155,7 +145,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { useDebounceFn, useElementHover } from '@vueuse/core'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
@@ -223,7 +213,6 @@ const {
|
||||
isSelected,
|
||||
handleAssetClick,
|
||||
hasSelection,
|
||||
selectedCount,
|
||||
clearSelection,
|
||||
getSelectedAssets,
|
||||
activate: activateSelection,
|
||||
@@ -232,8 +221,15 @@ const {
|
||||
|
||||
const { downloadMultipleAssets, deleteMultipleAssets } = useMediaAssetActions()
|
||||
|
||||
// Hover state for selection count
|
||||
const isHoveringSelectionCount = ref(false)
|
||||
// Hover state for selection count button
|
||||
const selectionCountButtonRef = ref<HTMLElement | null>(null)
|
||||
const isHoveringSelectionCount = useElementHover(selectionCountButtonRef)
|
||||
|
||||
// Total output count for all selected assets
|
||||
const totalOutputCount = computed(() => {
|
||||
const selectedAssets = getSelectedAssets(displayAssets.value)
|
||||
return selectedAssets.reduce((sum, asset) => sum + getOutputCount(asset), 0)
|
||||
})
|
||||
|
||||
const currentAssets = computed(() =>
|
||||
activeTab.value === 'input' ? inputAssets : outputAssets
|
||||
@@ -400,7 +396,6 @@ const exitFolderView = () => {
|
||||
folderExecutionTime.value = undefined
|
||||
folderAssets.value = []
|
||||
searchQuery.value = ''
|
||||
clearSelection()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -413,7 +408,12 @@ onUnmounted(() => {
|
||||
|
||||
const handleDeselectAll = () => {
|
||||
clearSelection()
|
||||
isHoveringSelectionCount.value = false
|
||||
}
|
||||
|
||||
const handleEmptySpaceClick = () => {
|
||||
if (hasSelection) {
|
||||
clearSelection()
|
||||
}
|
||||
}
|
||||
|
||||
const copyJobId = async () => {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<template>
|
||||
<div class="relative size-full overflow-hidden rounded">
|
||||
<div
|
||||
class="flex size-full flex-col items-center justify-center gap-2 bg-modal-card-placeholder-background text-base-foreground"
|
||||
class="flex size-full flex-col items-center justify-center gap-2 bg-modal-card-placeholder-background transition-transform duration-300 group-hover:scale-105 group-data-[selected=true]:scale-105"
|
||||
>
|
||||
<i class="icon-[lucide--box] text-3xl" />
|
||||
<span>{{ $t('assetBrowser.media.threeDModelPlaceholder') }}</span>
|
||||
<i class="icon-[lucide--box] text-3xl text-base-foreground" />
|
||||
<span class="text-base-foreground">{{
|
||||
$t('assetBrowser.media.threeDModelPlaceholder')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
variant="ghost"
|
||||
rounded="lg"
|
||||
:class="containerClasses"
|
||||
:data-selected="selected"
|
||||
@click.stop
|
||||
>
|
||||
<template #top>
|
||||
<CardTop
|
||||
@@ -247,10 +249,10 @@ provide(MediaAssetKey, {
|
||||
|
||||
const containerClasses = computed(() =>
|
||||
cn(
|
||||
'gap-1 select-none',
|
||||
'gap-1 select-none group',
|
||||
selected
|
||||
? 'border-3 border-base-foreground bg-modal-card-background'
|
||||
: 'hover:bg-modal-card-background/70'
|
||||
? 'ring-3 ring-inset ring-base-foreground bg-modal-card-background'
|
||||
: 'hover:bg-modal-card-background'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<template>
|
||||
<div class="relative size-full overflow-hidden rounded">
|
||||
<div
|
||||
class="flex size-full flex-col items-center justify-center gap-2 bg-modal-card-placeholder-background text-base-foreground"
|
||||
class="flex size-full flex-col items-center justify-center gap-2 bg-modal-card-placeholder-background transition-transform duration-300 group-hover:scale-105 group-data-[selected=true]:scale-105"
|
||||
>
|
||||
<i class="icon-[lucide--music] text-3xl" />
|
||||
<span>{{ $t('assetBrowser.media.audioPlaceholder') }}</span>
|
||||
<i class="icon-[lucide--music] text-3xl text-base-foreground" />
|
||||
<span class="text-base-foreground">{{
|
||||
$t('assetBrowser.media.audioPlaceholder')
|
||||
}}</span>
|
||||
</div>
|
||||
<audio
|
||||
controls
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
v-if="!error"
|
||||
:src="asset.src"
|
||||
:alt="asset.name"
|
||||
class="size-full object-contain"
|
||||
class="size-full object-contain transition-transform duration-300 group-hover:scale-105 group-data-[selected=true]:scale-105"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="flex size-full items-center justify-center bg-modal-card-placeholder-background"
|
||||
>
|
||||
<i class="pi pi-image text-3xl text-smoke-400" />
|
||||
<i class="pi pi-image text-3xl text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
loop
|
||||
playsinline
|
||||
:poster="asset.preview_url"
|
||||
class="relative size-full object-contain"
|
||||
class="relative size-full object-contain transition-transform duration-300 group-hover:scale-105 group-data-[selected=true]:scale-105"
|
||||
@click.stop
|
||||
@play="onVideoPlay"
|
||||
@pause="onVideoPause"
|
||||
|
||||
Reference in New Issue
Block a user