Enhance MediaAssetCard selection UI and interaction (#6729)

This commit is contained in:
Jin Yi
2025-11-18 10:54:56 +09:00
committed by GitHub
parent a4d979e4c9
commit 7a0302ba7a
6 changed files with 45 additions and 39 deletions

View File

@@ -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 () => {

View File

@@ -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>

View File

@@ -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'
)
)

View File

@@ -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

View File

@@ -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>

View File

@@ -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"