feat: units system — mm/cm/m switchable, configurable grid, real measurements, formatted display

This commit is contained in:
A0 Orchestrator
2026-07-02 08:46:07 +02:00
parent 72e3d1da24
commit 0463a793bc
14 changed files with 661 additions and 76 deletions
+74 -2
View File
@@ -26,10 +26,11 @@ import { getCommandRegistry } from './services/commandRegistry';
import { importFile, type ImportResult } from './services/importService';
import { exportProject, downloadBlob, type ExportFormat } from './services/exportService';
import type { ProjectData } from './types/cad.types';
import { loadProjectDataTyped, createElementTyped, updateElement, deleteElement as apiDeleteElement, createLayerTyped, updateLayer, deleteLayer as apiDeleteLayer, createBlockTyped, updateBlock, deleteBlock as apiDeleteBlock, aiChat } from './services/api';
import { loadProjectDataTyped, createElementTyped, updateElement, deleteElement as apiDeleteElement, createLayerTyped, updateLayer, deleteLayer as apiDeleteLayer, createBlockTyped, updateBlock, deleteBlock as apiDeleteBlock, aiChat, getSetting, setSetting } from './services/api';
import { useYjsBinding } from './crdt';
import { registerBuiltinPlugins, pluginRegistry } from './plugins';
import type { PluginContext } from './plugins';
import type { UnitType } from './utils/format';
import './styles.css';
import './styles/auth.css';
import { useAuth } from './contexts/AuthContext';
@@ -99,6 +100,11 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
const [snapEnabled, setSnapEnabled] = useState(true);
const [polarEnabled, setPolarEnabled] = useState(false);
// Units
const [unit, setUnit] = useState<UnitType>('mm');
const [gridSize, setGridSize] = useState<number>(20);
const [scaleFactor, setScaleFactor] = useState<number>(1);
// Right sidebar
const [activeRightPanel, setActiveRightPanel] = useState<RightPanel>('tool');
const [selectedElement, setSelectedElement] = useState<CADElement | null>(null);
@@ -328,6 +334,53 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectId, token]);
// Load unit settings from backend on mount
React.useEffect(() => {
if (!token) return;
let cancelled = false;
(async () => {
try {
const [unitSetting, gridSetting, scaleSetting] = await Promise.all([
getSetting(token, 'cad.unit').catch(() => null),
getSetting(token, 'cad.gridSize').catch(() => null),
getSetting(token, 'cad.scaleFactor').catch(() => null),
]);
if (cancelled) return;
if (unitSetting && (unitSetting.value === 'mm' || unitSetting.value === 'cm' || unitSetting.value === 'm')) {
setUnit(unitSetting.value as UnitType);
}
if (gridSetting) {
const gs = parseFloat(gridSetting.value);
if (!isNaN(gs) && gs > 0) setGridSize(gs);
}
if (scaleSetting) {
const sf = parseFloat(scaleSetting.value);
if (!isNaN(sf) && sf > 0) setScaleFactor(sf);
}
} catch {
// Settings not yet stored — use defaults
}
})();
return () => { cancelled = true; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);
// Save unit settings to backend when they change
const handleUnitChange = useCallback((newUnit: UnitType) => {
setUnit(newUnit);
if (token) setSetting(token, 'cad.unit', newUnit).catch(() => {});
}, [token]);
const handleGridSizeChange = useCallback((newSize: number) => {
setGridSize(newSize);
if (token) setSetting(token, 'cad.gridSize', String(newSize)).catch(() => {});
}, [token]);
const handleScaleFactorChange = useCallback((newFactor: number) => {
setScaleFactor(newFactor);
if (token) setSetting(token, 'cad.scaleFactor', String(newFactor)).catch(() => {});
}, [token]);
// Sync remote → local: when Yjs data changes from other users, update local state
React.useEffect(() => {
if (collab.status !== 'connected') return;
@@ -1471,6 +1524,8 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
onOpenSettings={() => setSettingsOpen(true)}
onOpenLeftDrawer={() => setMobileLeftOpen(true)}
onOpenRightDrawer={() => setMobileRightOpen(true)}
unit={unit}
onUnitChange={handleUnitChange}
/>
<RibbonBar
activeTab={activeRibbonTab}
@@ -1532,6 +1587,9 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
bgConfig={bgConfig}
remoteCursors={collab.cursors}
groups={groups}
unit={unit}
gridSize={gridSize}
scaleFactor={scaleFactor}
/>
<RightSidebar
activePanel={activeRightPanel}
@@ -1574,6 +1632,8 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
groups={groups}
onSelectElement={handleSelectElement}
token={token}
unit={unit}
scaleFactor={scaleFactor}
/>
</div>
<CommandLine
@@ -1595,6 +1655,9 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
onTogglePolar={handleTogglePolar}
onToggleGrid={handleToggleGrid}
seatCount={seatCount}
unit={unit}
scaleFactor={scaleFactor}
onUnitChange={handleUnitChange}
/>
<MobileDrawers
leftOpen={mobileLeftOpen}
@@ -1604,7 +1667,16 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
onCloseRight={() => setMobileRightOpen(false)}
onRightTabChange={setActiveDrawerTab}
/>
<SettingsModal open={settingsOpen} onClose={() => setSettingsOpen(false)} />
<SettingsModal
open={settingsOpen}
onClose={() => setSettingsOpen(false)}
unit={unit}
gridSize={gridSize}
scaleFactor={scaleFactor}
onUnitChange={handleUnitChange}
onGridSizeChange={handleGridSizeChange}
onScaleFactorChange={handleScaleFactorChange}
/>
<BackgroundImport
open={bgImportOpen}
onClose={() => setBgImportOpen(false)}