Files
ComfyUI_frontend/src/platform/assets/components/MediaAssetCard.stories.ts
Jin Yi 06ba106f59 Media Assets Management Sidebar Tab Implementation (#6112)
## 📋 Overview
Implemented a new Media Assets sidebar tab in ComfyUI for managing
user-uploaded input files and generated output files. This feature
supports both local and cloud environments and is currently enabled only
in development mode.

## 🎯 Key Features

### 1. Media Assets Sidebar Tab
- **Imported** / **Generated** files separated by tabs
- Visual display with file preview cards
- Gallery view support (navigable with arrow keys)

### 2. Environment-Specific Implementation
- **`useInternalMediaAssets`**: For local environment
  - Fetches file list via `/files` API
  - Retrieves generation task execution time via `/history` API
  - Processes history data using the same logic as QueueSidebarTab
- **`useCloudMediaAssets`**: For cloud environment
  - File retrieval through assetService
  - History data processing using TaskItemImpl
- Auto-truncation of long filenames over 20 characters (e.g.,
`very_long_filename_here.png` → `very_long_...here.png`)

### 3. Execution Time Display
- Shows task execution time on generated image cards (e.g., "2.3s")
- Calculated from History API's `execution_start` and
`execution_success` messages
- Displayed at MediaAssetCard's duration chip location

### 4. Gallery Feature
- Full-screen gallery mode on image click
- Navigate between images with keyboard arrows
- Exit gallery with ESC key
- Reuses ResultGallery component from QueueSidebarTab

### 5. Development Mode Only
- Excluded from production builds using `import.meta.env.DEV` condition
- Feature in development, scheduled for official release after
stabilization

## 🛠️ Technical Changes

### New Files Added
- `src/components/sidebar/tabs/AssetsSidebarTab.vue` - Main sidebar tab
component
- `src/composables/sidebarTabs/useAssetsSidebarTab.ts` - Sidebar tab
definition
- `src/composables/useInternalMediaAssets.ts` - Local environment
implementation
- `src/composables/useCloudMediaAssets.ts` - Cloud environment
implementation
- `packages/design-system/src/icons/image-ai-edit.svg` - Icon addition

### Modified Files
- `src/stores/workspace/sidebarTabStore.ts` - Added dev mode only tab
display logic
- `src/platform/assets/components/MediaAssetCard.vue` - Added execution
time display, zoom event
- `src/platform/assets/components/MediaImageTop.vue` - Added image
dimension detection
- `packages/shared-frontend-utils/src/formatUtil.ts` - Added media type
determination utility functions
- `src/locales/en/main.json` - Added translation keys


[media_asset_OSS_cloud.webm](https://github.com/user-attachments/assets/a6ee3b49-19ed-4735-baad-c2ac2da868ef)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-10-29 03:39:16 +00:00

334 lines
7.5 KiB
TypeScript

import type { Meta, StoryObj } from '@storybook/vue3-vite'
import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'
import { useMediaAssetGalleryStore } from '../composables/useMediaAssetGalleryStore'
import type { AssetItem } from '../schemas/assetSchema'
import MediaAssetCard from './MediaAssetCard.vue'
const meta: Meta<typeof MediaAssetCard> = {
title: 'Platform/Assets/MediaAssetCard',
component: MediaAssetCard,
decorators: [
() => ({
components: { ResultGallery },
setup() {
const galleryStore = useMediaAssetGalleryStore()
return { galleryStore }
},
template: `
<div>
<story />
<ResultGallery
v-model:active-index="galleryStore.activeIndex"
:all-gallery-items="galleryStore.items"
/>
</div>
`
})
],
argTypes: {
loading: {
control: 'boolean'
}
}
}
export default meta
type Story = StoryObj<typeof meta>
// Public sample media URLs
const SAMPLE_MEDIA = {
image1: 'https://i.imgur.com/OB0y6MR.jpg',
image2: 'https://i.imgur.com/CzXTtJV.jpg',
image3: 'https://farm9.staticflickr.com/8505/8441256181_4e98d8bff5_z_d.jpg',
video:
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
videoThumbnail:
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg',
audio: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'
}
const sampleAsset: AssetItem = {
id: 'asset-1',
name: 'sample-image.png',
size: 2048576,
created_at: new Date().toISOString(),
preview_url: SAMPLE_MEDIA.image1,
tags: ['input'],
user_metadata: {
duration: 3345,
dimensions: {
width: 1920,
height: 1080
}
}
}
export const ImageAsset: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: sampleAsset,
loading: false
}
}
export const VideoAsset: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: {
...sampleAsset,
id: 'asset-2',
name: 'Big_Buck_Bunny.mp4',
size: 10485760,
preview_url: SAMPLE_MEDIA.videoThumbnail,
user_metadata: {
duration: 13425,
dimensions: {
width: 1280,
height: 720
}
}
}
}
}
export const Model3DAsset: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: {
...sampleAsset,
id: 'asset-3',
name: 'Asset-3d-model.glb',
size: 7340032,
preview_url: '',
user_metadata: {
duration: 18023
}
}
}
}
export const AudioAsset: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: {
...sampleAsset,
id: 'asset-4',
name: 'SoundHelix-Song.mp3',
size: 5242880,
preview_url: SAMPLE_MEDIA.audio,
user_metadata: {
duration: 23180
}
}
}
}
export const LoadingState: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: sampleAsset,
loading: true
}
}
export const LongFileName: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: {
...sampleAsset,
name: 'very-long-file-name-that-should-be-truncated-in-the-ui-to-prevent-overflow.png'
}
}
}
export const SelectedState: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: sampleAsset,
selected: true
}
}
export const WebMVideo: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: {
id: 'asset-webm',
name: 'animated-clip.webm',
size: 3145728,
created_at: new Date().toISOString(),
preview_url: SAMPLE_MEDIA.image1,
tags: ['input'],
user_metadata: {
duration: 620,
dimensions: {
width: 640,
height: 360
}
}
}
}
}
export const GifAnimation: Story = {
decorators: [
() => ({
template: '<div style="max-width: 280px;"><story /></div>'
})
],
args: {
asset: {
id: 'asset-gif',
name: 'animation.gif',
size: 1572864,
created_at: new Date().toISOString(),
preview_url: 'https://media.giphy.com/media/3o7aCTPPm4OHfRLSH6/giphy.gif',
tags: ['input'],
user_metadata: {
duration: 1345,
dimensions: {
width: 480,
height: 270
}
}
}
}
}
export const GridLayout: Story = {
render: () => ({
components: { MediaAssetCard },
setup() {
const assets: AssetItem[] = [
{
id: 'grid-1',
name: 'image-file.jpg',
size: 2097152,
created_at: new Date().toISOString(),
preview_url: SAMPLE_MEDIA.image1,
tags: ['input'],
user_metadata: {
duration: 4500,
dimensions: { width: 1920, height: 1080 }
}
},
{
id: 'grid-2',
name: 'image-file.jpg',
size: 2097152,
created_at: new Date().toISOString(),
preview_url: SAMPLE_MEDIA.image2,
tags: ['input'],
user_metadata: {
duration: 4500,
dimensions: { width: 1920, height: 1080 }
}
},
{
id: 'grid-3',
name: 'video-file.mp4',
size: 10485760,
created_at: new Date().toISOString(),
preview_url: SAMPLE_MEDIA.videoThumbnail,
tags: ['input'],
user_metadata: {
duration: 13425,
dimensions: { width: 1280, height: 720 }
}
},
{
id: 'grid-4',
name: 'audio-file.mp3',
size: 5242880,
created_at: new Date().toISOString(),
preview_url: SAMPLE_MEDIA.audio,
tags: ['input'],
user_metadata: {
duration: 180
}
},
{
id: 'grid-5',
name: 'animation.gif',
size: 3145728,
created_at: new Date().toISOString(),
preview_url:
'https://media.giphy.com/media/l0HlNaQ6gWfllcjDO/giphy.gif',
tags: ['input'],
user_metadata: {
duration: 1345,
dimensions: { width: 480, height: 360 }
}
},
{
id: 'grid-6',
name: 'Asset-3d-model.glb',
size: 7340032,
preview_url: '',
created_at: new Date().toISOString(),
tags: ['input'],
user_metadata: {
duration: 18023
}
},
{
id: 'grid-7',
name: 'image-file.jpg',
size: 2097152,
created_at: new Date().toISOString(),
preview_url: SAMPLE_MEDIA.image3,
tags: ['input'],
user_metadata: {
duration: 4500,
dimensions: { width: 1920, height: 1080 }
}
}
]
return { assets }
},
template: `
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; padding: 16px;">
<MediaAssetCard
v-for="asset in assets"
:key="asset.id"
:asset="asset"
/>
</div>
`
})
}