import React, { useEffect, useRef } from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
import * as THREE from 'three';
import useStore from './store.js';
import wsClient from './WebSocketClient.js';
import OfficeScene from './OfficeScene.jsx';
// ── Camera setup for true isometric ─────────────────────────────
// Orthographic with rotation: 45° around Y, then ~35.264° tilt down (atan(1/√2))
// But the React-idiomatic way: position the camera and use lookAt
// Simpler: use orbit controls locked to isometric-ish angle
function CameraSetup() {
// We use OrbitControls with limited rotation for a fixed-ish isometric feel
return null; // Controls are in the Canvas
}
// ── UI Overlay ──────────────────────────────────────────────────
function UIOverlay() {
const agents = useStore(s => s.agents);
const connected = useStore(s => s.wsConnected);
return (
Spatial Office
|
{connected ? 'Live' : 'Offline'}
|
{Object.keys(agents).length} agents
);
}
// ── App ─────────────────────────────────────────────────────────
export default function App() {
const hasConnected = useRef(false);
useEffect(() => {
if (hasConnected.current) return;
hasConnected.current = true;
// Try to connect to WebSocket backend
wsClient.connect();
// After 2 seconds, if no backend, start mock mode
const mockTimer = setTimeout(() => {
const state = useStore.getState();
if (!state.wsConnected) {
console.log('[App] No backend found, starting mock mode');
wsClient.startMock();
}
}, 2000);
return () => {
clearTimeout(mockTimer);
wsClient.disconnect();
};
}, []);
return (
);
}
// ── Ortho camera wrapper ────────────────────────────────────────
function OrthoCamera() {
const ref = useRef();
useEffect(() => {
if (!ref.current) return;
// On mount, set orthographic projection
const camera = ref.current;
const aspect = window.innerWidth / window.innerHeight;
const frustumSize = 18;
camera.left = -frustumSize * aspect / 2;
camera.right = frustumSize * aspect / 2;
camera.top = frustumSize / 2;
camera.bottom = -frustumSize / 2;
camera.near = 0.1;
camera.far = 200;
camera.position.set(14, 18, 14);
camera.lookAt(0, 0, 5);
camera.updateProjectionMatrix();
// Handle resize
const onResize = () => {
const a = window.innerWidth / window.innerHeight;
camera.left = -frustumSize * a / 2;
camera.right = frustumSize * a / 2;
camera.top = frustumSize / 2;
camera.bottom = -frustumSize / 2;
camera.updateProjectionMatrix();
};
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
return ;
}