fix(review): address audit findings before merge
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled

This commit is contained in:
Tom Boullay
2026-05-29 01:23:08 +02:00
parent 4728690a11
commit 093ffd726d
45 changed files with 823 additions and 785 deletions
+54 -14
View File
@@ -18,6 +18,7 @@ import {
Unlock,
X,
} from "lucide-react";
import { useState } from "react";
import { EditorCinematicManifestPanel } from "@/components/editor/EditorCinematicManifestPanel";
import { EditorDialogueManifestPanel } from "@/components/editor/EditorDialogueManifestPanel";
import { EditorSrtPanel } from "@/components/editor/EditorSrtPanel";
@@ -102,6 +103,52 @@ function EditorPanelGroup({
);
}
interface EditorScaleFieldProps {
axis: 0 | 1 | 2;
label: string;
value: number;
onCommit: (axis: 0 | 1 | 2, value: number) => void;
}
function EditorScaleField({
axis,
label,
onCommit,
value,
}: EditorScaleFieldProps): React.JSX.Element {
const [draftValue, setDraftValue] = useState(() =>
String(Number(value.toFixed(4))),
);
const commitDraftValue = (): void => {
const nextValue = Number(draftValue);
if (!draftValue.trim() || Number.isNaN(nextValue)) {
setDraftValue(String(Number(value.toFixed(4))));
return;
}
onCommit(axis, nextValue);
};
return (
<label>
<span>{label}</span>
<input
type="number"
step="0.01"
value={draftValue}
onBlur={commitDraftValue}
onChange={(event) => setDraftValue(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.currentTarget.blur();
}
}}
/>
</label>
);
}
export function EditorControls({
transformMode,
onTransformModeChange,
@@ -303,20 +350,13 @@ export function EditorControls({
{selectedNodeScale ? (
<div className="editor-scale-fields">
{selectedNodeScale.map((value, axis) => (
<label key={axis}>
<span>{["X", "Y", "Z"][axis]}</span>
<input
type="number"
step="0.01"
value={Number(value.toFixed(4))}
onChange={(event) =>
onSelectedScaleChange(
axis as 0 | 1 | 2,
Number(event.target.value),
)
}
/>
</label>
<EditorScaleField
key={`${axis}:${value}`}
axis={axis as 0 | 1 | 2}
label={["X", "Y", "Z"][axis] ?? "?"}
value={value}
onCommit={onSelectedScaleChange}
/>
))}
</div>
) : null}
@@ -12,6 +12,17 @@ import type { MapNode, TransformMode, SceneData } from "@/types/editor/editor";
const EDITOR_CAMERA_HOME_POSITION = new THREE.Vector3(0, 50, 100);
const EDITOR_CAMERA_HOME_TARGET = new THREE.Vector3(0, 0, 0);
function isEditableShortcutTarget(target: EventTarget | null): boolean {
if (!(target instanceof HTMLElement)) return false;
return (
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
target instanceof HTMLSelectElement ||
target.isContentEditable
);
}
export interface EditorCinematicPreviewRequest {
id: string;
cinematic: CinematicDefinition;
@@ -148,6 +159,8 @@ export function EditorScene({
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (isEditableShortcutTarget(e.target)) return;
if (e.ctrlKey || e.metaKey) {
if (e.key === "z" || e.key === "Z") {
e.preventDefault();
@@ -3,7 +3,7 @@ import { Component, useEffect, useMemo, useRef } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useGLTF } from "@react-three/drei";
import * as THREE from "three";
import { clone } from "three/addons/utils/SkeletonUtils.js";
import { SkeletonUtils } from "three-stdlib";
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
import {
useHandTrackingGloveStatus,
@@ -255,7 +255,7 @@ function HandTrackingGloveModel({
throw new Error(`Missing glove root node ${config.rootNodeName}`);
}
const clonedRootNode = clone(rootNode);
const clonedRootNode = SkeletonUtils.clone(rootNode);
clonedRootNode.visible = false;
return clonedRootNode;
+1 -1
View File
@@ -42,7 +42,7 @@ export function SimpleModel({
rotation,
scale,
});
const model = useClonedObject(scene);
const model = useClonedObject(scene, { cloneResources: true });
useEffect(() => {
applyShadowSettings(model, castShadow, receiveShadow);
@@ -0,0 +1,17 @@
import { useGLTF } from "@react-three/drei";
import {
MergedStaticMapModel,
type MergedStaticMapModelProps,
} from "@/components/three/world/MergedStaticMapModel";
const LA_FABRIK_MODEL_PATH = "/models/lafabrik/model.gltf";
type LaFabrikMapModelProps = Omit<MergedStaticMapModelProps, "modelPath">;
export function LaFabrikMapModel(
props: LaFabrikMapModelProps,
): React.JSX.Element {
return <MergedStaticMapModel modelPath={LA_FABRIK_MODEL_PATH} {...props} />;
}
useGLTF.preload(LA_FABRIK_MODEL_PATH);
@@ -1,15 +0,0 @@
import { useGLTF } from "@react-three/drei";
import {
MergedStaticMapModel,
type MergedStaticMapModelProps,
} from "@/components/three/world/MergedStaticMapModel";
const LAFABRIK_MODEL_PATH = "/models/lafabrik/model.gltf";
type LafabrikModelProps = Omit<MergedStaticMapModelProps, "modelPath">;
export function LafabrikModel(props: LafabrikModelProps): React.JSX.Element {
return <MergedStaticMapModel modelPath={LAFABRIK_MODEL_PATH} {...props} />;
}
useGLTF.preload(LAFABRIK_MODEL_PATH);
@@ -2,7 +2,7 @@ import { useEffect, useRef } from "react";
import { useGLTF } from "@react-three/drei";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js";
import { mergeBufferGeometries } from "three-stdlib";
import type { Vector3Tuple } from "@/types/three/three";
import { optimizeGLTFSceneTextures } from "@/utils/three/optimizeGLTFScene";
@@ -102,7 +102,7 @@ function createMergedMeshes(scene: THREE.Group): MergedMeshData[] {
};
}
const geometry = mergeGeometries(group.geometries, false);
const geometry = mergeBufferGeometries(group.geometries, false);
for (const sourceGeometry of group.geometries) {
sourceGeometry.dispose();
+6 -1
View File
@@ -5,6 +5,7 @@ import * as THREE from "three";
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
interface SkyModelProps {
fallbackModelScale?: number | undefined;
fallbackModelPath?: string | undefined;
modelPath: string;
fallbackColor?: string | undefined;
@@ -53,6 +54,7 @@ class SkyModelErrorBoundary extends Component<
export function SkyModel({
fallbackColor,
fallbackModelScale = SKY_MODEL_SCALE,
fallbackModelPath,
modelPath,
scale = SKY_MODEL_SCALE,
@@ -62,7 +64,10 @@ export function SkyModel({
) : null;
const fallback = fallbackModelPath ? (
<SkyModelErrorBoundary key={fallbackModelPath} fallback={colorFallback}>
<SkyModelContent modelPath={fallbackModelPath} scale={scale} />
<SkyModelContent
modelPath={fallbackModelPath}
scale={fallbackModelScale}
/>
</SkyModelErrorBoundary>
) : (
colorFallback