mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary
Implements a comprehensive media asset card component system for the
Asset Manager sidebar, enabling display and interaction with various
media types (images, videos, audio, and 3D models).
## Changes
### New Components
- **MediaAssetCard**: Main card component for displaying media assets
- **Media type-specific components**: Specialized display logic for each
media type
- MediaImageTop/Bottom
- MediaVideoTop/Bottom
- MediaAudioTop/Bottom
- Media3DTop/Bottom
- **MediaAssetActions**: Top-left action buttons (delete, download, more
options)
- **MediaAssetMoreMenu**: Dropdown menu for additional actions
- **SquareChip**: Chip component for displaying duration and file format
with dark/light variants
- **MediaAssetButtonDivider**: Visual separator for button groups
### Features
- **Video playback**: Autoplay with native video controls
- Dynamic duration chip positioning based on control visibility
- Hides overlays when video is playing
- **Audio playback**: Audio icon with HTML5 audio element
- Duration chip with consistent positioning
- **3D model support**: Icon display for 3D assets
- **Selection state**: Proper hover and selected state handling with CSS
priority fixes
### Architecture Improvements
- **Domain-Driven Design structure**: Organized under
`src/platform/mediaAsset/` following DDD principles
- **Provide/Inject pattern**: Eliminates props drilling with
MediaAssetKey InjectionKey
- **Composable pattern**: `useMediaAssetActions` manages all action
handlers
- **Type safety**: Comprehensive TypeScript types for media assets and
actions
### UI/UX Enhancements
- **CardTop component**: Added custom class props for slot positioning
- **SquareChip component**: Backdrop blur effects with variant system
- **Lazy loading**: Image optimization with LazyImage component
- **Responsive states**: Loading, selected, and hover states
### Utilities
- **formatDuration**: Converts milliseconds to human-readable format
(45s, 1m 23s, 1h 2m)
## Testing
- Comprehensive Storybook stories for all media types
- Grid layout examples
- Loading and selected state demonstrations
## File Structure
```
src/platform/assets/
├── components/
│ ├── MediaAssetCard.vue
│ ├── MediaAssetCard.stories.ts
│ ├── MediaAssetActions.vue
│ ├── MediaAssetMoreMenu.vue
│ ├── MediaAssetButtonDivider.vue
│ ├── MediaImageTop.vue
│ ├── MediaImageBottom.vue
│ ├── MediaVideoTop.vue
│ ├── MediaVideoBottom.vue
│ ├── MediaAudioTop.vue
│ ├── MediaAudioBottom.vue
│ ├── Media3DTop.vue
│ └── Media3DBottom.vue
├── composables/
│ └── useMediaAssetActions.ts
└── schemas/
└── mediaAssetSchema.ts
```
## Screenshots
[media_asset_record.webm](https://github.com/user-attachments/assets/d13b5cc0-a262-4850-bb81-ca1daa0dd969)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
76 lines
1.6 KiB
Vue
76 lines
1.6 KiB
Vue
<template>
|
|
<div class="relative inline-flex items-center">
|
|
<IconButton :size="size" :type="type" @click="toggle">
|
|
<i v-if="!isVertical" class="icon-[lucide--ellipsis] text-sm" />
|
|
<i v-else class="icon-[lucide--more-vertical] text-sm" />
|
|
</IconButton>
|
|
|
|
<Popover
|
|
ref="popover"
|
|
:append-to="'body'"
|
|
:auto-z-index="true"
|
|
:base-z-index="1000"
|
|
:dismissable="true"
|
|
:close-on-escape="true"
|
|
unstyled
|
|
:pt="pt"
|
|
@show="$emit('menuOpened')"
|
|
@hide="$emit('menuClosed')"
|
|
>
|
|
<div class="flex min-w-40 flex-col gap-2 p-2">
|
|
<slot :close="hide" />
|
|
</div>
|
|
</Popover>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import Popover from 'primevue/popover'
|
|
import { computed, ref } from 'vue'
|
|
|
|
import type { BaseButtonProps } from '@/types/buttonTypes'
|
|
import { cn } from '@/utils/tailwindUtil'
|
|
|
|
import IconButton from './IconButton.vue'
|
|
|
|
interface MoreButtonProps extends BaseButtonProps {
|
|
isVertical?: boolean
|
|
}
|
|
|
|
const popover = ref<InstanceType<typeof Popover>>()
|
|
|
|
const {
|
|
size = 'md',
|
|
type = 'secondary',
|
|
isVertical = false
|
|
} = defineProps<MoreButtonProps>()
|
|
|
|
defineEmits<{
|
|
menuOpened: []
|
|
menuClosed: []
|
|
}>()
|
|
|
|
const toggle = (event: Event) => {
|
|
popover.value?.toggle(event)
|
|
}
|
|
|
|
const hide = () => {
|
|
popover.value?.hide()
|
|
}
|
|
|
|
const pt = computed(() => ({
|
|
root: {
|
|
class: cn('absolute z-50')
|
|
},
|
|
content: {
|
|
class: cn(
|
|
'mt-1 rounded-lg',
|
|
'bg-white dark-theme:bg-zinc-800',
|
|
'text-neutral dark-theme:text-white',
|
|
'shadow-lg',
|
|
'border border-zinc-200 dark-theme:border-zinc-700'
|
|
)
|
|
}
|
|
}))
|
|
</script>
|