mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-22 23:39:45 +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>
51 lines
1.3 KiB
Vue
51 lines
1.3 KiB
Vue
<template>
|
|
<IconGroup>
|
|
<IconButton size="sm" @click="handleDelete">
|
|
<i class="icon-[lucide--trash-2] size-4" />
|
|
</IconButton>
|
|
<IconButton size="sm" @click="handleDownload">
|
|
<i class="icon-[lucide--download] size-4" />
|
|
</IconButton>
|
|
<MoreButton
|
|
size="sm"
|
|
@menu-opened="emit('menuStateChanged', true)"
|
|
@menu-closed="emit('menuStateChanged', false)"
|
|
>
|
|
<template #default="{ close }">
|
|
<MediaAssetMoreMenu :close="close" />
|
|
</template>
|
|
</MoreButton>
|
|
</IconGroup>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { inject } from 'vue'
|
|
|
|
import IconButton from '@/components/button/IconButton.vue'
|
|
import IconGroup from '@/components/button/IconGroup.vue'
|
|
import MoreButton from '@/components/button/MoreButton.vue'
|
|
|
|
import { useMediaAssetActions } from '../composables/useMediaAssetActions'
|
|
import { MediaAssetKey } from '../schemas/mediaAssetSchema'
|
|
import MediaAssetMoreMenu from './MediaAssetMoreMenu.vue'
|
|
|
|
const emit = defineEmits<{
|
|
menuStateChanged: [isOpen: boolean]
|
|
}>()
|
|
|
|
const { asset } = inject(MediaAssetKey)!
|
|
const actions = useMediaAssetActions()
|
|
|
|
const handleDelete = () => {
|
|
if (asset.value) {
|
|
actions.deleteAsset(asset.value.id)
|
|
}
|
|
}
|
|
|
|
const handleDownload = () => {
|
|
if (asset.value) {
|
|
actions.downloadAsset(asset.value.id)
|
|
}
|
|
}
|
|
</script>
|