mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 10:46:06 +00:00
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)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { memo, useState, useCallback } from "react";
|
||||
import { memo, useState, useCallback, useMemo } from "react";
|
||||
import {
|
||||
FileHtml,
|
||||
FileCss,
|
||||
@@ -7,10 +7,15 @@ import {
|
||||
FileTs,
|
||||
FileTsx,
|
||||
FileTxt,
|
||||
FileMd,
|
||||
FileSvg,
|
||||
FilePng,
|
||||
FileJpg,
|
||||
FileVideo,
|
||||
FileCode,
|
||||
File,
|
||||
FilmStrip,
|
||||
MusicNote,
|
||||
Waveform,
|
||||
TextAa,
|
||||
Image as PhImage,
|
||||
} from "@phosphor-icons/react";
|
||||
import { ChevronDown, ChevronRight } from "../../icons/SystemIcons";
|
||||
@@ -22,27 +27,36 @@ interface FileTreeProps {
|
||||
}
|
||||
|
||||
const SZ = 14;
|
||||
const W = "duotone" as const;
|
||||
|
||||
function FileIcon({ path }: { path: string }) {
|
||||
const ext = path.split(".").pop()?.toLowerCase() ?? "";
|
||||
const d = { size: SZ, weight: "duotone" as const, className: "flex-shrink-0" };
|
||||
if (ext === "html") return <FileHtml {...d} color="#E44D26" />;
|
||||
if (ext === "css") return <FileCss {...d} color="#264DE4" />;
|
||||
if (ext === "js" || ext === "mjs" || ext === "cjs") return <FileJs {...d} color="#F0DB4F" />;
|
||||
if (ext === "jsx") return <FileJsx {...d} color="#61DAFB" />;
|
||||
if (ext === "ts" || ext === "mts") return <FileTs {...d} color="#3178C6" />;
|
||||
if (ext === "tsx") return <FileTsx {...d} color="#3178C6" />;
|
||||
if (ext === "txt" || ext === "md" || ext === "mdx") return <FileTxt {...d} color="#9CA3AF" />;
|
||||
if (ext === "json" || ext === "svg") return <FileCode {...d} color="#22C55E" />;
|
||||
if (ext === "wav" || ext === "mp3" || ext === "ogg" || ext === "m4a")
|
||||
return <MusicNote size={SZ} color="#3CE6AC" className="flex-shrink-0" />;
|
||||
const c = "flex-shrink-0";
|
||||
if (ext === "html") return <FileHtml size={SZ} weight={W} color="#E44D26" className={c} />;
|
||||
if (ext === "css") return <FileCss size={SZ} weight={W} color="#264DE4" className={c} />;
|
||||
if (ext === "js" || ext === "mjs" || ext === "cjs")
|
||||
return <FileJs size={SZ} weight={W} color="#F0DB4F" className={c} />;
|
||||
if (ext === "jsx") return <FileJsx size={SZ} weight={W} color="#61DAFB" className={c} />;
|
||||
if (ext === "ts" || ext === "mts")
|
||||
return <FileTs size={SZ} weight={W} color="#3178C6" className={c} />;
|
||||
if (ext === "tsx") return <FileTsx size={SZ} weight={W} color="#3178C6" className={c} />;
|
||||
if (ext === "json") return <FileCode size={SZ} weight={W} color="#4ADE80" className={c} />;
|
||||
if (ext === "svg") return <FileSvg size={SZ} weight={W} color="#F97316" className={c} />;
|
||||
if (ext === "md" || ext === "mdx")
|
||||
return <FileMd size={SZ} weight={W} color="#9CA3AF" className={c} />;
|
||||
if (ext === "txt") return <FileTxt size={SZ} weight={W} color="#9CA3AF" className={c} />;
|
||||
if (ext === "png") return <FilePng size={SZ} weight={W} color="#22C55E" className={c} />;
|
||||
if (ext === "jpg" || ext === "jpeg")
|
||||
return <FileJpg size={SZ} weight={W} color="#22C55E" className={c} />;
|
||||
if (ext === "webp" || ext === "gif" || ext === "ico")
|
||||
return <PhImage size={SZ} weight={W} color="#22C55E" className={c} />;
|
||||
if (ext === "mp4" || ext === "webm" || ext === "mov")
|
||||
return <FilmStrip size={SZ} color="#A855F7" className="flex-shrink-0" />;
|
||||
if (ext === "png" || ext === "jpg" || ext === "jpeg" || ext === "webp" || ext === "gif")
|
||||
return <PhImage size={SZ} color="#22C55E" className="flex-shrink-0" />;
|
||||
return <FileVideo size={SZ} weight={W} color="#A855F7" className={c} />;
|
||||
if (ext === "mp3" || ext === "wav" || ext === "ogg" || ext === "m4a")
|
||||
return <Waveform size={SZ} weight={W} color="#3CE6AC" className={c} />;
|
||||
if (ext === "woff" || ext === "woff2" || ext === "ttf" || ext === "otf")
|
||||
return <File size={SZ} weight="duotone" color="#6B7280" className="flex-shrink-0" />;
|
||||
return <File size={SZ} weight="duotone" color="#6B7280" className="flex-shrink-0" />;
|
||||
return <TextAa size={SZ} weight={W} color="#6B7280" className={c} />;
|
||||
return <File size={SZ} weight={W} color="#6B7280" className={c} />;
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
@@ -188,8 +202,8 @@ function isActiveInSubtree(node: TreeNode, activeFile: string | null): boolean {
|
||||
}
|
||||
|
||||
export const FileTree = memo(function FileTree({ files, activeFile, onSelectFile }: FileTreeProps) {
|
||||
const tree = buildTree(files);
|
||||
const children = sortChildren(tree.children);
|
||||
const tree = useMemo(() => buildTree(files), [files]);
|
||||
const children = useMemo(() => sortChildren(tree.children), [tree]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full min-h-0">
|
||||
|
||||
@@ -94,7 +94,7 @@ export const PropertyPanel = memo(function PropertyPanel({
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={isPickMode ? onDisablePick : onEnablePick}
|
||||
className={`mt-3 ${isPickMode ? "bg-blue-500/20 text-blue-400 border-blue-500/30" : ""}`}
|
||||
className={`mt-3 ${isPickMode ? "bg-studio-accent/20 text-studio-accent border-studio-accent/30" : ""}`}
|
||||
>
|
||||
{isPickMode ? "Pick mode active..." : "Enable Pick Mode"}
|
||||
</Button>
|
||||
@@ -109,7 +109,7 @@ export const PropertyPanel = memo(function PropertyPanel({
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b border-neutral-800 flex-shrink-0">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<span className="text-2xs font-mono text-blue-400 truncate">{element.selector}</span>
|
||||
<span className="text-2xs font-mono text-studio-accent truncate">{element.selector}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<IconButton
|
||||
@@ -117,7 +117,7 @@ export const PropertyPanel = memo(function PropertyPanel({
|
||||
aria-label={isPickMode ? "Disable pick mode" : "Enable pick mode"}
|
||||
size="sm"
|
||||
onClick={isPickMode ? onDisablePick : onEnablePick}
|
||||
className={isPickMode ? "text-blue-400 bg-blue-500/10" : ""}
|
||||
className={isPickMode ? "text-studio-accent bg-studio-accent/10" : ""}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<X size={11} />}
|
||||
|
||||
Reference in New Issue
Block a user