import { Box, Braces, ChevronDown, Download, Expand, Keyboard, Lock, MousePointer2, Move3D, Redo2, RotateCw, Save, ScanSearch, Undo2, Unlock, X, } from "lucide-react"; import { EditorCinematicManifestPanel } from "@/components/editor/EditorCinematicManifestPanel"; import { EditorDialogueManifestPanel } from "@/components/editor/EditorDialogueManifestPanel"; import { EditorSrtPanel } from "@/components/editor/EditorSrtPanel"; import type { CinematicDefinition } from "@/types/cinematics/cinematics"; import type { MapNode, TransformMode } from "@/types/editor/editor"; interface EditorControlsProps { transformMode: TransformMode; onTransformModeChange: (mode: TransformMode) => void; selectedNodeIndex: number | null; mapNodes: MapNode[]; nodesCount: number; selectedNodeName: string | null; lockTerrainSelection: boolean; onLockTerrainSelectionChange: (locked: boolean) => void; isSelectionLocked: boolean; onSelectionLockToggle: () => void; onClearSelection: () => void; undoCount: number; redoCount: number; onUndo: () => void; onRedo: () => void; cameraActionLabel: string; onCameraAction: () => void; onExportJson: () => void; onSaveToServer?: (() => void | Promise) | undefined; onPlayerMode?: (() => void) | undefined; onPreviewCinematic?: ((cinematic: CinematicDefinition) => void) | undefined; isPlayerMode?: boolean; } const TRANSFORM_OPTIONS = [ { mode: "translate", label: "Translate", shortcut: "T", Icon: Move3D }, { mode: "rotate", label: "Rotate", shortcut: "R", Icon: RotateCw }, { mode: "scale", label: "Scale", shortcut: "S", Icon: Expand }, ] as const; const EDITOR_SHORTCUTS = [ ["Click", "Select object"], ["T / R / S", "Transform mode"], ["Ctrl Z / Y", "Undo / redo"], ["Esc", "Deselect"], ["WASD", "Move when locked"], ] as const; interface EditorPanelGroupProps { title: string; summary?: string; defaultOpen?: boolean; children: React.ReactNode; } function EditorPanelGroup({ title, summary, defaultOpen = false, children, }: EditorPanelGroupProps): React.JSX.Element { return (
{title} {summary ? {summary} : null}
{children}
); } export function EditorControls({ transformMode, onTransformModeChange, selectedNodeIndex, mapNodes, nodesCount, selectedNodeName, lockTerrainSelection, onLockTerrainSelectionChange, isSelectionLocked, onSelectionLockToggle, onClearSelection, undoCount, redoCount, onUndo, onRedo, cameraActionLabel, onCameraAction, onExportJson, onSaveToServer, onPlayerMode, onPreviewCinematic, isPlayerMode, }: EditorControlsProps): React.JSX.Element { const viewModeLabel = isPlayerMode ? "View locked" : "Lock view"; const jsonPreview = getJsonPreview(mapNodes, selectedNodeIndex); const selectedNode = selectedNodeIndex !== null ? mapNodes[selectedNodeIndex] : null; const transformValues = getTransformValues(selectedNode ?? null); return ( <> ); } function formatNumber(value: number): string { return Number.isInteger(value) ? String(value) : value.toFixed(2); } function formatVector(values: readonly [number, number, number]): string { return `X ${formatNumber(values[0])} · Y ${formatNumber(values[1])} · Z ${formatNumber(values[2])}`; } function formatRotation(values: readonly [number, number, number]): string { const degrees = values.map((value) => (value * 180) / Math.PI) as [ number, number, number, ]; return `X ${formatNumber(degrees[0])}° · Y ${formatNumber(degrees[1])}° · Z ${formatNumber(degrees[2])}°`; } function getTransformValues( node: MapNode | null, ): Record { if (!node) { return { translate: "No selection", rotate: "No selection", scale: "No selection", }; } return { translate: formatVector(node.position), rotate: formatRotation(node.rotation), scale: formatVector(node.scale), }; } interface JsonPreviewLine { number: number; content: string; isSelected: boolean; } interface JsonPreview { label: string; lines: JsonPreviewLine[]; } function getJsonPreview( mapNodes: MapNode[], selectedNodeIndex: number | null, ): JsonPreview { const { lines, ranges } = formatMapNodesWithRanges(mapNodes); if (selectedNodeIndex === null || !ranges[selectedNodeIndex]) { return { label: `${lines.length} raw lines`, lines: lines.map((content, index) => ({ number: index + 1, content, isSelected: false, })), }; } const range = ranges[selectedNodeIndex]; const selectedLines = lines.slice(range.start - 1, range.end); return { label: `Lines ${range.start}-${range.end}`, lines: selectedLines.map((content, index) => ({ number: range.start + index, content, isSelected: true, })), }; } function formatMapNodesWithRanges(mapNodes: MapNode[]): { lines: string[]; ranges: Array<{ start: number; end: number }>; } { const lines = ["["]; const ranges: Array<{ start: number; end: number }> = []; mapNodes.forEach((node, index) => { const objectLines = JSON.stringify(node, null, 2) .split("\n") .map((line) => ` ${line}`); if (index < mapNodes.length - 1) { objectLines[objectLines.length - 1] += ","; } const start = lines.length + 1; lines.push(...objectLines); ranges.push({ start, end: lines.length }); }); lines.push("]"); return { lines, ranges }; }