Merge remote map editor updates

This commit is contained in:
Tom Boullay
2026-05-27 22:24:59 +02:00
20 changed files with 718 additions and 209 deletions
+75 -5
View File
@@ -13,6 +13,7 @@ import {
RotateCw,
Save,
Trash2,
ScanSearch,
Undo2,
Unlock,
X,
@@ -21,17 +22,19 @@ import { EditorCinematicManifestPanel } from "@/components/editor/EditorCinemati
import { EditorDialogueManifestPanel } from "@/components/editor/EditorDialogueManifestPanel";
import { EditorSrtPanel } from "@/components/editor/EditorSrtPanel";
import type { CinematicDefinition } from "@/types/cinematics/cinematics";
import type { EditableMapNode, TransformMode } from "@/types/editor/editor";
import type { MapNode, TransformMode } from "@/types/editor/editor";
import type { Vector3Tuple } from "@/types/three/three";
interface EditorControlsProps {
transformMode: TransformMode;
onTransformModeChange: (mode: TransformMode) => void;
selectedNodeIndex: number | null;
mapNodes: EditableMapNode[];
mapNodes: MapNode[];
nodesCount: number;
selectedNodeName: string | null;
selectedNodeScale: Vector3Tuple | null;
lockTerrainSelection: boolean;
onLockTerrainSelectionChange: (locked: boolean) => void;
isSelectionLocked: boolean;
onSelectionLockToggle: () => void;
onClearSelection: () => void;
@@ -46,6 +49,8 @@ interface EditorControlsProps {
redoCount: number;
onUndo: () => void;
onRedo: () => void;
cameraActionLabel: string;
onCameraAction: () => void;
onExportJson: () => void;
onSaveToServer?: (() => void | Promise<void>) | undefined;
onPlayerMode?: (() => void) | undefined;
@@ -102,6 +107,8 @@ export function EditorControls({
nodesCount,
selectedNodeName,
selectedNodeScale,
lockTerrainSelection,
onLockTerrainSelectionChange,
isSelectionLocked,
onSelectionLockToggle,
onClearSelection,
@@ -116,6 +123,8 @@ export function EditorControls({
redoCount,
onUndo,
onRedo,
cameraActionLabel,
onCameraAction,
onExportJson,
onSaveToServer,
onPlayerMode,
@@ -124,6 +133,9 @@ export function EditorControls({
}: 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 (
<>
@@ -174,7 +186,10 @@ export function EditorControls({
aria-pressed={transformMode === mode}
>
<Icon size={16} aria-hidden="true" />
<span>{label}</span>
<span className="editor-transform-label">
<span>{label}</span>
<small>{transformValues[mode]}</small>
</span>
<kbd>{shortcut}</kbd>
</button>
))}
@@ -339,6 +354,25 @@ export function EditorControls({
{viewModeLabel}
</button>
)}
<button className="editor-action-button" onClick={onCameraAction}>
<ScanSearch size={16} aria-hidden="true" />
{cameraActionLabel}
</button>
<label className="editor-checkbox-row">
<input
type="checkbox"
checked={lockTerrainSelection}
onChange={(event) =>
onLockTerrainSelectionChange(event.currentTarget.checked)
}
/>
<span>
<strong>Lock terrain</strong>
<small>Keep terrain visible but ignore terrain clicks</small>
</span>
</label>
</section>
<section
@@ -411,6 +445,42 @@ export function EditorControls({
);
}
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<TransformMode, string> {
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;
@@ -423,7 +493,7 @@ interface JsonPreview {
}
function getJsonPreview(
mapNodes: EditableMapNode[],
mapNodes: MapNode[],
selectedNodeIndex: number | null,
): JsonPreview {
const { lines, ranges } = formatMapNodesWithRanges(mapNodes);
@@ -452,7 +522,7 @@ function getJsonPreview(
};
}
function formatMapNodesWithRanges(mapNodes: EditableMapNode[]): {
function formatMapNodesWithRanges(mapNodes: MapNode[]): {
lines: string[];
ranges: Array<{ start: number; end: number }>;
} {