feat/fix: visuals.jsx — growing migrate flow + deltacast cleanup
This commit is contained in:
parent
29238a339e
commit
5525041901
1 changed files with 16 additions and 25 deletions
|
|
@ -25,10 +25,6 @@ function AssetThumb({ asset, size = 'md' }) {
|
|||
);
|
||||
}
|
||||
|
||||
// Live/recording assets: once the capture sidecar has published a poster
|
||||
// thumbnail (first frame of the recording), show that static frame instead
|
||||
// of the HLS "connecting…" player. Until the poster exists (the brief window
|
||||
// before the first segment is grabbed), fall back to the live HLS preview.
|
||||
if (asset.status === 'live' && asset.id) {
|
||||
if (asset.thumbnail_s3_key || thumbUrl) {
|
||||
const altText = asset.name ? `Thumbnail for ${asset.name}` : 'Live recording thumbnail';
|
||||
|
|
@ -37,7 +33,6 @@ function AssetThumb({ asset, size = 'md' }) {
|
|||
{thumbUrl
|
||||
? <img src={thumbUrl} alt={altText} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
|
||||
: <FauxFrame />}
|
||||
{/* Keep the pulsing LIVE border so it still reads as recording */}
|
||||
<div style={{ position: 'absolute', inset: 0, border: '2px solid var(--live)', pointerEvents: 'none', borderRadius: 'inherit' }} />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -45,6 +40,19 @@ function AssetThumb({ asset, size = 'md' }) {
|
|||
return <LiveThumb assetId={asset.id} aspect={aspect} />;
|
||||
}
|
||||
|
||||
if (asset.status === 'pending_migration') {
|
||||
return (
|
||||
<div className="asset-thumb" style={{ aspectRatio: aspect, position: 'relative', background: 'var(--bg-2)', overflow: 'hidden' }}>
|
||||
<div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
|
||||
alignItems: 'center', justifyContent: 'center', gap: 6,
|
||||
color: 'var(--text-3)', fontSize: 11 }}>
|
||||
<Icon name="upload" size={20} style={{ opacity: 0.5 }} />
|
||||
<span>Awaiting migration</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const altText = asset.name ? `Thumbnail for ${asset.name}` : 'Asset thumbnail';
|
||||
return (
|
||||
<div className="asset-thumb" style={{ background: 'var(--bg-2)', aspectRatio: aspect, overflow: 'hidden', position: 'relative' }}>
|
||||
|
|
@ -55,10 +63,6 @@ function AssetThumb({ asset, size = 'md' }) {
|
|||
);
|
||||
}
|
||||
|
||||
// Muted inline HLS preview for a live/recording asset tile. Attaches hls.js
|
||||
// (or native HLS on Safari) to show the live feed inside the library card.
|
||||
// Shows a "connecting…" spinner while the manifest loads, falls back to a
|
||||
// placeholder with a record icon if hls.js is unavailable or playback fails.
|
||||
function LiveThumb({ assetId, aspect }) {
|
||||
const videoRef = React.useRef(null);
|
||||
const [ready, setReady] = React.useState(false);
|
||||
|
|
@ -129,7 +133,6 @@ function LiveThumb({ assetId, aspect }) {
|
|||
autoPlay
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
|
||||
/>
|
||||
{/* Pulsing red border while connecting or playing, matches LIVE badge colour */}
|
||||
<div style={{ position: 'absolute', inset: 0, border: '2px solid var(--live)', pointerEvents: 'none', borderRadius: 'inherit' }} />
|
||||
{!ready && !failed && (
|
||||
<div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
|
||||
|
|
@ -232,6 +235,7 @@ function StatusDot({ status }) {
|
|||
done: { color: 'var(--success)', pulse: false },
|
||||
failed: { color: 'var(--danger)', pulse: false },
|
||||
stopped: { color: 'var(--text-4)', pulse: false },
|
||||
pending_migration: { color: 'var(--warning)', pulse: false },
|
||||
};
|
||||
const s = map[status] || { color: 'var(--text-3)' };
|
||||
return <span className={'status-dot ' + (s.pulse ? 'pulse' : '')} style={{ background: s.color, boxShadow: '0 0 0 3px ' + s.color + '30' }} />;
|
||||
|
|
@ -250,19 +254,6 @@ function Elapsed({ seconds, live = false }) {
|
|||
return <span className="mono">{String(h).padStart(2,'0')}:{String(m).padStart(2,'0')}:{String(s).padStart(2,'0')}</span>;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// ConfirmModal + useConfirm — in-page replacement for window.confirm().
|
||||
//
|
||||
// Usage in a component:
|
||||
// const [confirm, confirmModal] = useConfirm();
|
||||
// ...
|
||||
// if (!(await confirm({ title: 'Delete user?', message: '…' }))) return;
|
||||
// ...
|
||||
// return (<>{confirmModal} ...rest of UI... </>);
|
||||
//
|
||||
// confirm(opts) returns a Promise<boolean>. Options:
|
||||
// title, message, confirmLabel (default 'Delete'), cancelLabel ('Cancel'),
|
||||
// danger (default true → red confirm button).
|
||||
function ConfirmModal({ title, message, confirmLabel = 'Delete', cancelLabel = 'Cancel', danger = true, onConfirm, onCancel }) {
|
||||
React.useEffect(() => {
|
||||
const onKey = (e) => {
|
||||
|
|
@ -283,7 +274,7 @@ function ConfirmModal({ title, message, confirmLabel = 'Delete', cancelLabel = '
|
|||
<div className="modal-body">
|
||||
{typeof message === 'string'
|
||||
? message.split('\n').map((line, i) => (
|
||||
<div key={i} style={{ fontSize: 13, color: 'var(--text-2)', lineHeight: 1.5 }}>{line || ' '}</div>
|
||||
<div key={i} style={{ fontSize: 13, color: 'var(--text-2)', lineHeight: 1.5 }}>{line || ' '}</div>
|
||||
))
|
||||
: <div style={{ fontSize: 13, color: 'var(--text-2)', lineHeight: 1.5 }}>{message}</div>}
|
||||
</div>
|
||||
|
|
@ -297,7 +288,7 @@ function ConfirmModal({ title, message, confirmLabel = 'Delete', cancelLabel = '
|
|||
}
|
||||
|
||||
function useConfirm() {
|
||||
const [state, setState] = React.useState(null); // { opts, resolve } | null
|
||||
const [state, setState] = React.useState(null);
|
||||
|
||||
const confirm = React.useCallback((opts) => {
|
||||
return new Promise((resolve) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue