Refactor: Move Export/Import to top nav, enable 3D scene controls

This commit is contained in:
2025-12-21 22:45:00 +09:00
parent c206a31af6
commit 30b5f94856
4 changed files with 183 additions and 165 deletions

19
App.tsx
View File

@@ -10,7 +10,7 @@ import {
IOLogicRule, LogicCondition, LogicAction, ProjectData IOLogicRule, LogicCondition, LogicAction, ProjectData
} from './types'; } from './types';
import { EditableObject } from './components/SceneObjects'; import { EditableObject } from './components/SceneObjects';
import { Layout as LayoutIcon, Cpu, Play, Pause, Square } from 'lucide-react'; import { Layout as LayoutIcon, Cpu, Play, Pause, Square, Download, Upload } from 'lucide-react';
// --- Simulation Manager (PLC Scan Cycle) --- // --- Simulation Manager (PLC Scan Cycle) ---
const SimulationLoop = ({ const SimulationLoop = ({
@@ -264,6 +264,19 @@ export default function App() {
<Cpu size={16} /> Logic <Cpu size={16} /> Logic
</button> </button>
</div> </div>
<div className="h-8 w-[1px] bg-gray-800 mx-2" />
<div className="flex bg-gray-950 p-1 rounded-xl border border-gray-800 shadow-inner">
<button onClick={handleExport} className="px-3 py-2 rounded-lg text-gray-400 hover:text-blue-400 hover:bg-gray-800 transition-all" title="Export Project">
<Download size={18} />
</button>
<label className="px-3 py-2 rounded-lg text-gray-400 hover:text-purple-400 hover:bg-gray-800 cursor-pointer transition-all flex items-center" title="Import Project">
<Upload size={18} />
<input type="file" className="hidden" accept=".json" onChange={handleImport} />
</label>
</div>
</div> </div>
{/* Global Controls */} {/* Global Controls */}
@@ -318,7 +331,7 @@ export default function App() {
<ambientLight intensity={0.5} /> <ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 5]} intensity={1} castShadow /> <directionalLight position={[10, 10, 5]} intensity={1} castShadow />
<Grid position={[0, -0.01, 0]} args={[20, 20]} cellSize={1} cellThickness={0.5} cellColor="#1e293b" sectionSize={5} sectionThickness={1} sectionColor="#334155" fadeDistance={30} infiniteGrid /> <Grid position={[0, -0.01, 0]} args={[20, 20]} cellSize={1} cellThickness={0.5} cellColor="#1e293b" sectionSize={5} sectionThickness={1} sectionColor="#334155" fadeDistance={30} infiniteGrid />
<OrbitControls makeDefault enabled={!isPlaying || !selectedId} /> <OrbitControls makeDefault />
{objects.map(obj => ( {objects.map(obj => (
<EditableObject <EditableObject
@@ -357,8 +370,6 @@ export default function App() {
onUpdateObject={(id, updates) => setObjects(prev => prev.map(o => o.id === id ? { ...o, ...updates } as SimObject : o))} onUpdateObject={(id, updates) => setObjects(prev => prev.map(o => o.id === id ? { ...o, ...updates } as SimObject : o))}
onSelect={setSelectedId} onSelect={setSelectedId}
onUpdatePortName={handleUpdatePortName} onUpdatePortName={handleUpdatePortName}
onExport={handleExport}
onImport={handleImport}
/> />
</div> </div>
) : ( ) : (

16
Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
# 1단계: 빌드 (Node.js)
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 2단계: 실행 (Nginx)
FROM nginx:stable-alpine
# 빌드된 파일들을 Nginx의 기본 경로로 복사
COPY --from=build /app/dist /usr/share/nginx/html
# (선택) 커스텀 nginx 설정을 넣고 싶다면 아래 주석 해제
# COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -21,8 +21,6 @@ interface SidebarProps {
onUpdateObject: (id: string, updates: Partial<SimObject>) => void; onUpdateObject: (id: string, updates: Partial<SimObject>) => void;
onSelect: (id: string | null) => void; onSelect: (id: string | null) => void;
onUpdatePortName: (type: 'input' | 'output', index: number, name: string) => void; onUpdatePortName: (type: 'input' | 'output', index: number, name: string) => void;
onExport: () => void;
onImport: (e: React.ChangeEvent<HTMLInputElement>) => void;
} }
export const Sidebar: React.FC<SidebarProps> = ({ export const Sidebar: React.FC<SidebarProps> = ({
@@ -34,9 +32,7 @@ export const Sidebar: React.FC<SidebarProps> = ({
onAddObject, onAddObject,
onDeleteObject, onDeleteObject,
onUpdateObject, onUpdateObject,
onUpdatePortName, onUpdatePortName
onExport,
onImport
}) => { }) => {
const [activeTab, setActiveTab] = useState<'properties' | 'system'>('properties'); const [activeTab, setActiveTab] = useState<'properties' | 'system'>('properties');
const selectedObject = objects.find(o => o.id === selectedId); const selectedObject = objects.find(o => o.id === selectedId);
@@ -165,16 +161,6 @@ export const Sidebar: React.FC<SidebarProps> = ({
{activeTab === 'system' && ( {activeTab === 'system' && (
<div className="flex-1 overflow-y-auto p-4 custom-scrollbar flex flex-col gap-6"> <div className="flex-1 overflow-y-auto p-4 custom-scrollbar flex flex-col gap-6">
<div className="grid grid-cols-2 gap-2">
<button onClick={onExport} className="bg-blue-600 hover:bg-blue-500 py-2.5 rounded-lg text-xs font-bold flex items-center justify-center gap-2 transition-all shadow-lg shadow-blue-900/20">
<Download size={14}/> EXPORT
</button>
<label className="bg-purple-600 hover:bg-purple-500 py-2.5 rounded-lg text-xs font-bold flex items-center justify-center gap-2 cursor-pointer transition-all shadow-lg shadow-purple-900/20">
<Upload size={14}/> IMPORT
<input type="file" className="hidden" accept=".json" onChange={onImport} />
</label>
</div>
<div className="space-y-4"> <div className="space-y-4">
<h3 className="text-[10px] font-black text-gray-500 uppercase tracking-widest border-b border-gray-800 pb-2">Hardware Mapping</h3> <h3 className="text-[10px] font-black text-gray-500 uppercase tracking-widest border-b border-gray-800 pb-2">Hardware Mapping</h3>

View File

@@ -5,8 +5,13 @@ import react from '@vitejs/plugin-react';
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
const env = loadEnv(mode, '.', ''); const env = loadEnv(mode, '.', '');
return { return {
base: '/motionsim/',
server: { server: {
port: 3000, port: 4173,
host: '0.0.0.0',
},
preview: {
port: 4173,
host: '0.0.0.0', host: '0.0.0.0',
}, },
plugins: [react()], plugins: [react()],