merge: sync develop into env manager
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (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

This commit is contained in:
Tom Boullay
2026-05-12 10:56:56 +02:00
171 changed files with 4560 additions and 1507 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ export function DocsAnimationPage(): React.JSX.Element {
<DocsDocument
content={animation}
frContent={animation}
meta="08"
meta="11"
title="Animation & 3D Model System"
/>
);
+13
View File
@@ -0,0 +1,13 @@
import audio from "../../../../docs/technical/audio.md?raw";
import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsAudioPage(): React.JSX.Element {
return (
<DocsDocument
content={audio}
frContent={audio}
meta="05"
title="Audio Technical Notes"
/>
);
}
+1 -1
View File
@@ -7,7 +7,7 @@ export function DocsEditorPage(): React.JSX.Element {
<DocsDocument
content={editor}
frContent={editorFr}
meta="09"
meta="10"
title="Editor User Guide"
/>
);
+1 -1
View File
@@ -7,7 +7,7 @@ export function DocsFeaturesPage(): React.JSX.Element {
<DocsDocument
content={features}
frContent={featuresFr}
meta="06"
meta="08"
title="Features"
/>
);
+1 -1
View File
@@ -6,7 +6,7 @@ export function DocsHandTrackingPage(): React.JSX.Element {
<DocsDocument
content={handTracking}
frContent={handTracking}
meta="05"
meta="06"
title="Hand Tracking Technical Notes"
/>
);
+1 -1
View File
@@ -6,7 +6,7 @@ export function DocsMainFeaturePage(): React.JSX.Element {
<DocsDocument
content={mainFeature}
frContent={mainFeature}
meta="07"
meta="09"
title="Main Feature"
/>
);
+1 -1
View File
@@ -7,7 +7,7 @@ export function DocsZustandPage(): React.JSX.Element {
<DocsDocument
content={zustand}
frContent={zustandFr}
meta="05"
meta="07"
title="Zustand Game State"
/>
);
+91 -22
View File
@@ -1,20 +1,56 @@
import { useCallback, useState } from "react";
import { Suspense, useCallback, useEffect, useState } from "react";
import { Canvas } from "@react-three/fiber";
import { useProgress } from "@react-three/drei";
import { EditorControls } from "@/components/editor/EditorControls";
import { EditorScene } from "@/components/editor/scene/EditorScene";
import type { EditorCinematicPreviewRequest } from "@/components/editor/scene/EditorScene";
import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay";
import { Subtitles } from "@/components/ui/Subtitles";
import { useEditorHistory } from "@/hooks/editor/useEditorHistory";
import type { CinematicDefinition } from "@/types/cinematics/cinematics";
import { useEditorSceneData } from "@/hooks/editor/useEditorSceneData";
import type { MapNode, SceneData, TransformMode } from "@/types/editor/editor";
import {
INITIAL_SCENE_LOADING_STATE,
type SceneLoadingChangeHandler,
type SceneLoadingState,
} from "@/types/world/sceneLoading";
const SAVE_ERROR_MESSAGE = "Erreur lors de l'enregistrement";
interface EditorSceneLoadingTrackerProps {
onLoadingStateChange: SceneLoadingChangeHandler;
}
function serializeMapNodes(sceneData: SceneData): string {
return JSON.stringify(sceneData.mapNodes, null, 2);
}
function EditorSceneLoadingTracker({
onLoadingStateChange,
}: EditorSceneLoadingTrackerProps): null {
const { active, progress } = useProgress();
useEffect(() => {
if (active) {
onLoadingStateChange({
currentStep: "Importation des models",
progress: 0.2 + (progress / 100) * 0.7,
status: "loading",
});
return;
}
onLoadingStateChange({
currentStep: "Gameplay prêt",
progress: 1,
status: "ready",
});
}, [active, onLoadingStateChange, progress]);
return null;
}
export function EditorPage(): React.JSX.Element {
const {
hasMapJson,
@@ -32,6 +68,35 @@ export function EditorPage(): React.JSX.Element {
useState<TransformMode>("translate");
const [isPlayerMode, setIsPlayerMode] = useState(false);
const [isSelectionLocked, setIsSelectionLocked] = useState(false);
const [sceneLoadingState, setSceneLoadingState] = useState<SceneLoadingState>(
{
...INITIAL_SCENE_LOADING_STATE,
currentStep: "Montage progressif des models",
progress: 0.2,
},
);
const handleSceneLoadingStateChange = useCallback(
(nextState: SceneLoadingState) => {
setSceneLoadingState((currentState) => {
const shouldRestartProgress = currentState.status === "ready";
return {
...nextState,
progress: shouldRestartProgress
? nextState.progress
: Math.max(currentState.progress, nextState.progress),
};
});
},
[],
);
const editorLoadingState = isMapLoading
? {
currentStep: "Récupération blocking",
progress: 0.08,
status: "loading" as const,
}
: sceneLoadingState;
const [cinematicPreviewRequest, setCinematicPreviewRequest] =
useState<EditorCinematicPreviewRequest | null>(null);
@@ -131,10 +196,7 @@ export function EditorPage(): React.JSX.Element {
if (isMapLoading) {
return (
<div className="editor-container">
<div className="editor-loading">
<h2>Chargement de l'éditeur...</h2>
<p>Vérification de map.json dans public/</p>
</div>
<SceneLoadingOverlay state={editorLoadingState} />
</div>
);
}
@@ -185,26 +247,33 @@ export function EditorPage(): React.JSX.Element {
gl.setClearColor("#050505");
}}
>
<EditorScene
sceneData={sceneData!}
selectedNodeIndex={selectedNodeIndex}
onSelectNode={handleSelectNode}
isSelectionLocked={isSelectionLocked}
hoveredNodeIndex={hoveredNodeIndex}
onHoverNode={handleHoverNode}
transformMode={transformMode}
onTransformModeChange={handleTransformModeChange}
onTransformStart={handleTransformStart}
onTransformEnd={handleTransformEnd}
onNodeTransform={handleNodeTransform}
onUndo={handleUndo}
onRedo={handleRedo}
isPlayerMode={isPlayerMode}
cinematicPreviewRequest={cinematicPreviewRequest}
onCinematicPreviewComplete={handleCinematicPreviewComplete}
<EditorSceneLoadingTracker
onLoadingStateChange={handleSceneLoadingStateChange}
/>
<Suspense fallback={null}>
<EditorScene
sceneData={sceneData!}
selectedNodeIndex={selectedNodeIndex}
onSelectNode={handleSelectNode}
isSelectionLocked={isSelectionLocked}
hoveredNodeIndex={hoveredNodeIndex}
onHoverNode={handleHoverNode}
transformMode={transformMode}
onTransformModeChange={handleTransformModeChange}
onTransformStart={handleTransformStart}
onTransformEnd={handleTransformEnd}
onNodeTransform={handleNodeTransform}
onUndo={handleUndo}
onRedo={handleRedo}
isPlayerMode={isPlayerMode}
cinematicPreviewRequest={cinematicPreviewRequest}
onCinematicPreviewComplete={handleCinematicPreviewComplete}
/>
</Suspense>
</Canvas>
<SceneLoadingOverlay state={editorLoadingState} />
{sceneData && (
<EditorControls
transformMode={transformMode}
+27 -2
View File
@@ -1,12 +1,36 @@
import { Suspense } from "react";
import { Suspense, useCallback, useState } from "react";
import { Canvas } from "@react-three/fiber";
import * as THREE from "three";
import { DebugPerf } from "@/components/debug/DebugPerf";
import { GameUI } from "@/components/ui/GameUI";
import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay";
import { HandTrackingProvider } from "@/providers/gameplay/HandTrackingProvider";
import {
INITIAL_SCENE_LOADING_STATE,
type SceneLoadingState,
} from "@/types/world/sceneLoading";
import { World } from "@/world/World";
export function HomePage(): React.JSX.Element {
const [sceneLoadingState, setSceneLoadingState] = useState<SceneLoadingState>(
INITIAL_SCENE_LOADING_STATE,
);
const handleSceneLoadingStateChange = useCallback(
(nextState: SceneLoadingState) => {
setSceneLoadingState((currentState) => {
if (currentState.status === "ready" && nextState.status === "loading") {
return currentState;
}
return {
...nextState,
progress: Math.max(currentState.progress, nextState.progress),
};
});
},
[],
);
return (
<HandTrackingProvider>
<Canvas
@@ -14,11 +38,12 @@ export function HomePage(): React.JSX.Element {
shadows={{ type: THREE.PCFShadowMap }}
>
<Suspense fallback={null}>
<World />
<World onLoadingStateChange={handleSceneLoadingStateChange} />
<DebugPerf />
</Suspense>
</Canvas>
<GameUI />
<SceneLoadingOverlay state={sceneLoadingState} />
</HandTrackingProvider>
);
}