chore: code quality audit and lint fixes
🔍 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
🔍 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
- Fix all 63 ESLint errors across codebase - Consolidate MaterialWithTextureSlots type in src/types/three/three.ts - Add CSS custom properties for design tokens - Extract ebike constants to src/data/ebike/ebikeConfig.ts - Add proper TypeScript types for window extensions - Fix React hooks violations (refs during render, setState in effects) - Remove unused exports and redundant CSS - Add type guards for Three.js material handling - Clean up AI slop comments and legacy CSS patterns
This commit is contained in:
@@ -1,7 +1,34 @@
|
||||
import React, { useState, useEffect, useRef, useMemo } from "react";
|
||||
import React, { useState, useRef, useMemo, useCallback } from "react";
|
||||
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
||||
import { MapControls, OrthographicCamera, useGLTF } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
import type { MapControls as MapControlsImpl } from "three-stdlib";
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Types
|
||||
// ----------------------------------------------------------------------------
|
||||
interface WaypointData {
|
||||
id: number;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
connections: number[];
|
||||
}
|
||||
|
||||
interface Bounds {
|
||||
minX: number;
|
||||
maxX: number;
|
||||
minZ: number;
|
||||
maxZ: number;
|
||||
}
|
||||
|
||||
// Extend window for global functions
|
||||
declare global {
|
||||
interface Window {
|
||||
applyAutoBounds?: () => void;
|
||||
downloadMapScreenshot?: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 1. Terrain Scene
|
||||
@@ -24,7 +51,7 @@ function WaypointOverlay({
|
||||
waypoints,
|
||||
visible,
|
||||
}: {
|
||||
waypoints: any[];
|
||||
waypoints: WaypointData[];
|
||||
visible: boolean;
|
||||
}) {
|
||||
if (!visible) return null;
|
||||
@@ -47,54 +74,71 @@ function CameraManager({
|
||||
autoBounds,
|
||||
boundsTextRef,
|
||||
}: {
|
||||
autoBounds: any;
|
||||
autoBounds: Bounds | null;
|
||||
boundsTextRef: React.RefObject<HTMLPreElement | null>;
|
||||
}) {
|
||||
const { camera, gl, scene } = useThree();
|
||||
const controlsRef = useRef<any>(null);
|
||||
const controlsRef = useRef<MapControlsImpl>(null);
|
||||
// Use refs to store mutable camera properties that we need to modify
|
||||
const cameraRef = useRef(camera);
|
||||
|
||||
// Apply Auto-Bounds function
|
||||
useEffect(() => {
|
||||
const applyAutoBounds = () => {
|
||||
if (camera instanceof THREE.OrthographicCamera && autoBounds) {
|
||||
const width = autoBounds.maxX - autoBounds.minX;
|
||||
const height = autoBounds.maxZ - autoBounds.minZ;
|
||||
const centerX = (autoBounds.minX + autoBounds.maxX) / 2;
|
||||
const centerZ = (autoBounds.minZ + autoBounds.maxZ) / 2;
|
||||
// Update cameraRef in an effect to avoid refs during render error
|
||||
React.useEffect(() => {
|
||||
cameraRef.current = camera;
|
||||
}, [camera]);
|
||||
|
||||
camera.position.set(centerX, 200, centerZ);
|
||||
camera.left = -width / 2;
|
||||
camera.right = width / 2;
|
||||
camera.top = height / 2;
|
||||
camera.bottom = -height / 2;
|
||||
camera.zoom = 1;
|
||||
camera.updateProjectionMatrix();
|
||||
// Apply Auto-Bounds function using useCallback to create a stable reference
|
||||
const applyAutoBounds = useCallback(() => {
|
||||
const cam = cameraRef.current;
|
||||
if (cam instanceof THREE.OrthographicCamera && autoBounds) {
|
||||
const width = autoBounds.maxX - autoBounds.minX;
|
||||
const height = autoBounds.maxZ - autoBounds.minZ;
|
||||
const centerX = (autoBounds.minX + autoBounds.maxX) / 2;
|
||||
const centerZ = (autoBounds.minZ + autoBounds.maxZ) / 2;
|
||||
|
||||
if (controlsRef.current) {
|
||||
controlsRef.current.target.set(centerX, 0, centerZ);
|
||||
controlsRef.current.update();
|
||||
}
|
||||
cam.position.set(centerX, 200, centerZ);
|
||||
cam.left = -width / 2;
|
||||
cam.right = width / 2;
|
||||
cam.top = height / 2;
|
||||
cam.bottom = -height / 2;
|
||||
cam.zoom = 1;
|
||||
cam.updateProjectionMatrix();
|
||||
|
||||
if (controlsRef.current) {
|
||||
controlsRef.current.target.set(centerX, 0, centerZ);
|
||||
controlsRef.current.update();
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [autoBounds]);
|
||||
|
||||
(window as any).applyAutoBounds = applyAutoBounds;
|
||||
// Initial apply
|
||||
applyAutoBounds();
|
||||
// Initial apply on autoBounds change (using useFrame to run once after mount)
|
||||
const hasAppliedRef = useRef(false);
|
||||
useFrame(() => {
|
||||
if (!hasAppliedRef.current && autoBounds) {
|
||||
applyAutoBounds();
|
||||
hasAppliedRef.current = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Reset hasApplied when autoBounds changes
|
||||
React.useEffect(() => {
|
||||
hasAppliedRef.current = false;
|
||||
window.applyAutoBounds = applyAutoBounds;
|
||||
return () => {
|
||||
delete (window as any).applyAutoBounds;
|
||||
delete window.applyAutoBounds;
|
||||
};
|
||||
}, [camera, autoBounds]);
|
||||
}, [applyAutoBounds]);
|
||||
|
||||
// Track dynamic bounds without triggering React re-renders!
|
||||
useFrame(() => {
|
||||
if (camera instanceof THREE.OrthographicCamera && boundsTextRef.current) {
|
||||
const width = (camera.right - camera.left) / camera.zoom;
|
||||
const height = (camera.top - camera.bottom) / camera.zoom;
|
||||
const minX = Math.round(camera.position.x - width / 2);
|
||||
const maxX = Math.round(camera.position.x + width / 2);
|
||||
const minZ = Math.round(camera.position.z - height / 2);
|
||||
const maxZ = Math.round(camera.position.z + height / 2);
|
||||
const cam = cameraRef.current;
|
||||
if (cam instanceof THREE.OrthographicCamera && boundsTextRef.current) {
|
||||
const width = (cam.right - cam.left) / cam.zoom;
|
||||
const height = (cam.top - cam.bottom) / cam.zoom;
|
||||
const minX = Math.round(cam.position.x - width / 2);
|
||||
const maxX = Math.round(cam.position.x + width / 2);
|
||||
const minZ = Math.round(cam.position.z - height / 2);
|
||||
const maxZ = Math.round(cam.position.z + height / 2);
|
||||
|
||||
// Direct DOM mutation for 60fps performance (prevents WebGL Context Lost!)
|
||||
boundsTextRef.current.innerText = JSON.stringify(
|
||||
@@ -106,10 +150,10 @@ function CameraManager({
|
||||
});
|
||||
|
||||
// Attach screenshot capture logic
|
||||
useEffect(() => {
|
||||
(window as any).downloadMapScreenshot = () => {
|
||||
React.useEffect(() => {
|
||||
window.downloadMapScreenshot = () => {
|
||||
// Force an immediate render frame to ensure no UI overlays are missing
|
||||
gl.render(scene, camera);
|
||||
gl.render(scene, cameraRef.current);
|
||||
const dataUrl = gl.domElement.toDataURL("image/png");
|
||||
const a = document.createElement("a");
|
||||
a.href = dataUrl;
|
||||
@@ -117,9 +161,9 @@ function CameraManager({
|
||||
a.click();
|
||||
};
|
||||
return () => {
|
||||
delete (window as any).downloadMapScreenshot;
|
||||
delete window.downloadMapScreenshot;
|
||||
};
|
||||
}, [gl, camera, scene]);
|
||||
}, [gl, scene]);
|
||||
|
||||
return (
|
||||
<MapControls ref={controlsRef} enableRotate={false} dampingFactor={0.05} />
|
||||
@@ -130,25 +174,35 @@ function CameraManager({
|
||||
// 4. Main Page Route Component
|
||||
// ----------------------------------------------------------------------------
|
||||
export function BackgroundMapPage() {
|
||||
const [waypoints, setWaypoints] = useState<any[]>([]);
|
||||
const [showWaypoints, setShowWaypoints] = useState(true);
|
||||
const boundsTextRef = useRef<HTMLPreElement>(null);
|
||||
|
||||
// Load road network waypoints to compute perfect GPS bounds
|
||||
useEffect(() => {
|
||||
// Use lazy initialization to avoid setState in useEffect
|
||||
const [waypoints, setWaypoints] = useState<WaypointData[]>(() => {
|
||||
const saved = localStorage.getItem("la-fabrik-waypoints");
|
||||
if (saved) {
|
||||
setWaypoints(JSON.parse(saved));
|
||||
} else {
|
||||
try {
|
||||
return JSON.parse(saved) as WaypointData[];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const [showWaypoints, setShowWaypoints] = useState(true);
|
||||
const boundsTextRef = useRef<HTMLPreElement>(null);
|
||||
const hasFetchedRef = useRef(false);
|
||||
|
||||
// Fetch from network as fallback if localStorage was empty
|
||||
React.useEffect(() => {
|
||||
if (waypoints.length === 0 && !hasFetchedRef.current) {
|
||||
hasFetchedRef.current = true;
|
||||
fetch("/roadNetwork.json")
|
||||
.then((res) => res.json())
|
||||
.then((data) => setWaypoints(data))
|
||||
.then((data: WaypointData[]) => setWaypoints(data))
|
||||
.catch(() => {});
|
||||
}
|
||||
}, []);
|
||||
}, [waypoints.length]); // Include dependency to satisfy linter
|
||||
|
||||
// Compute exact bounds that the EbikeGPSMap will use by default
|
||||
const autoBounds = useMemo(() => {
|
||||
const autoBounds = useMemo((): Bounds | null => {
|
||||
if (waypoints.length === 0) return null;
|
||||
const xs = waypoints.map((w) => w.x);
|
||||
const zs = waypoints.map((w) => w.z);
|
||||
@@ -271,13 +325,12 @@ export function BackgroundMapPage() {
|
||||
transition: "all 0.2s",
|
||||
}}
|
||||
>
|
||||
{showWaypoints ? "👁️ Masquer Waypoints" : "👁️🗨️ Afficher Waypoints"}
|
||||
{showWaypoints ? "Masquer Waypoints" : "Afficher Waypoints"}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
if ((window as any).applyAutoBounds)
|
||||
(window as any).applyAutoBounds();
|
||||
if (window.applyAutoBounds) window.applyAutoBounds();
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -292,13 +345,12 @@ export function BackgroundMapPage() {
|
||||
transition: "all 0.2s",
|
||||
}}
|
||||
>
|
||||
🎯 Cadrage Automatique
|
||||
Cadrage Automatique
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
if ((window as any).downloadMapScreenshot)
|
||||
(window as any).downloadMapScreenshot();
|
||||
if (window.downloadMapScreenshot) window.downloadMapScreenshot();
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -313,7 +365,7 @@ export function BackgroundMapPage() {
|
||||
boxShadow: "0 4px 6px -1px rgba(14, 165, 233, 0.4)",
|
||||
}}
|
||||
>
|
||||
📸 Capturer la carte (.png)
|
||||
Capturer la carte (.png)
|
||||
</button>
|
||||
|
||||
<div
|
||||
|
||||
+23
-43
@@ -53,6 +53,11 @@ import {
|
||||
GAME_SCENE_SKY_MODEL_PATH,
|
||||
GAME_SCENE_SKY_MODEL_SCALE,
|
||||
} from "@/data/world/environmentConfig";
|
||||
import {
|
||||
disposeModelMaterials,
|
||||
MATERIAL_TEXTURE_KEYS,
|
||||
type MaterialWithTextureSlots,
|
||||
} from "@/utils/three/dispose";
|
||||
|
||||
interface GalleryModelProps {
|
||||
model: GalleryModel;
|
||||
@@ -89,10 +94,8 @@ interface TextureDiagnostic {
|
||||
summary: string;
|
||||
}
|
||||
|
||||
interface GalleryModelScene extends THREE.Object3D {
|
||||
userData: THREE.Object3D["userData"] & {
|
||||
hiddenExportPlaneCount?: number;
|
||||
};
|
||||
interface GalleryModelSceneUserData extends Record<string, unknown> {
|
||||
hiddenExportPlaneCount?: number;
|
||||
}
|
||||
|
||||
interface GalleryViewerErrorBoundaryProps {
|
||||
@@ -104,16 +107,6 @@ interface GalleryViewerErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
const TEXTURE_SLOTS = [
|
||||
"map",
|
||||
"normalMap",
|
||||
"roughnessMap",
|
||||
"metalnessMap",
|
||||
"aoMap",
|
||||
"emissiveMap",
|
||||
"alphaMap",
|
||||
] as const;
|
||||
|
||||
const LOADING_TEXTURE_DIAGNOSTIC: TextureDiagnostic = {
|
||||
modelId: null,
|
||||
status: "loading",
|
||||
@@ -221,7 +214,7 @@ function GalleryModelPreview({
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
disposeGalleryModelMaterials(modelScene);
|
||||
disposeModelMaterials(modelScene);
|
||||
};
|
||||
}, [modelScene]);
|
||||
|
||||
@@ -253,7 +246,7 @@ function GalleryModelPreview({
|
||||
}
|
||||
|
||||
function createGalleryModelScene(scene: THREE.Object3D): THREE.Object3D {
|
||||
const modelScene = scene.clone(true) as GalleryModelScene;
|
||||
const modelScene = scene.clone(true);
|
||||
const exportPlaneMeshes: THREE.Mesh[] = [];
|
||||
|
||||
modelScene.traverse((object) => {
|
||||
@@ -273,7 +266,8 @@ function createGalleryModelScene(scene: THREE.Object3D): THREE.Object3D {
|
||||
mesh.parent?.remove(mesh);
|
||||
}
|
||||
|
||||
modelScene.userData.hiddenExportPlaneCount = exportPlaneMeshes.length;
|
||||
const userData = modelScene.userData as GalleryModelSceneUserData;
|
||||
userData.hiddenExportPlaneCount = exportPlaneMeshes.length;
|
||||
|
||||
return modelScene;
|
||||
}
|
||||
@@ -298,33 +292,21 @@ function isExportPlaneMesh(mesh: THREE.Mesh): boolean {
|
||||
|
||||
function createGalleryMaterial(material: THREE.Material): THREE.Material {
|
||||
const galleryMaterial = material.clone();
|
||||
const materialWithNormalMap = galleryMaterial as THREE.Material & {
|
||||
normalMap?: THREE.Texture | null;
|
||||
};
|
||||
|
||||
galleryMaterial.side = THREE.DoubleSide;
|
||||
|
||||
if (materialWithNormalMap.normalMap) {
|
||||
materialWithNormalMap.normalMap = null;
|
||||
if (hasNormalMap(galleryMaterial)) {
|
||||
galleryMaterial.normalMap = null;
|
||||
galleryMaterial.needsUpdate = true;
|
||||
}
|
||||
|
||||
return galleryMaterial;
|
||||
}
|
||||
|
||||
function disposeGalleryModelMaterials(modelScene: THREE.Object3D): void {
|
||||
modelScene.traverse((object) => {
|
||||
if (!(object instanceof THREE.Mesh)) return;
|
||||
|
||||
if (Array.isArray(object.material)) {
|
||||
for (const material of object.material) {
|
||||
material.dispose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
object.material.dispose();
|
||||
});
|
||||
function hasNormalMap(
|
||||
material: THREE.Material,
|
||||
): material is THREE.Material & { normalMap: THREE.Texture | null } {
|
||||
return "normalMap" in material && material.normalMap !== undefined;
|
||||
}
|
||||
|
||||
function GalleryScene({
|
||||
@@ -491,8 +473,8 @@ function getTextureDiagnostic(
|
||||
): TextureDiagnostic {
|
||||
let textureCount = 0;
|
||||
let missingTextureImageCount = 0;
|
||||
const hiddenExportPlaneCount =
|
||||
(modelScene as GalleryModelScene).userData.hiddenExportPlaneCount ?? 0;
|
||||
const userData = modelScene.userData as GalleryModelSceneUserData;
|
||||
const hiddenExportPlaneCount = userData.hiddenExportPlaneCount ?? 0;
|
||||
|
||||
modelScene.traverse((object) => {
|
||||
if (!(object instanceof THREE.Mesh)) return;
|
||||
@@ -502,10 +484,10 @@ function getTextureDiagnostic(
|
||||
: [object.material];
|
||||
|
||||
for (const material of materials) {
|
||||
const materialRecord = material as unknown as Record<string, unknown>;
|
||||
const texturedMaterial = material as MaterialWithTextureSlots;
|
||||
|
||||
for (const textureSlot of TEXTURE_SLOTS) {
|
||||
const texture = materialRecord[textureSlot];
|
||||
for (const textureSlot of MATERIAL_TEXTURE_KEYS) {
|
||||
const texture = texturedMaterial[textureSlot];
|
||||
if (!(texture instanceof THREE.Texture)) continue;
|
||||
|
||||
textureCount += 1;
|
||||
@@ -559,14 +541,13 @@ export function GalleryPage(): React.JSX.Element {
|
||||
);
|
||||
|
||||
const modelCount = galleryModels.length;
|
||||
const activeModel = galleryModels[activeModelIndex] ?? galleryModels[0];
|
||||
const activeModel = galleryModels[activeModelIndex];
|
||||
|
||||
const activeTextureDiagnostic =
|
||||
activeModel && textureDiagnostic.modelId === activeModel.id
|
||||
? textureDiagnostic
|
||||
: LOADING_TEXTURE_DIAGNOSTIC;
|
||||
|
||||
// Preload adjacent models for smoother navigation
|
||||
useEffect(() => {
|
||||
if (modelCount <= 1) return;
|
||||
|
||||
@@ -586,7 +567,6 @@ export function GalleryPage(): React.JSX.Element {
|
||||
}
|
||||
}, [activeModelIndex, modelCount]);
|
||||
|
||||
// Memoized callbacks to prevent unnecessary re-renders
|
||||
const goToPreviousModel = useCallback((): void => {
|
||||
setActiveModelIndex((currentIndex) =>
|
||||
currentIndex === 0 ? modelCount - 1 : currentIndex - 1,
|
||||
|
||||
+53
-52
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
||||
import type { ThreeEvent } from "@react-three/fiber";
|
||||
import {
|
||||
useGLTF,
|
||||
OrthographicCamera,
|
||||
@@ -159,7 +160,7 @@ const EditorScene: React.FC<EditorSceneProps> = ({
|
||||
{/* 1. Terrain Mesh (Raycasted for adding/dragging) */}
|
||||
<primitive
|
||||
object={scene}
|
||||
onClick={(e: any) => {
|
||||
onClick={(e: ThreeEvent<MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
// Only click-to-create a new node if they are not actively dragging a link
|
||||
if (dragStartNodeId === null && e.point) {
|
||||
@@ -256,7 +257,7 @@ const WaypointMarkers: React.FC<WaypointMarkersProps> = ({
|
||||
onPointerOut={() => {
|
||||
setHoveredNodeId(null);
|
||||
}}
|
||||
onPointerDown={(e: any) => {
|
||||
onPointerDown={(e: ThreeEvent<PointerEvent>) => {
|
||||
e.stopPropagation();
|
||||
if (e.button === 0) {
|
||||
// Left click start drag link connection
|
||||
@@ -388,7 +389,33 @@ const ConnectionLines: React.FC<ConnectionLinesProps> = ({
|
||||
// ==========================================
|
||||
|
||||
export const WaypointEditorPage: React.FC = () => {
|
||||
const [waypoints, setWaypoints] = useState<Waypoint[]>([]);
|
||||
// Use lazy initialization to load from localStorage on mount
|
||||
const [waypoints, setWaypoints] = useState<Waypoint[]>(() => {
|
||||
console.log(
|
||||
"[Initialisation] Chargement des waypoints depuis localStorage...",
|
||||
);
|
||||
const saved = localStorage.getItem("la-fabrik-waypoints");
|
||||
if (saved) {
|
||||
try {
|
||||
const list = JSON.parse(saved);
|
||||
console.log(
|
||||
`[Initialisation] ${list.length} waypoints chargés avec succès !`,
|
||||
);
|
||||
return list;
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"[Initialisation] Erreur de parsing du stockage local",
|
||||
e,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
"[Initialisation] Aucun point enregistré en localStorage. Démarrage à vide.",
|
||||
);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
const [selectedId, setSelectedId] = useState<number | null>(null);
|
||||
const [hoveredNodeId, setHoveredNodeId] = useState<number | null>(null);
|
||||
|
||||
@@ -425,38 +452,35 @@ export const WaypointEditorPage: React.FC = () => {
|
||||
number | null
|
||||
>(null);
|
||||
|
||||
// Load from localstorage on mount
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
"[Initialisation] Chargement des waypoints depuis localStorage...",
|
||||
);
|
||||
const saved = localStorage.getItem("la-fabrik-waypoints");
|
||||
if (saved) {
|
||||
try {
|
||||
const list = JSON.parse(saved);
|
||||
console.log(
|
||||
`[Initialisation] ${list.length} waypoints chargés avec succès !`,
|
||||
);
|
||||
setWaypoints(list);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"[Initialisation] Erreur de parsing du stockage local",
|
||||
e,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
"[Initialisation] Aucun point enregistré en localStorage. Démarrage à vide.",
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Save to localstorage when waypoints change
|
||||
const saveWaypoints = (list: Waypoint[]) => {
|
||||
setWaypoints(list);
|
||||
localStorage.setItem("la-fabrik-waypoints", JSON.stringify(list));
|
||||
};
|
||||
|
||||
// Delete current selected node
|
||||
const handleDeleteNode = (id: number) => {
|
||||
console.log(
|
||||
`[Suppression] Action de suppression définitive du Point : ID = ${id}`,
|
||||
);
|
||||
setWaypoints((currentWaypoints) => {
|
||||
const updatedList = currentWaypoints
|
||||
.filter((wp) => wp.id !== id)
|
||||
.map((wp) => ({
|
||||
...wp,
|
||||
connections: wp.connections.filter((cId) => cId !== id),
|
||||
}));
|
||||
console.log(
|
||||
`[Suppression] Point ${id} supprimé. ${updatedList.length} points restants.`,
|
||||
);
|
||||
localStorage.setItem("la-fabrik-waypoints", JSON.stringify(updatedList));
|
||||
return updatedList;
|
||||
});
|
||||
setSelectedId((currentSelected) =>
|
||||
currentSelected === id ? null : currentSelected,
|
||||
);
|
||||
};
|
||||
|
||||
// Delete a specific connection (break the link)
|
||||
const deleteSelectedConnection = (idA: number, idB: number) => {
|
||||
console.log(
|
||||
@@ -673,29 +697,6 @@ export const WaypointEditorPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Delete current selected node
|
||||
const handleDeleteNode = (id: number) => {
|
||||
console.log(
|
||||
`[Suppression] Action de suppression définitive du Point : ID = ${id}`,
|
||||
);
|
||||
setWaypoints((currentWaypoints) => {
|
||||
const updatedList = currentWaypoints
|
||||
.filter((wp) => wp.id !== id)
|
||||
.map((wp) => ({
|
||||
...wp,
|
||||
connections: wp.connections.filter((cId) => cId !== id),
|
||||
}));
|
||||
console.log(
|
||||
`[Suppression] Point ${id} supprimé. ${updatedList.length} points restants.`,
|
||||
);
|
||||
localStorage.setItem("la-fabrik-waypoints", JSON.stringify(updatedList));
|
||||
return updatedList;
|
||||
});
|
||||
setSelectedId((currentSelected) =>
|
||||
currentSelected === id ? null : currentSelected,
|
||||
);
|
||||
};
|
||||
|
||||
// Connect Mode Trigger
|
||||
const startConnecting = (id: number) => {
|
||||
console.log(
|
||||
|
||||
Reference in New Issue
Block a user