05ccebad8d
- Restore TS frontend source (canvas, tools, CRDT, components, services) - Keep 5 JSX components that TS code depends on (CADCanvas, LayerPanel, BlockLibrary, PluginRegistry, Toolbar) - Keep JS services (api.js, blockService.js) that components depend on - Fix vite/plugin-react version mismatch (downgrade to v4 for vite 6) - Add axios dependency - Skip tsc in build script (vite/esbuild handles transpilation) - Fix index.html lang=de, favicon path - Add vite proxy config for /api and /ws - Backend unchanged (already from deployed containers)
129 lines
4.2 KiB
TypeScript
129 lines
4.2 KiB
TypeScript
import React, { useState, useEffect, useRef } from 'react';
|
|
import './SidePanel.css';
|
|
import LayerPanel from './LayerPanel';
|
|
import BlockLibrary from './BlockLibrary';
|
|
import PropertiesPanel from './PropertiesPanel/PropertiesPanel';
|
|
|
|
interface SidePanelProps {
|
|
canvasRef?: React.RefObject<unknown>;
|
|
yjsDoc?: React.RefObject<unknown>;
|
|
}
|
|
|
|
const SidePanel: React.FC<SidePanelProps> = ({ canvasRef, yjsDoc }) => {
|
|
const [activeTab, setActiveTab] = useState('blocks');
|
|
const [layers, setLayers] = useState<unknown[]>([]);
|
|
const [activeLayer, setActiveLayer] = useState('');
|
|
const localCanvasRef = useRef(null);
|
|
const localYjsDocRef = useRef(null);
|
|
|
|
// Use props if provided, otherwise fall back to local refs
|
|
const effectiveCanvasRef = (canvasRef ?? localCanvasRef) as React.RefObject<unknown>;
|
|
const effectiveYjsDocRef = (yjsDoc ?? localYjsDocRef) as React.RefObject<unknown>;
|
|
|
|
const tabs = [
|
|
{ id: 'blocks', label: 'Blocks', icon: '🧱' },
|
|
{ id: 'layers', label: 'Layers', icon: '📄' },
|
|
{ id: 'properties', label: 'Properties', icon: '⚙️' },
|
|
{ id: 'copilot', label: 'KI Copilot', icon: '🤖' },
|
|
];
|
|
|
|
// Observe changes in yjsDoc.layers
|
|
useEffect(() => {
|
|
const yjsDocInstance = effectiveYjsDocRef?.current as {
|
|
layers?: {
|
|
forEach: (cb: (layer: Record<string, unknown>, id: string) => void) => void;
|
|
observe: (cb: () => void) => void;
|
|
unobserve: (cb: () => void) => void;
|
|
};
|
|
} | null;
|
|
const yjsLayers = yjsDocInstance?.layers;
|
|
if (!yjsLayers) return;
|
|
|
|
// Function to update layers from yjsDoc
|
|
const updateLayers = () => {
|
|
const layersArray: Array<Record<string, unknown> & { id: string }> = [];
|
|
yjsLayers.forEach((layer, id) => {
|
|
layersArray.push({ ...layer, id });
|
|
});
|
|
setLayers(layersArray);
|
|
|
|
// Set active layer if none is set or if current active layer doesn't exist
|
|
if (!activeLayer || !layersArray.some(layer => layer.id === activeLayer)) {
|
|
if (layersArray.length > 0) {
|
|
setActiveLayer(layersArray[0].id);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Initial update
|
|
updateLayers();
|
|
|
|
// Observe changes
|
|
yjsLayers.observe(updateLayers);
|
|
|
|
// Cleanup observer on unmount
|
|
return () => {
|
|
yjsLayers.unobserve(updateLayers);
|
|
};
|
|
}, [activeLayer, effectiveYjsDocRef]);
|
|
|
|
const renderContent = () => {
|
|
switch (activeTab) {
|
|
case 'blocks':
|
|
return <BlockLibrary canvasRef={effectiveCanvasRef as never} yjsDoc={(effectiveYjsDocRef as React.MutableRefObject<unknown>).current} />;
|
|
case 'layers':
|
|
return (
|
|
<LayerPanel
|
|
layers={layers as never}
|
|
setLayers={setLayers as never}
|
|
activeLayer={activeLayer}
|
|
setActiveLayer={setActiveLayer}
|
|
yjsDoc={(effectiveYjsDocRef as React.MutableRefObject<unknown>).current}
|
|
/>
|
|
);
|
|
case 'properties':
|
|
return (
|
|
<PropertiesPanel
|
|
canvasRef={effectiveCanvasRef as never}
|
|
yjsDocRef={effectiveYjsDocRef as never}
|
|
/>
|
|
);
|
|
case 'copilot':
|
|
return <div className="panel-content">KI Copilot content goes here</div>;
|
|
default:
|
|
return <div className="panel-content">Select a tab</div>;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="side-panel" role="complementary" aria-label="Properties Panel">
|
|
<div className="panel-tabs" role="tablist" aria-label="Panel Tabs">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
className={`panel-tab ${activeTab === tab.id ? 'active' : ''}`}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
role="tab"
|
|
aria-selected={activeTab === tab.id}
|
|
aria-controls={`panel-${tab.id}`}
|
|
id={`tab-${tab.id}`}
|
|
>
|
|
<span className="tab-icon" aria-hidden="true">{tab.icon}</span>
|
|
<span className="tab-label">{tab.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div
|
|
className="panel-content-container"
|
|
role="tabpanel"
|
|
id={`panel-${activeTab}`}
|
|
aria-labelledby={`tab-${activeTab}`}
|
|
>
|
|
{renderContent()}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SidePanel;
|