update
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
import { useContext } from "react";
|
||||
import { DocsLanguageContext } from "@/contexts/docs/DocsLanguageContext";
|
||||
|
||||
export function useDocsLanguage() {
|
||||
const context = useContext(DocsLanguageContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useDocsLanguage must be used inside DocsLanguageProvider");
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import type { MapNode, SceneData } from "@/types/editor";
|
||||
|
||||
interface ObjectTransform {
|
||||
uuid: string;
|
||||
position: { x: number; y: number; z: number };
|
||||
rotation: { x: number; y: number; z: number };
|
||||
scale: { x: number; y: number; z: number };
|
||||
}
|
||||
|
||||
class HistoryManager {
|
||||
private history: ObjectTransform[][] = [];
|
||||
private currentIndex = -1;
|
||||
private maxSize: number;
|
||||
|
||||
constructor(maxSize = 50) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
saveSnapshot(objects: ObjectTransform[]): void {
|
||||
if (this.currentIndex < this.history.length - 1) {
|
||||
this.history = this.history.slice(0, this.currentIndex + 1);
|
||||
}
|
||||
|
||||
this.history.push(objects.map((object) => ({ ...object })));
|
||||
this.currentIndex = this.history.length - 1;
|
||||
|
||||
if (this.history.length > this.maxSize) {
|
||||
this.history.shift();
|
||||
this.currentIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
undo(): ObjectTransform[] | undefined {
|
||||
if (this.currentIndex <= 0) return undefined;
|
||||
|
||||
this.currentIndex--;
|
||||
return this.history[this.currentIndex];
|
||||
}
|
||||
|
||||
redo(): ObjectTransform[] | undefined {
|
||||
if (this.currentIndex >= this.history.length - 1) return undefined;
|
||||
|
||||
this.currentIndex++;
|
||||
return this.history[this.currentIndex];
|
||||
}
|
||||
|
||||
getUndoCount(): number {
|
||||
return this.currentIndex;
|
||||
}
|
||||
|
||||
getRedoCount(): number {
|
||||
return this.history.length - 1 - this.currentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
interface UseEditorHistoryResult {
|
||||
undoCount: number;
|
||||
redoCount: number;
|
||||
handleUndo: () => void;
|
||||
handleRedo: () => void;
|
||||
handleTransformStart: () => void;
|
||||
handleTransformEnd: () => void;
|
||||
}
|
||||
|
||||
export function useEditorHistory(
|
||||
sceneData: SceneData | null,
|
||||
setSceneData: React.Dispatch<React.SetStateAction<SceneData | null>>,
|
||||
): UseEditorHistoryResult {
|
||||
const [undoCount, setUndoCount] = useState(0);
|
||||
const [redoCount, setRedoCount] = useState(0);
|
||||
const historyManager = useRef(new HistoryManager(50));
|
||||
|
||||
const updateHistoryCounts = useCallback(() => {
|
||||
setUndoCount(historyManager.current.getUndoCount());
|
||||
setRedoCount(historyManager.current.getRedoCount());
|
||||
}, []);
|
||||
|
||||
const applySnapshot = useCallback(
|
||||
(snapshot: ObjectTransform[]): void => {
|
||||
setSceneData((prev) => {
|
||||
if (!prev) return null;
|
||||
|
||||
const mapNodes = prev.mapNodes.map((node, index) => {
|
||||
const transform = snapshot.find(
|
||||
(item) => item.uuid === `node-${index}`,
|
||||
);
|
||||
if (!transform) return node;
|
||||
|
||||
return {
|
||||
...node,
|
||||
position: [
|
||||
transform.position.x,
|
||||
transform.position.y,
|
||||
transform.position.z,
|
||||
],
|
||||
rotation: [
|
||||
transform.rotation.x,
|
||||
transform.rotation.y,
|
||||
transform.rotation.z,
|
||||
],
|
||||
scale: [transform.scale.x, transform.scale.y, transform.scale.z],
|
||||
} satisfies MapNode;
|
||||
});
|
||||
|
||||
return { ...prev, mapNodes };
|
||||
});
|
||||
},
|
||||
[setSceneData],
|
||||
);
|
||||
|
||||
const handleUndo = useCallback(() => {
|
||||
const snapshot = historyManager.current.undo();
|
||||
if (!snapshot) return;
|
||||
|
||||
applySnapshot(snapshot);
|
||||
updateHistoryCounts();
|
||||
}, [applySnapshot, updateHistoryCounts]);
|
||||
|
||||
const handleRedo = useCallback(() => {
|
||||
const snapshot = historyManager.current.redo();
|
||||
if (!snapshot) return;
|
||||
|
||||
applySnapshot(snapshot);
|
||||
updateHistoryCounts();
|
||||
}, [applySnapshot, updateHistoryCounts]);
|
||||
|
||||
const handleTransformStart = useCallback(() => {
|
||||
if (!sceneData) return;
|
||||
historyManager.current.saveSnapshot(createSnapshot(sceneData));
|
||||
}, [sceneData]);
|
||||
|
||||
const handleTransformEnd = useCallback(() => {
|
||||
if (!sceneData) return;
|
||||
historyManager.current.saveSnapshot(createSnapshot(sceneData));
|
||||
updateHistoryCounts();
|
||||
}, [sceneData, updateHistoryCounts]);
|
||||
|
||||
return {
|
||||
undoCount,
|
||||
redoCount,
|
||||
handleUndo,
|
||||
handleRedo,
|
||||
handleTransformStart,
|
||||
handleTransformEnd,
|
||||
};
|
||||
}
|
||||
|
||||
function createSnapshot(sceneData: SceneData): ObjectTransform[] {
|
||||
return sceneData.mapNodes.map((node, index) => ({
|
||||
uuid: `node-${index}`,
|
||||
position: {
|
||||
x: node.position[0],
|
||||
y: node.position[1],
|
||||
z: node.position[2],
|
||||
},
|
||||
rotation: {
|
||||
x: node.rotation[0],
|
||||
y: node.rotation[1],
|
||||
z: node.rotation[2],
|
||||
},
|
||||
scale: { x: node.scale[0], y: node.scale[1], z: node.scale[2] },
|
||||
}));
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { createSceneDataFromFiles } from "@/utils/editor/loadEditorScene";
|
||||
import { loadMapSceneData } from "@/utils/loadMapSceneData";
|
||||
import type { SceneData } from "@/types/editor";
|
||||
|
||||
interface UseEditorSceneDataResult {
|
||||
hasMapJson: boolean;
|
||||
isMapLoading: boolean;
|
||||
sceneData: SceneData | null;
|
||||
setSceneData: React.Dispatch<React.SetStateAction<SceneData | null>>;
|
||||
handleFolderUpload: (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
export function useEditorSceneData(): UseEditorSceneDataResult {
|
||||
const [hasMapJson, setHasMapJson] = useState<boolean>(false);
|
||||
const [isMapLoading, setIsMapLoading] = useState<boolean>(true);
|
||||
const [sceneData, setSceneData] = useState<SceneData | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadScene = async (): Promise<void> => {
|
||||
setIsMapLoading(true);
|
||||
|
||||
try {
|
||||
const loadedSceneData = await loadMapSceneData();
|
||||
setSceneData(loadedSceneData);
|
||||
setHasMapJson(Boolean(loadedSceneData));
|
||||
} catch (error) {
|
||||
console.error("Error loading map data:", error);
|
||||
setHasMapJson(false);
|
||||
} finally {
|
||||
setIsMapLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadScene();
|
||||
}, []);
|
||||
|
||||
const handleFolderUpload = useCallback(
|
||||
async (event: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
|
||||
const files = event.target.files;
|
||||
if (!files) return;
|
||||
|
||||
try {
|
||||
const uploadedSceneData = await createSceneDataFromFiles(files);
|
||||
setSceneData(uploadedSceneData);
|
||||
setHasMapJson(true);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Erreur";
|
||||
console.error("Error processing upload:", error);
|
||||
alert(message);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
hasMapJson,
|
||||
isMapLoading,
|
||||
sceneData,
|
||||
setSceneData,
|
||||
handleFolderUpload,
|
||||
};
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/* eslint-disable react-hooks/immutability */
|
||||
import { useRef, useEffect, useState, useCallback } from "react";
|
||||
import { useGLTF, useAnimations } from "@react-three/drei";
|
||||
import type { AnimationAction, AnimationMixer } from "three";
|
||||
import * as THREE from "three";
|
||||
|
||||
export interface CharacterAnimationConfig {
|
||||
modelPath: string;
|
||||
initialAnimation?: string;
|
||||
fadeDuration?: number;
|
||||
}
|
||||
|
||||
interface UseCharacterAnimationReturn {
|
||||
scene: THREE.Group;
|
||||
actions: { [key: string]: AnimationAction | null };
|
||||
names: string[];
|
||||
mixer: AnimationMixer;
|
||||
groupRef: React.MutableRefObject<THREE.Group | null>;
|
||||
currentAnimation: string;
|
||||
play: (name: string) => void;
|
||||
stop: () => void;
|
||||
fadeTo: (name: string, duration?: number) => void;
|
||||
setAnimationSpeed: (speed: number) => void;
|
||||
}
|
||||
|
||||
const DEFAULT_FADE_DURATION = 0.3;
|
||||
|
||||
export function useCharacterAnimation(
|
||||
config: CharacterAnimationConfig,
|
||||
): UseCharacterAnimationReturn {
|
||||
const {
|
||||
modelPath,
|
||||
initialAnimation = "Idle",
|
||||
fadeDuration = DEFAULT_FADE_DURATION,
|
||||
} = config;
|
||||
|
||||
const groupRef = useRef<THREE.Group | null>(null);
|
||||
const { scene, animations } = useGLTF(modelPath);
|
||||
const { actions, names, mixer } = useAnimations(animations, groupRef);
|
||||
const [currentAnimation, setCurrentAnimation] = useState(initialAnimation);
|
||||
|
||||
const play = useCallback(
|
||||
(name: string) => {
|
||||
const action = actions[name];
|
||||
if (action) {
|
||||
Object.values(actions).forEach((a) => {
|
||||
if (a && a !== action) a.fadeOut(fadeDuration);
|
||||
});
|
||||
action.reset().fadeIn(fadeDuration).play();
|
||||
setCurrentAnimation(name);
|
||||
}
|
||||
},
|
||||
[actions, fadeDuration],
|
||||
);
|
||||
|
||||
const stop = useCallback(() => {
|
||||
Object.values(actions).forEach((a) => a?.fadeOut(fadeDuration));
|
||||
const defaultAction = actions[initialAnimation as string];
|
||||
if (defaultAction) {
|
||||
defaultAction.reset().fadeIn(fadeDuration).play();
|
||||
setCurrentAnimation(initialAnimation);
|
||||
}
|
||||
}, [actions, initialAnimation, fadeDuration]);
|
||||
|
||||
const fadeTo = useCallback(
|
||||
(name: string, duration = fadeDuration) => {
|
||||
const targetAction = actions[name];
|
||||
if (targetAction) {
|
||||
Object.values(actions).forEach((a) => {
|
||||
if (a && a !== targetAction) a.fadeOut(duration);
|
||||
});
|
||||
targetAction.reset().fadeIn(duration).play();
|
||||
setCurrentAnimation(name);
|
||||
}
|
||||
},
|
||||
[actions, fadeDuration],
|
||||
);
|
||||
|
||||
const setAnimationSpeed = useCallback(
|
||||
(speed: number) => {
|
||||
if (mixer) {
|
||||
mixer.timeScale = speed;
|
||||
}
|
||||
},
|
||||
[mixer],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultAction = actions[initialAnimation as string];
|
||||
if (defaultAction) {
|
||||
defaultAction.play();
|
||||
}
|
||||
}, [actions, initialAnimation]);
|
||||
|
||||
return {
|
||||
scene,
|
||||
actions,
|
||||
names,
|
||||
mixer,
|
||||
groupRef,
|
||||
currentAnimation,
|
||||
play,
|
||||
stop,
|
||||
fadeTo,
|
||||
setAnimationSpeed,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { InteractionManager } from "@/managers/InteractionManager";
|
||||
import { InteractionManager } from "@/stateManager/InteractionManager";
|
||||
import type { InteractionSnapshot } from "@/types/interaction";
|
||||
|
||||
const manager = InteractionManager.getInstance();
|
||||
|
||||
@@ -2,19 +2,14 @@ import { useEffect, useRef } from "react";
|
||||
import type { RefObject } from "react";
|
||||
import type { Object3D } from "three";
|
||||
import { Octree } from "three/addons/math/Octree.js";
|
||||
import type { OctreeReadyHandler } from "@/types/three";
|
||||
import type { OctreeReadyHandler } from "@/types/3d";
|
||||
|
||||
export function useOctreeGraphNode(
|
||||
graphNodeRef: RefObject<Object3D | null>,
|
||||
onOctreeReady: OctreeReadyHandler,
|
||||
rebuildKey: string | number = 0,
|
||||
): void {
|
||||
const octreeBuilt = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
octreeBuilt.current = false;
|
||||
}, [rebuildKey]);
|
||||
|
||||
useEffect(() => {
|
||||
const graphNode = graphNodeRef.current;
|
||||
if (octreeBuilt.current || !graphNode) return;
|
||||
@@ -25,5 +20,5 @@ export function useOctreeGraphNode(
|
||||
const octree = new Octree();
|
||||
octree.fromGraphNode(graphNode);
|
||||
onOctreeReady(octree);
|
||||
}, [graphNodeRef, onOctreeReady, rebuildKey]);
|
||||
}, [graphNodeRef, onOctreeReady]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user