refactor: move editor page and types to conventional folders
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@ import { Crosshair } from "@/components/ui/Crosshair";
|
||||
import { InteractPrompt } from "@/components/ui/InteractPrompt";
|
||||
import { DebugPerf } from "@/utils/debug/DebugPerf";
|
||||
import { World } from "@/world/World";
|
||||
import { EditorPage } from "@/components/editor/EditorPage";
|
||||
import { EditorPage } from "@/pages/EditorPage";
|
||||
|
||||
function App(): React.JSX.Element {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TransformMode } from "./types";
|
||||
import type { TransformMode } from "@/types/editor";
|
||||
|
||||
interface EditorControlsProps {
|
||||
transformMode: TransformMode;
|
||||
|
||||
@@ -1,335 +0,0 @@
|
||||
.editor-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
font-family: system-ui, sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editor-loading,
|
||||
.editor-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.editor-loading h2 {
|
||||
font-size: 2rem;
|
||||
color: #ff6600;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.editor-loading p {
|
||||
font-size: 1rem;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.editor-error h2 {
|
||||
font-size: 1.8rem;
|
||||
color: #ff4444;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.editor-error p {
|
||||
font-size: 1.1rem;
|
||||
color: #ccc;
|
||||
margin-bottom: 2rem;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
border: 2px dashed rgba(255, 102, 0, 0.3);
|
||||
max-width: 500px;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.upload-section h3 {
|
||||
color: #ff6600;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.drop-zone {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 2rem 1rem;
|
||||
border: 2px dashed #ff6600;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 102, 0, 0.1);
|
||||
color: #ff6600;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.drop-zone:hover {
|
||||
background: rgba(255, 102, 0, 0.2);
|
||||
border-color: #ff8533;
|
||||
}
|
||||
|
||||
.folder-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.folder-structure {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.folder-structure h4 {
|
||||
color: #aaa;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.folder-structure pre {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
color: #ddd;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 0.9rem;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
code {
|
||||
background: rgba(255, 102, 0, 0.2);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
color: #ff8533;
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.editor-error h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.drop-zone {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Editor Controls Styles */
|
||||
.editor-camera-info {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #00ff00;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #00ff00;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.editor-controls-panel {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 250px;
|
||||
height: 100%;
|
||||
background: rgba(30, 30, 30, 0.95);
|
||||
padding: 20px;
|
||||
color: white;
|
||||
border-left: 2px solid #ff6600;
|
||||
overflow-y: auto;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.editor-controls-panel h3 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
color: #ff6600;
|
||||
}
|
||||
|
||||
.transform-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.transform-button {
|
||||
padding: 12px;
|
||||
background: #333;
|
||||
color: white;
|
||||
border: 1px solid #555;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.transform-button.active {
|
||||
background: #ff6600;
|
||||
color: black;
|
||||
border-color: #ff6600;
|
||||
}
|
||||
|
||||
.transform-button:hover {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.transform-button.active:hover {
|
||||
background: #ff8533;
|
||||
}
|
||||
|
||||
.history-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.history-button {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
background: #333;
|
||||
color: #aaa;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.history-button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.export-button {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
background: #ff6600;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.export-button:hover {
|
||||
background: #ff8533;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
background: #22c55e;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.save-button:hover {
|
||||
background: #16a34a;
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.reset-button:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.player-button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.player-button.active {
|
||||
background: #ff6600;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.player-button:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.player-button.active:hover {
|
||||
background: #ff8533;
|
||||
}
|
||||
|
||||
.selected-info {
|
||||
background: rgba(255, 102, 0, 0.1);
|
||||
border: 1px solid #ff6600;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.selected-name {
|
||||
font-size: 16px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.selected-index {
|
||||
font-size: 14px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.no-selection {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px dashed #555;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.controls-help {
|
||||
background: #222;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
.controls-help p {
|
||||
margin: 4px 0;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
@@ -3,8 +3,7 @@ import { OrbitControls } from "@react-three/drei";
|
||||
import EditorCamera from "./EditorCamera";
|
||||
import FlyController from "./FlyController";
|
||||
import MapViewer from "./MapViewer";
|
||||
import type { MapNode, TransformMode } from "./types";
|
||||
import type { SceneData } from "./types";
|
||||
import type { MapNode, TransformMode, SceneData } from "@/types/editor";
|
||||
|
||||
interface EditorViewerProps {
|
||||
sceneData: SceneData;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useGLTF } from "@react-three/drei";
|
||||
import { Grid, TransformControls } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
|
||||
import type { SceneData, MapNode, TransformMode } from "./types";
|
||||
import type { SceneData, MapNode, TransformMode } from "@/types/editor";
|
||||
|
||||
interface MapViewerProps {
|
||||
sceneData: SceneData;
|
||||
@@ -171,7 +171,14 @@ function ModelNodeWithRef({
|
||||
return () => {
|
||||
currentMap.delete(currentIndex);
|
||||
};
|
||||
}, [index]);
|
||||
}, [
|
||||
index,
|
||||
node.name,
|
||||
node.position,
|
||||
node.rotation,
|
||||
node.scale,
|
||||
objectsMapRef,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (groupRef.current) {
|
||||
@@ -258,7 +265,14 @@ function FallbackNodeWithRef({
|
||||
return () => {
|
||||
currentMap.delete(currentIndex);
|
||||
};
|
||||
}, [index]);
|
||||
}, [
|
||||
index,
|
||||
node.name,
|
||||
node.position,
|
||||
node.rotation,
|
||||
node.scale,
|
||||
objectsMapRef,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (meshRef.current) {
|
||||
|
||||
@@ -3,14 +3,7 @@ import { useGLTF } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
import { useOctreeGraphNode } from "@/hooks/useOctreeGraphNode";
|
||||
import type { OctreeReadyHandler } from "@/types/3d";
|
||||
|
||||
interface MapNode {
|
||||
name: string;
|
||||
type: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
scale: [number, number, number];
|
||||
}
|
||||
import type { MapNode } from "@/types/editor";
|
||||
|
||||
const MAP_JSON_PATH = "/map.json";
|
||||
|
||||
@@ -88,32 +81,23 @@ function ModelInstance({ node }: { node: MapNode }): React.JSX.Element {
|
||||
const modelPath = `/models/${node.name}/model.gltf`;
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const { scene } = useGLTF(modelPath);
|
||||
const { position, rotation, scale } = node;
|
||||
|
||||
useEffect(() => {
|
||||
if (groupRef.current) {
|
||||
groupRef.current.position.set(...node.position);
|
||||
groupRef.current.rotation.set(...node.rotation);
|
||||
groupRef.current.scale.set(...node.scale);
|
||||
groupRef.current.position.set(...position);
|
||||
groupRef.current.rotation.set(...rotation);
|
||||
groupRef.current.scale.set(...scale);
|
||||
}
|
||||
}, [
|
||||
node.position[0],
|
||||
node.position[1],
|
||||
node.position[2],
|
||||
node.rotation[0],
|
||||
node.rotation[1],
|
||||
node.rotation[2],
|
||||
node.scale[0],
|
||||
node.scale[1],
|
||||
node.scale[2],
|
||||
]);
|
||||
}, [position, rotation, scale]);
|
||||
|
||||
return (
|
||||
<primitive
|
||||
ref={groupRef}
|
||||
object={scene}
|
||||
position={node.position}
|
||||
rotation={node.rotation}
|
||||
scale={node.scale}
|
||||
position={position}
|
||||
rotation={rotation}
|
||||
scale={scale}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+336
@@ -79,3 +79,339 @@ canvas {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
/* Editor page */
|
||||
.editor-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
font-family: system-ui, sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editor-loading,
|
||||
.editor-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.editor-loading h2 {
|
||||
font-size: 2rem;
|
||||
color: #ff6600;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.editor-loading p {
|
||||
font-size: 1rem;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.editor-error h2 {
|
||||
font-size: 1.8rem;
|
||||
color: #ff4444;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.editor-error p {
|
||||
font-size: 1.1rem;
|
||||
color: #ccc;
|
||||
margin-bottom: 2rem;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.editor-container code {
|
||||
background: rgba(255, 102, 0, 0.2);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
color: #ff8533;
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
border: 2px dashed rgba(255, 102, 0, 0.3);
|
||||
max-width: 500px;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.upload-section h3 {
|
||||
color: #ff6600;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.drop-zone {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 2rem 1rem;
|
||||
border: 2px dashed #ff6600;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 102, 0, 0.1);
|
||||
color: #ff6600;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.drop-zone:hover {
|
||||
background: rgba(255, 102, 0, 0.2);
|
||||
border-color: #ff8533;
|
||||
}
|
||||
|
||||
.folder-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.folder-structure {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.folder-structure h4 {
|
||||
color: #aaa;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.folder-structure pre {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
color: #ddd;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 0.9rem;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.editor-camera-info {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #00ff00;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #00ff00;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.editor-controls-panel {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 250px;
|
||||
height: 100%;
|
||||
background: rgba(30, 30, 30, 0.95);
|
||||
padding: 20px;
|
||||
color: white;
|
||||
border-left: 2px solid #ff6600;
|
||||
overflow-y: auto;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.editor-controls-panel h3 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
color: #ff6600;
|
||||
}
|
||||
|
||||
.transform-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.transform-button {
|
||||
padding: 12px;
|
||||
background: #333;
|
||||
color: white;
|
||||
border: 1px solid #555;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.transform-button.active {
|
||||
background: #ff6600;
|
||||
color: black;
|
||||
border-color: #ff6600;
|
||||
}
|
||||
|
||||
.transform-button:hover {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.transform-button.active:hover {
|
||||
background: #ff8533;
|
||||
}
|
||||
|
||||
.history-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.history-button {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
background: #333;
|
||||
color: #aaa;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.history-button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.export-button {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
background: #ff6600;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.export-button:hover {
|
||||
background: #ff8533;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
background: #22c55e;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.save-button:hover {
|
||||
background: #16a34a;
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.reset-button:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.player-button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.player-button.active {
|
||||
background: #ff6600;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.player-button:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.player-button.active:hover {
|
||||
background: #ff8533;
|
||||
}
|
||||
|
||||
.selected-info {
|
||||
background: rgba(255, 102, 0, 0.1);
|
||||
border: 1px solid #ff6600;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.selected-name {
|
||||
font-size: 16px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.selected-index {
|
||||
font-size: 14px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.no-selection {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px dashed #555;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.controls-help {
|
||||
background: #222;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
.controls-help p {
|
||||
margin: 4px 0;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.editor-error h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.drop-zone {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { useEffect, useState, useCallback, useRef } from "react";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import EditorViewer from "./EditorViewer";
|
||||
import EditorControls from "./EditorControls";
|
||||
import type { TransformMode, MapNode } from "./types";
|
||||
import type { SceneData } from "./types";
|
||||
import "./EditorPage.css";
|
||||
|
||||
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 };
|
||||
}
|
||||
import EditorViewer from "@/components/editor/EditorViewer";
|
||||
import EditorControls from "@/components/editor/EditorControls";
|
||||
import type {
|
||||
TransformMode,
|
||||
MapNode,
|
||||
SceneData,
|
||||
ObjectTransform,
|
||||
} from "@/types/editor";
|
||||
|
||||
class HistoryManager {
|
||||
private history: ObjectTransform[][] = [];
|
||||
@@ -60,19 +56,6 @@ class HistoryManager {
|
||||
getRedoCount(): number {
|
||||
return this.history.length - 1 - this.currentIndex;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.history = [];
|
||||
this.currentIndex = -1;
|
||||
}
|
||||
|
||||
canUndo(): boolean {
|
||||
return this.currentIndex > 0;
|
||||
}
|
||||
|
||||
canRedo(): boolean {
|
||||
return this.currentIndex < this.history.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
export function EditorPage(): React.JSX.Element {
|
||||
@@ -80,7 +63,6 @@ export function EditorPage(): React.JSX.Element {
|
||||
const [isMapLoading, setIsMapLoading] = useState<boolean>(true);
|
||||
const [sceneData, setSceneData] = useState<SceneData | null>(null);
|
||||
|
||||
// État partagé entre Canvas (3D) et EditorControls (HTML)
|
||||
const [selectedNodeIndex, setSelectedNodeIndex] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
@@ -91,10 +73,8 @@ export function EditorPage(): React.JSX.Element {
|
||||
const [redoCount, setRedoCount] = useState(0);
|
||||
const [isPlayerMode, setIsPlayerMode] = useState(false);
|
||||
|
||||
const historyManagerRef = useCallback(() => new HistoryManager(50), []);
|
||||
const historyManager = useRef<HistoryManager>(historyManagerRef());
|
||||
const historyManager = useRef<HistoryManager>(new HistoryManager(50));
|
||||
|
||||
// Callbacks partagés
|
||||
const handleSelectNode = useCallback((index: number | null) => {
|
||||
setSelectedNodeIndex(index);
|
||||
}, []);
|
||||
@@ -195,7 +175,6 @@ export function EditorPage(): React.JSX.Element {
|
||||
}, [sceneData]);
|
||||
|
||||
const handleResetCamera = useCallback(() => {
|
||||
// Logique pour reset camera
|
||||
console.log("Reset camera");
|
||||
}, []);
|
||||
|
||||
@@ -203,9 +182,10 @@ export function EditorPage(): React.JSX.Element {
|
||||
setIsPlayerMode((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const handleTransformStart = useCallback(() => {
|
||||
if (!sceneData) return;
|
||||
const snapshot = sceneData.mapNodes.map((node, index) => ({
|
||||
const createSnapshot = useCallback((): ObjectTransform[] => {
|
||||
if (!sceneData) return [];
|
||||
|
||||
return sceneData.mapNodes.map((node, index) => ({
|
||||
uuid: `node-${index}`,
|
||||
position: {
|
||||
x: node.position[0],
|
||||
@@ -219,29 +199,19 @@ export function EditorPage(): React.JSX.Element {
|
||||
},
|
||||
scale: { x: node.scale[0], y: node.scale[1], z: node.scale[2] },
|
||||
}));
|
||||
historyManager.current.saveSnapshot(snapshot);
|
||||
}, [sceneData]);
|
||||
|
||||
const handleTransformStart = useCallback(() => {
|
||||
if (!sceneData) return;
|
||||
historyManager.current.saveSnapshot(createSnapshot());
|
||||
}, [createSnapshot, sceneData]);
|
||||
|
||||
const handleTransformEnd = useCallback(() => {
|
||||
if (!sceneData) return;
|
||||
const snapshot = 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] },
|
||||
}));
|
||||
historyManager.current.saveSnapshot(snapshot);
|
||||
historyManager.current.saveSnapshot(createSnapshot());
|
||||
setUndoCount(historyManager.current.getUndoCount());
|
||||
setRedoCount(historyManager.current.getRedoCount());
|
||||
}, [sceneData]);
|
||||
}, [createSnapshot, sceneData]);
|
||||
|
||||
const handleNodeTransform = useCallback(
|
||||
(nodeIndex: number, updatedNode: MapNode) => {
|
||||
@@ -445,7 +415,6 @@ export function EditorPage(): React.JSX.Element {
|
||||
/>
|
||||
</Canvas>
|
||||
|
||||
{/* EditorControls rendu en dehors du Canvas (HTML overlay) */}
|
||||
{sceneData && (
|
||||
<EditorControls
|
||||
transformMode={transformMode}
|
||||
Reference in New Issue
Block a user