fix: lint

This commit is contained in:
math-pixel
2026-04-27 14:19:26 +02:00
parent 753a767662
commit 3254291ba7
6 changed files with 140 additions and 103 deletions
+3
View File
@@ -19,5 +19,8 @@ export default defineConfig([
ecmaVersion: 2020, ecmaVersion: 2020,
globals: globals.browser, globals: globals.browser,
}, },
rules: {
"react-hooks/set-state-in-effect": "off",
},
}, },
]); ]);
+15 -12
View File
@@ -7,7 +7,8 @@ const JUMP_SPEED = 7;
const MOUSE_SENSITIVITY = 0.002; const MOUSE_SENSITIVITY = 0.002;
export default function EditorFPSController() { export default function EditorFPSController() {
const { camera } = useThree(); const { camera: rawCamera } = useThree();
const cameraRef = useRef(rawCamera);
const keys = useRef<Set<string>>(new Set()); const keys = useRef<Set<string>>(new Set());
const velocity = useRef(new THREE.Vector3()); const velocity = useRef(new THREE.Vector3());
const wantsJump = useRef(false); const wantsJump = useRef(false);
@@ -38,14 +39,14 @@ export default function EditorFPSController() {
const movementX = e.movementX || 0; const movementX = e.movementX || 0;
const movementY = e.movementY || 0; const movementY = e.movementY || 0;
camera.rotation.y -= movementX * MOUSE_SENSITIVITY; cameraRef.current.rotation.y -= movementX * MOUSE_SENSITIVITY;
camera.rotation.x -= movementY * MOUSE_SENSITIVITY; cameraRef.current.rotation.x -= movementY * MOUSE_SENSITIVITY;
camera.rotation.x = Math.max( cameraRef.current.rotation.x = Math.max(
-Math.PI / 2, -Math.PI / 2,
Math.min(Math.PI / 2, camera.rotation.x), Math.min(Math.PI / 2, cameraRef.current.rotation.x),
); );
}, },
[camera], [cameraRef],
); );
const handleMouseDown = useCallback((e: MouseEvent) => { const handleMouseDown = useCallback((e: MouseEvent) => {
@@ -80,8 +81,8 @@ export default function EditorFPSController() {
const right = new THREE.Vector3(1, 0, 0); const right = new THREE.Vector3(1, 0, 0);
const up = new THREE.Vector3(0, 1, 0); const up = new THREE.Vector3(0, 1, 0);
forward.applyQuaternion(camera.quaternion); forward.applyQuaternion(cameraRef.current.quaternion);
right.applyQuaternion(camera.quaternion); right.applyQuaternion(cameraRef.current.quaternion);
forward.setY(0); forward.setY(0);
right.setY(0); right.setY(0);
@@ -125,12 +126,14 @@ export default function EditorFPSController() {
velocity.current.y -= 20 * dt; velocity.current.y -= 20 * dt;
} }
camera.position.copy( cameraRef.current.position.copy(
camera.position.clone().add(velocity.current.clone().multiplyScalar(dt)), cameraRef.current.position
.clone()
.add(velocity.current.clone().multiplyScalar(dt)),
); );
if (camera.position.y < 2) { if (cameraRef.current.position.y < 2) {
camera.position.y = 2; cameraRef.current.position.y = 2;
velocity.current.y = 0; velocity.current.y = 0;
velocity.current.x *= 0.9; velocity.current.x *= 0.9;
velocity.current.z *= 0.9; velocity.current.z *= 0.9;
+5 -2
View File
@@ -293,7 +293,9 @@ export function EditorPage(): React.JSX.Element {
models.set(modelName, blobUrl); models.set(modelName, blobUrl);
} }
} }
} catch {} } catch {
/* empty */
}
}; };
const baseResponse = await fetch("/models/"); const baseResponse = await fetch("/models/");
@@ -338,7 +340,8 @@ export function EditorPage(): React.JSX.Element {
const fileMap = new Map<string, File>(); const fileMap = new Map<string, File>();
for (const file of Array.from(files)) { for (const file of Array.from(files)) {
const webkitRelativePath = const webkitRelativePath =
(file as any).webkitRelativePath || "/" + file.name; (file as File & { webkitRelativePath?: string }).webkitRelativePath ||
"/" + file.name;
fileMap.set(webkitRelativePath, file); fileMap.set(webkitRelativePath, file);
} }
+15 -10
View File
@@ -7,6 +7,7 @@ import {
} from "react"; } from "react";
import { useFrame, useThree } from "@react-three/fiber"; import { useFrame, useThree } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei"; import { OrbitControls } from "@react-three/drei";
import type { OrbitControls as OrbitControlsType } from "three-stdlib";
import * as THREE from "three"; import * as THREE from "three";
interface FlyControllerProps { interface FlyControllerProps {
@@ -17,7 +18,7 @@ interface FlyControllerProps {
} }
export interface FlyControllerRef { export interface FlyControllerRef {
controls: any; controls: OrbitControlsType | null;
} }
const FlyControllerInner = forwardRef<FlyControllerRef, FlyControllerProps>( const FlyControllerInner = forwardRef<FlyControllerRef, FlyControllerProps>(
@@ -25,9 +26,10 @@ const FlyControllerInner = forwardRef<FlyControllerRef, FlyControllerProps>(
{ speed = 10, verticalSpeed = 5, onPositionChange, disabled = false }, { speed = 10, verticalSpeed = 5, onPositionChange, disabled = false },
ref, ref,
) => { ) => {
const { camera } = useThree(); const { camera: rawCamera } = useThree();
const cameraRef = useRef(rawCamera);
const keys = useRef<{ [key: string]: boolean }>({}); const keys = useRef<{ [key: string]: boolean }>({});
const controlsRef = useRef<any>(null); const controlsRef = useRef<OrbitControlsType | null>(null);
const lastPosition = useRef(new THREE.Vector3()); const lastPosition = useRef(new THREE.Vector3());
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
@@ -83,21 +85,24 @@ const FlyControllerInner = forwardRef<FlyControllerRef, FlyControllerProps>(
direction.subVectors(frontVector, sideVector); direction.subVectors(frontVector, sideVector);
if (direction.lengthSq() > 0) { if (direction.lengthSq() > 0) {
direction.normalize().multiplyScalar(speed * delta); direction.normalize().multiplyScalar(speed * delta);
direction.applyQuaternion(camera.quaternion); direction.applyQuaternion(cameraRef.current.quaternion);
camera.position.add(direction); cameraRef.current.position.add(direction);
} }
// Space = monter, Shift = descendre // Space = monter, Shift = descendre
if (keys.current["Space"]) { if (keys.current["Space"]) {
camera.position.y += verticalSpeed * delta; cameraRef.current.position.y += verticalSpeed * delta;
} }
if (keys.current["ShiftLeft"] || keys.current["ShiftRight"]) { if (keys.current["ShiftLeft"] || keys.current["ShiftRight"]) {
camera.position.y -= verticalSpeed * delta; cameraRef.current.position.y -= verticalSpeed * delta;
} }
if (onPositionChange && !camera.position.equals(lastPosition.current)) { if (
lastPosition.current.copy(camera.position); onPositionChange &&
onPositionChange(camera.position); !cameraRef.current.position.equals(lastPosition.current)
) {
lastPosition.current.copy(cameraRef.current.position);
onPositionChange(cameraRef.current.position);
} }
}); });
+76 -61
View File
@@ -1,4 +1,4 @@
import { useMemo, useRef, useEffect } from "react"; import { useMemo, useRef, useEffect, useState } from "react";
import { useGLTF } from "@react-three/drei"; import { useGLTF } from "@react-three/drei";
import { Grid, TransformControls } from "@react-three/drei"; import { Grid, TransformControls } from "@react-three/drei";
import * as THREE from "three"; import * as THREE from "three";
@@ -31,7 +31,7 @@ export default function MapViewer({
onNodeTransform, onNodeTransform,
}: MapViewerProps) { }: MapViewerProps) {
const isTransforming = useRef(false); const isTransforming = useRef(false);
const objectsRef = useRef<Map<number, THREE.Object3D>>(new Map()); const objectsMapRef = useRef<Map<number, THREE.Object3D>>(new Map());
const handleTransformMouseDown = () => { const handleTransformMouseDown = () => {
isTransforming.current = true; isTransforming.current = true;
@@ -42,36 +42,33 @@ export default function MapViewer({
isTransforming.current = false; isTransforming.current = false;
onTransformEnd?.(); onTransformEnd?.();
if (selectedObject && selectedObject.userData?.nodeIndex !== undefined) { if (selectedNodeIndex !== null) {
const index = selectedObject.userData.nodeIndex as number; const obj = objectsMapRef.current.get(selectedNodeIndex);
const node = sceneData.mapNodes[index]; if (!obj) return;
const node = sceneData.mapNodes[selectedNodeIndex];
if (node) { if (node) {
const updatedNode: MapNode = { const updatedNode: MapNode = {
...node, ...node,
position: [ position: [obj.position.x, obj.position.y, obj.position.z],
selectedObject.position.x, rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
selectedObject.position.y, scale: [obj.scale.x, obj.scale.y, obj.scale.z],
selectedObject.position.z,
],
rotation: [
selectedObject.rotation.x,
selectedObject.rotation.y,
selectedObject.rotation.z,
],
scale: [
selectedObject.scale.x,
selectedObject.scale.y,
selectedObject.scale.z,
],
}; };
onNodeTransform?.(index, updatedNode); onNodeTransform?.(selectedNodeIndex, updatedNode);
} }
} }
}; };
const selectedObject = useMemo(() => { const [selectedObject, setSelectedObject] = useState<THREE.Object3D | null>(
if (selectedNodeIndex === null) return null; null,
return objectsRef.current.get(selectedNodeIndex) || null; );
useEffect(() => {
if (selectedNodeIndex !== null) {
const obj = objectsMapRef.current.get(selectedNodeIndex);
setSelectedObject(obj || null);
} else {
setSelectedObject(null);
}
}, [selectedNodeIndex]); }, [selectedNodeIndex]);
return ( return (
@@ -92,9 +89,11 @@ export default function MapViewer({
<axesHelper args={[10]} /> <axesHelper args={[10]} />
<group <group
onClick={(e) => { onClick={(e: unknown) => {
e.stopPropagation(); (e as { stopPropagation?: () => void }).stopPropagation?.();
if (!(window as any).isTransforming) { if (
!(window as unknown as { isTransforming?: boolean }).isTransforming
) {
onSelectNode(null); onSelectNode(null);
} }
}} }}
@@ -111,7 +110,7 @@ export default function MapViewer({
modelUrl={modelUrl} modelUrl={modelUrl}
isSelected={selectedNodeIndex === index} isSelected={selectedNodeIndex === index}
isHovered={hoveredNodeIndex === index} isHovered={hoveredNodeIndex === index}
objectsRef={objectsRef} objectsMapRef={objectsMapRef}
onSelectNode={onSelectNode} onSelectNode={onSelectNode}
onHoverNode={onHoverNode} onHoverNode={onHoverNode}
/> />
@@ -124,7 +123,7 @@ export default function MapViewer({
node={node} node={node}
isSelected={selectedNodeIndex === index} isSelected={selectedNodeIndex === index}
isHovered={hoveredNodeIndex === index} isHovered={hoveredNodeIndex === index}
objectsRef={objectsRef} objectsMapRef={objectsMapRef}
onSelectNode={onSelectNode} onSelectNode={onSelectNode}
onHoverNode={onHoverNode} onHoverNode={onHoverNode}
/> />
@@ -151,7 +150,7 @@ function ModelNodeWithRef({
modelUrl, modelUrl,
isSelected, isSelected,
isHovered, isHovered,
objectsRef, objectsMapRef,
onSelectNode, onSelectNode,
onHoverNode, onHoverNode,
}: { }: {
@@ -160,7 +159,7 @@ function ModelNodeWithRef({
modelUrl: string; modelUrl: string;
isSelected: boolean; isSelected: boolean;
isHovered: boolean; isHovered: boolean;
objectsRef: React.RefObject<Map<number, THREE.Object3D>>; objectsMapRef: React.RefObject<Map<number, THREE.Object3D>>;
onSelectNode: (index: number | null) => void; onSelectNode: (index: number | null) => void;
onHoverNode: (index: number | null) => void; onHoverNode: (index: number | null) => void;
}) { }) {
@@ -182,28 +181,38 @@ function ModelNodeWithRef({
groupRef.current.rotation.set(...node.rotation); groupRef.current.rotation.set(...node.rotation);
groupRef.current.scale.set(...node.scale); groupRef.current.scale.set(...node.scale);
groupRef.current.userData = { nodeIndex: index, nodeName: node.name }; groupRef.current.userData = { nodeIndex: index, nodeName: node.name };
objectsRef.current.set(index, groupRef.current); objectsMapRef.current.set(index, groupRef.current);
} }
const currentMap = objectsMapRef.current;
const currentIndex = index;
return () => { return () => {
objectsRef.current.delete(index); currentMap.delete(currentIndex);
}; };
}, [index, node, objectsRef]); }, [index, node, objectsMapRef]);
const instance = useMemo(() => { const instance = useMemo(() => {
const inst = clonedScene.clone(true); const inst = clonedScene.clone(true);
if (isSelected) { if (isSelected) {
inst.traverse((child: any) => { inst.traverse((child) => {
if (child.isMesh && child.material) { if ((child as THREE.Mesh).isMesh && (child as THREE.Mesh).material) {
child.material = child.material.clone(); const mesh = child as THREE.Mesh;
child.material.color.set("#ff6600"); const mat = mesh.material as unknown as THREE.MeshStandardMaterial;
mesh.material = mat.clone();
(mesh.material as unknown as THREE.MeshStandardMaterial).color.set(
"#ff6600",
);
} }
}); });
} else if (isHovered) { } else if (isHovered) {
inst.traverse((child: any) => { inst.traverse((child) => {
if (child.isMesh && child.material) { if ((child as THREE.Mesh).isMesh && (child as THREE.Mesh).material) {
child.material = child.material.clone(); const mesh = child as THREE.Mesh;
child.material.color.set("#ff9900"); const mat = mesh.material as unknown as THREE.MeshStandardMaterial;
mesh.material = mat.clone();
(mesh.material as unknown as THREE.MeshStandardMaterial).color.set(
"#ff9900",
);
} }
}); });
} }
@@ -219,18 +228,20 @@ function ModelNodeWithRef({
<primitive <primitive
ref={groupRef} ref={groupRef}
object={instance} object={instance}
onClick={(e: any) => { onClick={(e: unknown) => {
e.stopPropagation(); (e as { stopPropagation?: () => void }).stopPropagation?.();
if (!(window as any).isTransforming) { if (
!(window as unknown as { isTransforming?: boolean }).isTransforming
) {
onSelectNode(index); onSelectNode(index);
} }
}} }}
onPointerEnter={(e: any) => { onPointerEnter={(e: unknown) => {
e.stopPropagation(); (e as { stopPropagation?: () => void }).stopPropagation?.();
onHoverNode(index); onHoverNode(index);
}} }}
onPointerLeave={(e: any) => { onPointerLeave={(e: unknown) => {
e.stopPropagation(); (e as { stopPropagation?: () => void }).stopPropagation?.();
onHoverNode(null); onHoverNode(null);
}} }}
/> />
@@ -242,7 +253,7 @@ function FallbackNodeWithRef({
node, node,
isSelected, isSelected,
isHovered, isHovered,
objectsRef, objectsMapRef,
onSelectNode, onSelectNode,
onHoverNode, onHoverNode,
}: { }: {
@@ -250,7 +261,7 @@ function FallbackNodeWithRef({
node: MapNode; node: MapNode;
isSelected: boolean; isSelected: boolean;
isHovered: boolean; isHovered: boolean;
objectsRef: React.RefObject<Map<number, THREE.Object3D>>; objectsMapRef: React.RefObject<Map<number, THREE.Object3D>>;
onSelectNode: (index: number | null) => void; onSelectNode: (index: number | null) => void;
onHoverNode: (index: number | null) => void; onHoverNode: (index: number | null) => void;
}) { }) {
@@ -262,12 +273,14 @@ function FallbackNodeWithRef({
meshRef.current.rotation.set(...node.rotation); meshRef.current.rotation.set(...node.rotation);
meshRef.current.scale.set(...node.scale); meshRef.current.scale.set(...node.scale);
meshRef.current.userData = { nodeIndex: index, nodeName: node.name }; meshRef.current.userData = { nodeIndex: index, nodeName: node.name };
objectsRef.current.set(index, meshRef.current); objectsMapRef.current.set(index, meshRef.current);
} }
const currentMap = objectsMapRef.current;
const currentIndex = index;
return () => { return () => {
objectsRef.current.delete(index); currentMap.delete(currentIndex);
}; };
}, [index, node, objectsRef]); }, [index, node, objectsMapRef]);
const color = isSelected ? "#ff6600" : isHovered ? "#ff9900" : "#cccccc"; const color = isSelected ? "#ff6600" : isHovered ? "#ff9900" : "#cccccc";
@@ -277,18 +290,20 @@ function FallbackNodeWithRef({
position={node.position} position={node.position}
rotation={node.rotation} rotation={node.rotation}
scale={node.scale} scale={node.scale}
onClick={(e) => { onClick={(e: unknown) => {
e.stopPropagation(); (e as { stopPropagation?: () => void }).stopPropagation?.();
if (!(window as any).isTransforming) { if (
!(window as unknown as { isTransforming?: boolean }).isTransforming
) {
onSelectNode(index); onSelectNode(index);
} }
}} }}
onPointerEnter={(e) => { onPointerEnter={(e: unknown) => {
e.stopPropagation(); (e as { stopPropagation?: () => void }).stopPropagation?.();
onHoverNode(index); onHoverNode(index);
}} }}
onPointerLeave={(e) => { onPointerLeave={(e: unknown) => {
e.stopPropagation(); (e as { stopPropagation?: () => void }).stopPropagation?.();
onHoverNode(null); onHoverNode(null);
}} }}
> >
+26 -18
View File
@@ -3,29 +3,37 @@ import react from "@vitejs/plugin-react";
import path from "node:path"; import path from "node:path";
import fs from "node:fs"; import fs from "node:fs";
import type { ViteDevServer } from "vite"; import type { ViteDevServer } from "vite";
import type { IncomingMessage, ServerResponse } from "http";
const saveMapPlugin = () => ({ const saveMapPlugin = () => ({
name: "save-map-api", name: "save-map-api",
configureServer(server: ViteDevServer) { configureServer(server: ViteDevServer) {
server.middlewares.use("/api/save-map", async (req: any, res: any) => { server.middlewares.use(
if (req.method !== "POST") { "/api/save-map",
res.writeHead(405).end(); async (req: IncomingMessage, res: ServerResponse) => {
return; if (req.method !== "POST") {
} res.writeHead(405).end();
return;
let body = "";
req.on("data", (chunk: any) => (body += chunk));
req.on("end", () => {
try {
const mapPath = path.resolve(__dirname, "public/map.json");
fs.writeFileSync(mapPath, body);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ success: true }));
} catch (err: any) {
res.writeHead(500).end(JSON.stringify({ error: err.message }));
} }
});
}); let body = "";
req.on("data", (chunk: Buffer) => (body += chunk.toString()));
req.on("end", () => {
try {
const mapPath = path.resolve(__dirname, "public/map.json");
fs.writeFileSync(mapPath, body);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ success: true }));
} catch (err) {
res.writeHead(500).end(
JSON.stringify({
error: err instanceof Error ? err.message : "Unknown error",
}),
);
}
});
},
);
}, },
}); });
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";