/* 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")}
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}
);
}