Files
av-tools/src/components/MediaBin.tsx

132 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { ImportedMediaAsset } from '../app/application-state';
import {
assetDurationSeconds,
assetHasVideo,
formatBytes,
formatDuration,
} from '../app/application-state';
import { Icon } from './Icon';
export interface MediaBinProps {
assets: readonly ImportedMediaAsset[];
selectedId?: string;
onSelect: (id: string) => void;
onInspect: (id: string) => void;
inspectDisabled?: boolean;
removeDisabled?: boolean;
onRemove: (id: string) => void;
}
export function MediaBin({
assets,
selectedId,
onSelect,
onInspect,
inspectDisabled = false,
removeDisabled = false,
onRemove,
}: MediaBinProps) {
if (assets.length === 0) {
return (
<div className="media-bin__empty">
<Icon name="video" />
<p>Your source files will appear here.</p>
</div>
);
}
return (
<ul className="media-list" aria-label="Imported media">
{assets.map((asset) => {
const hasVideo = assetHasVideo(asset) === true;
const video = asset.probe?.streams.find(
(stream) => stream.type === 'video'
);
const durationSeconds = assetDurationSeconds(asset);
const width = video?.width ?? asset.browserMetadata?.width;
const height = video?.height ?? asset.browserMetadata?.height;
const audioCount =
asset.probe?.streams.filter((stream) => stream.type === 'audio')
.length ?? 0;
return (
<li key={asset.id}>
<button
type="button"
className={[
'media-card',
selectedId === asset.id ? 'media-card--selected' : '',
]
.filter(Boolean)
.join(' ')}
aria-pressed={selectedId === asset.id}
onClick={() => onSelect(asset.id)}
>
<span className="media-card__type">
<Icon name={hasVideo ? 'video' : 'audio'} />
</span>
<span className="media-card__content">
<strong title={asset.file.name}>{asset.file.name}</strong>
<small>
{formatBytes(asset.file.size)}
{durationSeconds !== undefined
? ` · ${formatDuration(durationSeconds)}`
: ''}
</small>
<small className={`phase phase--${asset.phase}`}>
{asset.phase === 'probing'
? `${asset.browserMetadata ? 'Ready to play' : 'File ready'} · Inspecting details…`
: asset.phase === 'ready'
? `${hasVideo ? `${width ?? '?'}×${height ?? '?'}` : 'Audio'} · ${audioCount} audio`
: asset.phase === 'error'
? `${asset.browserMetadata ? 'Ready to play' : 'File ready'} · Details unavailable`
: asset.browserMetadataError
? 'File ready · Basic metadata unavailable'
: asset.browserMetadata
? 'Ready to play · Details on demand'
: 'File ready · Reading basic metadata…'}
</small>
</span>
</button>
{asset.phase !== 'ready' ? (
<button
className="icon-action"
type="button"
aria-label={`${asset.phase === 'error' ? 'Retry detailed inspection for' : 'Inspect details for'} ${asset.file.name}`}
title={
asset.phase === 'error'
? asset.error
: 'Read codecs, streams, chapters and tags'
}
disabled={inspectDisabled || asset.phase === 'probing'}
{...(asset.error
? { 'aria-describedby': `inspection-error-${asset.id}` }
: {})}
onClick={() => onInspect(asset.id)}
>
<Icon name={asset.phase === 'probing' ? 'queue' : 'info'} />
</button>
) : null}
{asset.error ? (
<span
className="visually-hidden"
id={`inspection-error-${asset.id}`}
>
{asset.error}
</span>
) : null}
<button
className="icon-action"
type="button"
aria-label={`Remove ${asset.file.name}`}
disabled={removeDisabled}
onClick={() => onRemove(asset.id)}
>
<Icon name="trash" />
</button>
</li>
);
})}
</ul>
);
}