Files
hyperframes/packages/studio/src/components/MediaPreview.tsx
T
Miguel Ángel dac304ed9f refactor(studio): code quality — 22 findings, dead code removal, App.tsx split (#144)
## Summary

Full code quality review of the studio package, fixing 22 of 25 findings. Removes dead code, extracts modules from App.tsx, fixes accessibility and performance issues.

## Critical fixes (3)

- **`aria-valuenow`** on seek bar now updates imperatively via `liveTime.subscribe` — screen readers previously always reported position 0
- **Speed menu** closes on outside click (was permanently stuck open)
- **RenderQueue auto-scroll** moved from render phase to `useEffect` (was violating React render purity via `queueMicrotask` during render)

## Dead code removed (-331 lines)

| File | Lines | Why dead |
|---|---|---|
| `PreviewPanel.tsx` | 180 | Replaced by NLELayout + NLEPreview |
| `useCodeEditor.ts` | 80 | Exported but never imported |
| `formatTick` alias | 2 | Deprecated, unused |
| `onClipChange` prop | 5 | Declared, never used |
| `trackH` prop | 5 | Declared, never used |
| `editRange*` + updaters in store | 60 | Never read or written |

## App.tsx extraction

| Extracted to | Lines | What |
|---|---|---|
| `components/LintModal.tsx` | 130 | Lint results modal + LintFinding type |
| `components/MediaPreview.tsx` | 75 | Image/video/audio/font file previewer |
| `utils/mediaTypes.ts` | 15 | Shared regex constants (App.tsx and AssetsTab.tsx had diverged copies) |

## Performance fixes

- `useMemo` for `compositions`/`assets` derivation from `fileTree`
- `useMemo` for `buildTree(files)` in FileTree
- Debounced `handleContentChange` PUT (600ms — was firing on every keystroke)
- CompositionsTab iframe hover debounced (300ms — was mounting immediately)
- `VideoFrameThumbnail` re-extracts frame when `src` prop changes

## Not addressed (3 — low priority)

- #6: SystemIcons consolidation (large refactor across many files)
- #16-17: Overlay dismiss pattern standardization
- #18: Inline SVG → Phosphor replacement (gradual, per-PR)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-31 04:21:12 +02:00

80 lines
2.7 KiB
TypeScript

import { IMAGE_EXT, VIDEO_EXT, AUDIO_EXT } from "../utils/mediaTypes";
export function MediaPreview({ projectId, filePath }: { projectId: string; filePath: string }) {
const serveUrl = `/api/projects/${projectId}/preview/${filePath}`;
const name = filePath.split("/").pop() ?? filePath;
if (IMAGE_EXT.test(filePath)) {
return (
<div className="flex flex-col items-center justify-center h-full p-4 bg-neutral-950">
<img
src={serveUrl}
alt={name}
className="max-w-full max-h-[70%] object-contain rounded border border-neutral-800"
/>
<span className="mt-3 text-[11px] text-neutral-500 font-mono">{filePath}</span>
</div>
);
}
if (VIDEO_EXT.test(filePath)) {
return (
<div className="flex flex-col items-center justify-center h-full p-4 bg-neutral-950">
<video
src={serveUrl}
controls
className="max-w-full max-h-[70%] rounded border border-neutral-800"
/>
<span className="mt-3 text-[11px] text-neutral-500 font-mono">{filePath}</span>
</div>
);
}
if (AUDIO_EXT.test(filePath)) {
return (
<div className="flex flex-col items-center justify-center h-full p-4 bg-neutral-950 gap-3">
<svg
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
className="text-neutral-600"
>
<path d="M9 18V5l12-2v13" strokeLinecap="round" strokeLinejoin="round" />
<circle cx="6" cy="18" r="3" />
<circle cx="18" cy="16" r="3" />
</svg>
<audio src={serveUrl} controls className="w-full max-w-[280px]" />
<span className="text-[11px] text-neutral-500 font-mono">{filePath}</span>
</div>
);
}
// Fonts and other binary — show info instead of binary dump
return (
<div className="flex flex-col items-center justify-center h-full p-4 bg-neutral-950 gap-2">
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
className="text-neutral-600"
>
<path
d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"
strokeLinecap="round"
strokeLinejoin="round"
/>
<polyline points="14 2 14 8 20 8" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<span className="text-sm text-neutral-400 font-medium">{name}</span>
<span className="text-[11px] text-neutral-600 font-mono">{filePath}</span>
<span className="text-[10px] text-neutral-600">Binary file preview not available</span>
</div>
);
}