/* Inspector / Properties — context panel for the current selection. */ import { Fragment, type ReactNode } from "react"; import { Icon } from "./Icon"; import { cap, fmt, shade } from "../state/util"; import type { EpisodeDocument, Selection } from "../state/types"; const STANDARD_CLIP_IDS = [ "idle", "talk", "gesture", "point", "nod", "wave", "walk", "run", "react", "sit", "shrug", "cross_arms", "salute", "shake_head" ]; function Field({ label, icon, value, mono, act, chev }: { label: string; icon?: string; value: ReactNode; mono?: boolean; act?: boolean; chev?: boolean; }) { return (
{icon && } {label}
{value} {chev && ( )}
); } function Sec({ children }: { children: ReactNode }) { return (
{children}
); } export interface InspectorProps { data: EpisodeDocument; sel: Selection | null; } export function Inspector({ data, sel }: InspectorProps) { if (!sel) { return (
Inspector
Nothing selected.
Select a shot, character, set, or prop — or describe a scene in the Director Console.
); } const typeLabel = ({ shot: "Shot", cast: "Character", set: "Set", prop: "Prop" } as Record)[sel.type] || "—"; let body: ReactNode = null; if (sel.type === "shot") { const s = data.shots.find((x) => x.id === sel.id) || data.shots[0]; const idx = data.shots.indexOf(s) + 1; const beats = data.beats.filter((b) => b.shot === s.id); body = (
{"SHOT " + String(idx).padStart(2, "0")}
{s.name}
{s.cam}
Cast in shot
{s.who.map((id) => { const c = data.cast.find((x) => x.id === id); if (!c) return null; return (
{c.glyph} {c.name}
); })}
{beats.length + " beat" + (beats.length === 1 ? "" : "s") + " · director plan"} {beats.length ? (
{beats.map((b) => { const speaker = data.cast.find((x) => x.id === b.who); const listener = data.cast.find((x) => x.id === b.listener); return (
“{cap(b.text)}”
{b.camera} {(speaker?.name ?? b.who)} · {b.speakingIntent} {b.listener && ( {(listener?.name ?? b.listener)} · {b.listenerIntent} )}
); })}
) : (
No dialogue beats yet — direct one from the console.
)} ); } else if (sel.type === "cast") { const c = data.cast.find((x) => x.id === sel.id) || data.cast[0]; const appears = data.shots.filter((s) => s.who.includes(c.id)).length; body = (
{c.glyph}
{c.name}
{c.kind}
{c.color} } />
{STANDARD_CLIP_IDS.map((id) => ( ))} } />
} />
{c.gradeAwareIntent ?? "—"} } />
{c.name + " is rigged and assignable to any beat. Type \"cast\" commands in the console to re-pose or re-cast."}
); } else { const list = sel.type === "set" ? data.sets : data.props; const o = list.find((x) => x.id === sel.id) || list[0]; body = (
{o.name}
{typeLabel + " · " + o.meta}
Linked into the working document. Re-light or swap from the console with “set …”.
); } return (
Inspector {typeLabel}
{body}
); }