Add MediaAssetCard presentation components (#5878)

## 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>
This commit is contained in:
Jin Yi
2025-10-12 06:39:04 +09:00
committed by GitHub
parent bb83b0107c
commit 9c0b3c4f7d
27 changed files with 1655 additions and 62 deletions

View File

@@ -0,0 +1,57 @@
<template>
<div
class="relative h-full w-full overflow-hidden rounded bg-black"
@mouseenter="showControls = true"
@mouseleave="showControls = false"
>
<video
ref="videoRef"
:controls="showControls"
preload="none"
:poster="asset.preview_url"
class="relative h-full w-full object-contain"
@click.stop
@play="onVideoPlay"
@pause="onVideoPause"
>
<source :src="asset.src || ''" />
</video>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import type { AssetContext, AssetMeta } from '../schemas/mediaAssetSchema'
const { asset } = defineProps<{
asset: AssetMeta
context: AssetContext
}>()
const emit = defineEmits<{
play: [assetId: string]
videoPlayingStateChanged: [isPlaying: boolean]
videoControlsChanged: [showControls: boolean]
}>()
const videoRef = ref<HTMLVideoElement>()
const showControls = ref(true)
watch(showControls, (controlsVisible) => {
emit('videoControlsChanged', controlsVisible)
})
onMounted(() => {
emit('videoControlsChanged', showControls.value)
})
const onVideoPlay = () => {
showControls.value = true
emit('videoPlayingStateChanged', true)
}
const onVideoPause = () => {
emit('videoPlayingStateChanged', false)
}
</script>