fix: remove left footer symbols + right sidebar edge toggle/resize + rename tab to Einstellungen

This commit is contained in:
A0 Orchestrator
2026-07-03 11:51:07 +02:00
parent eefd99ee48
commit d80e5990e4
5 changed files with 159 additions and 86 deletions
+4 -1
View File
@@ -155,6 +155,7 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
// Desktop sidebar collapse // Desktop sidebar collapse
const [leftSidebarCollapsed, setLeftSidebarCollapsed] = useState(false); const [leftSidebarCollapsed, setLeftSidebarCollapsed] = useState(false);
const [leftbarWidth, setLeftbarWidth] = useState(200); const [leftbarWidth, setLeftbarWidth] = useState(200);
const [rightbarWidth, setRightbarWidth] = useState(300);
const [rightSidebarCollapsed, setRightSidebarCollapsed] = useState(false); const [rightSidebarCollapsed, setRightSidebarCollapsed] = useState(false);
const [settingsOpen, setSettingsOpen] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false);
const [activeDrawerTab, setActiveDrawerTab] = useState<DrawerTab>('tool'); const [activeDrawerTab, setActiveDrawerTab] = useState<DrawerTab>('tool');
@@ -1640,9 +1641,11 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
onKISuggestionClick={handleSuggestionClick} onKISuggestionClick={handleSuggestionClick}
kiLoading={kiLoading} kiLoading={kiLoading}
className={mobileRightOpen ? 'open' : ''} className={mobileRightOpen ? 'open' : ''}
onCollapse={() => setMobileRightOpen(false)} onCollapse={() => { setMobileRightOpen(false); setRightSidebarCollapsed(true); }}
collapsed={rightSidebarCollapsed} collapsed={rightSidebarCollapsed}
onToggleCollapse={() => setRightSidebarCollapsed((p) => !p)} onToggleCollapse={() => setRightSidebarCollapsed((p) => !p)}
rightbarWidth={rightbarWidth}
onWidthChange={setRightbarWidth}
onReorder={handleReorderLayer} onReorder={handleReorderLayer}
onAddSubLayer={handleAddSubLayer} onAddSubLayer={handleAddSubLayer}
onUpdateElement={handleUpdateElement} onUpdateElement={handleUpdateElement}
-11
View File
@@ -200,17 +200,6 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, sel
<div className="leftbar-resize-handle" onMouseDown={handleResizeMouseDown} /> <div className="leftbar-resize-handle" onMouseDown={handleResizeMouseDown} />
<div className="leftbar-footer">
<button className="leftbar-footer-btn" title="Plugin hinzufügen" aria-label="Plugin hinzufügen">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
</button>
<button className="leftbar-footer-btn" title="Werkzeugkasten anpassen" aria-label="Werkzeugkasten anpassen">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
</button>
<button className="leftbar-footer-btn" title="Hilfe (F1)" aria-label="Hilfe">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
</button>
</div>
</aside> </aside>
); );
}; };
+114 -70
View File
@@ -1,4 +1,4 @@
import React from 'react'; import React, { useCallback } from 'react';
import type { RightSidebarProps, RightPanel } from '../types/ui.types'; import type { RightSidebarProps, RightPanel } from '../types/ui.types';
import PropertiesPanel from './PropertiesPanel'; import PropertiesPanel from './PropertiesPanel';
import LayerPanel from './LayerPanel'; import LayerPanel from './LayerPanel';
@@ -6,8 +6,11 @@ import BlockLibrary from './BlockLibrary';
import BlockLibraryTree from './BlockLibraryTree'; import BlockLibraryTree from './BlockLibraryTree';
import KICopilot from './KICopilot'; import KICopilot from './KICopilot';
const MIN_RIGHTBAR_WIDTH = 200;
const MAX_RIGHTBAR_WIDTH = 500;
const tabs: Array<{ id: RightPanel; label: string; svg: React.ReactNode; badge?: number }> = [ const tabs: Array<{ id: RightPanel; label: string; svg: React.ReactNode; badge?: number }> = [
{ id: 'tool', label: 'Werkzeug', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg> }, { id: 'tool', label: 'Einstellungen', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg> },
{ id: 'layer', label: 'Layer', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg> }, { id: 'layer', label: 'Layer', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg> },
{ id: 'library', label: 'Bibliothek', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h7a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><line x1="3" y1="12" x2="21" y2="12"/></svg> }, { id: 'library', label: 'Bibliothek', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h7a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><line x1="3" y1="12" x2="21" y2="12"/></svg> },
{ id: 'ki', label: 'KI', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>, badge: 2 }, { id: 'ki', label: 'KI', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>, badge: 2 },
@@ -19,8 +22,8 @@ const RightSidebar: React.FC<RightSidebarProps> = ({
activeLayerId, onSelectLayer, onAddLayer, onToggleLayer, activeLayerId, onSelectLayer, onAddLayer, onToggleLayer,
onDeleteLayer, onRenameLayer, onDuplicateLayer, onToggleLock, onDeleteLayer, onRenameLayer, onDuplicateLayer, onToggleLock,
onReorder, onAddSubLayer, onReorder, onAddSubLayer,
onElementsDeleted, onToggleElementVisible, onElementsDeleted, onToggleElementVisible,
elements, elements,
onRenameBlock, onDuplicateBlock, onDeleteBlock, onSvgImport, onSaveGroupAsBlock, onRenameBlock, onDuplicateBlock, onDeleteBlock, onSvgImport, onSaveGroupAsBlock,
onBlockCategoryChange, onBlockSearch, onDragBlock, onBlockCategoryChange, onBlockSearch, onDragBlock,
kiMessages, kiSuggestions, onKISend, onKISuggestionClick, kiLoading, onUpdateElement, kiMessages, kiSuggestions, onKISend, onKISuggestionClick, kiLoading, onUpdateElement,
@@ -33,76 +36,117 @@ const RightSidebar: React.FC<RightSidebarProps> = ({
token, token,
unit = 'mm', unit = 'mm',
scaleFactor = 1, scaleFactor = 1,
rightbarWidth = 300,
onWidthChange,
}) => { }) => {
const handleResizeMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const startClientX = e.clientX;
const startWidth = rightbarWidth;
const handleMouseMove = (ev: MouseEvent) => {
const delta = startClientX - ev.clientX;
const newWidth = Math.min(MAX_RIGHTBAR_WIDTH, Math.max(MIN_RIGHTBAR_WIDTH, startWidth + delta));
document.documentElement.style.setProperty('--rightbar-w', newWidth + 'px');
if (onWidthChange) {
onWidthChange(newWidth);
}
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.body.style.cursor = '';
};
document.body.style.cursor = 'col-resize';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}, [rightbarWidth, onWidthChange]);
return ( return (
<aside className={`rightbar${className ? ' ' + className : ''}${collapsed ? ' rightbar-collapsed' : ''}`} aria-label="Eigenschaften-Panels"> <aside className={`rightbar${className ? ' ' + className : ''}${collapsed ? ' rightbar-collapsed' : ''}`} aria-label="Eigenschaften-Panels">
<div className="rightbar-header"> <div className="rightbar-resize-handle" onMouseDown={handleResizeMouseDown} />
<span className="rightbar-title">Eigenschaften</span> <button className="rightbar-edge-toggle" aria-label="Rechte Leiste ein-/ausklappen" onClick={collapsed ? onToggleCollapse : onCollapse} title="Rechte Leiste ein-/ausklappen">
<button className="rightbar-toggle" aria-label="Rechte Leiste ein-/ausklappen" onClick={collapsed ? onToggleCollapse : onCollapse} title="Rechte Leiste ein-/ausklappen"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg> </button>
</button> {!collapsed && (
</div> <>
<div className="rightbar-tabs" role="tablist"> <div className="rightbar-header">
{tabs.map((tab) => ( <span className="rightbar-title">Eigenschaften</span>
<button </div>
key={tab.id} <div className="rightbar-tabs" role="tablist">
className={`rightbar-tab${activePanel === tab.id ? ' active' : ''}`} {tabs.map((tab) => (
data-panel={tab.id} <button
role="tab" key={tab.id}
aria-selected={activePanel === tab.id} className={`rightbar-tab${activePanel === tab.id ? ' active' : ''}`}
onClick={() => onPanelChange(tab.id)} data-panel={tab.id}
> role="tab"
{tab.svg} aria-selected={activePanel === tab.id}
<span>{tab.label}</span> onClick={() => onPanelChange(tab.id)}
{tab.badge !== undefined && <span className="rightbar-tab-badge">{tab.badge}</span>} >
</button> {tab.svg}
))} <span>{tab.label}</span>
</div> {tab.badge !== undefined && <span className="rightbar-tab-badge">{tab.badge}</span>}
</button>
<div className="rightbar-content"> ))}
<div className={`rightbar-panel${activePanel === 'tool' ? ' active' : ''}`} data-panel-content="tool"> </div>
{activePanel === 'tool' && <PropertiesPanel selectedElement={selectedElement} layers={layers} unit={unit} scaleFactor={scaleFactor} onDelete={selectedElement && onElementsDeleted ? () => onElementsDeleted([selectedElement.id]) : undefined} onUpdateProperty={(key, value) => { <div className="rightbar-content">
if (!selectedElement || !onUpdateElement) return; <div className={`rightbar-panel${activePanel === 'tool' ? ' active' : ''}`} data-panel-content="tool">
const updated = { ...selectedElement }; {activePanel === 'tool' && selectedElement && onUpdateElement && (
if (key === 'x' || key === 'y' || key === 'width' || key === 'height') { <PropertiesPanel selectedElement={selectedElement} layers={layers} unit={unit} scaleFactor={scaleFactor} onDelete={selectedElement && onElementsDeleted ? () => onElementsDeleted([selectedElement.id]) : undefined} onUpdateProperty={(key, value) => {
const numVal = typeof value === 'string' ? parseFloat(value.replace(/[^0-9.\-]/g, '')) || 0 : value as number; if (!selectedElement || !onUpdateElement) return;
if (key === 'x') updated.x = numVal; const updated = { ...selectedElement };
else if (key === 'y') updated.y = numVal; if (key === 'x' || key === 'y' || key === 'width' || key === 'height') {
else if (key === 'width') updated.width = numVal; const numVal = typeof value === 'string' ? parseFloat(value.replace(/[^0-9.\-.]/g, '')) || 0 : value as number;
else if (key === 'height') updated.height = numVal; if (key === 'x') updated.x = numVal;
} else if (key === 'layerId') { else if (key === 'y') updated.y = numVal;
updated.layerId = value as string; else if (key === 'width') updated.width = numVal;
} else if (key === 'rotation') { else if (key === 'height') updated.height = numVal;
const rotVal = typeof value === 'string' ? parseFloat(value.replace(/[^0-9.\-]/g, '')) || 0 : value as number; } else if (key === 'layerId') {
updated.properties = { ...updated.properties, rotation: rotVal }; updated.layerId = value as string;
} else { } else if (key === 'rotation') {
updated.properties = { ...updated.properties, [key]: value }; const rotVal = typeof value === 'string' ? parseFloat(value.replace(/[^0-9.\-.]/g, '')) || 0 : value as number;
} updated.properties = { ...updated.properties, rotation: rotVal };
onUpdateElement(updated); } else {
}} />} updated.properties = { ...updated.properties, [key]: value };
</div> }
<div className={`rightbar-panel${activePanel === 'layer' ? ' active' : ''}`} data-panel-content="layer"> onUpdateElement(updated);
{activePanel === 'layer' && <LayerPanel layers={layers} elements={elements} onElementsDeleted={onElementsDeleted} onToggleElementVisible={onToggleElementVisible} activeLayerId={activeLayerId} onSelectLayer={onSelectLayer ?? (() => {})} onSelectElement={onSelectElement} selectedElementIds={selectedElementIds} groups={groups} onAddLayer={onAddLayer ?? (() => {})} onToggleLayer={onToggleLayer ?? (() => {})} onDeleteLayer={onDeleteLayer} onRenameLayer={onRenameLayer} onDuplicateLayer={onDuplicateLayer} onToggleLock={onToggleLock} onReorder={onReorder} onAddSubLayer={onAddSubLayer} />} }} />
</div>
<div className={`rightbar-panel${activePanel === 'library' ? ' active' : ''}`} data-panel-content="library">
{activePanel === 'library' && (
<>
<BlockLibrary blocks={blocks} category="Alle" onCategoryChange={onBlockCategoryChange ?? (() => {})} onSearch={onBlockSearch ?? (() => {})} onDragBlock={onDragBlock ?? (() => {})} onRenameBlock={onRenameBlock} onDuplicateBlock={onDuplicateBlock} onDeleteBlock={onDeleteBlock} onSvgImport={onSvgImport} onSaveGroupAsBlock={onSaveGroupAsBlock} />
{token && (
<BlockLibraryTree
token={token}
onBlockDragStart={(_blockId, _blockData) => {
// Global blocks use a different drag type — handled in CanvasArea drop
}}
/>
)} )}
</> {activePanel === 'tool' && !selectedElement && (
)} <div className="tool-settings-panel">
</div> <p style={{ padding: '12px', color: 'var(--color-text-muted)', fontSize: '13px' }}>
<div className={`rightbar-panel${activePanel === 'ki' ? ' active' : ''}`} data-panel-content="ki"> Kein Objekt ausgewählt. Wählen Sie ein Objekt auf der Zeichenfläche um dessen Eigenschaften zu bearbeiten.
{activePanel === 'ki' && <KICopilot messages={kiMessages ?? []} suggestions={kiSuggestions ?? []} onSend={onKISend ?? (() => {})} onSuggestionClick={onKISuggestionClick ?? (() => {})} loading={kiLoading} />} </p>
</div> </div>
</div> )}
</div>
<div className={`rightbar-panel${activePanel === 'layer' ? ' active' : ''}`} data-panel-content="layer">
{activePanel === 'layer' && <LayerPanel layers={layers} elements={elements} onElementsDeleted={onElementsDeleted} onToggleElementVisible={onToggleElementVisible} activeLayerId={activeLayerId} onSelectLayer={onSelectLayer ?? (() => {})} onSelectElement={onSelectElement} selectedElementIds={selectedElementIds} groups={groups} onAddLayer={onAddLayer ?? (() => {})} onToggleLayer={onToggleLayer ?? (() => {})} onDeleteLayer={onDeleteLayer} onRenameLayer={onRenameLayer} onDuplicateLayer={onDuplicateLayer} onToggleLock={onToggleLock} onReorder={onReorder} onAddSubLayer={onAddSubLayer} />}
</div>
<div className={`rightbar-panel${activePanel === 'library' ? ' active' : ''}`} data-panel-content="library">
{activePanel === 'library' && (
<>
<BlockLibrary blocks={blocks} category="Alle" onCategoryChange={onBlockCategoryChange ?? (() => {})} onSearch={onBlockSearch ?? (() => {})} onDragBlock={onDragBlock ?? (() => {})} onRenameBlock={onRenameBlock} onDuplicateBlock={onDuplicateBlock} onDeleteBlock={onDeleteBlock} onSvgImport={onSvgImport} onSaveGroupAsBlock={onSaveGroupAsBlock} />
{token && (
<BlockLibraryTree
token={token}
onBlockDragStart={(_blockId, _blockData) => {
// Global blocks use a different drag type — handled in CanvasArea drop
}}
/>
)}
</>
)}
</div>
<div className={`rightbar-panel${activePanel === 'ki' ? ' active' : ''}`} data-panel-content="ki">
{activePanel === 'ki' && <KICopilot messages={kiMessages ?? []} suggestions={kiSuggestions ?? []} onSend={onKISend ?? (() => {})} onSuggestionClick={onKISuggestionClick ?? (() => {})} loading={kiLoading} />}
</div>
</div>
</>
)}
</aside> </aside>
); );
}; };
+39 -4
View File
@@ -2255,12 +2255,47 @@ body.drawer-open { overflow: hidden; }
.rightbar-toggle:hover { background: var(--color-surface-2); color: var(--color-text); } .rightbar-toggle:hover { background: var(--color-surface-2); color: var(--color-text); }
.rightbar-toggle svg { width: 14px; height: 14px; } .rightbar-toggle svg { width: 14px; height: 14px; }
.app-body.right-collapsed .rightbar { padding: 4px; } .rightbar-edge-toggle {
.app-body.right-collapsed .rightbar-header { justify-content: center; } position: absolute;
.app-body.right-collapsed .rightbar-title { display: none; } left: 0;
top: 0;
bottom: 0;
width: 20px;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 10px;
background: var(--color-surface-2);
border-right: 1px solid var(--color-border);
cursor: pointer;
z-index: 10;
color: var(--color-text-muted);
transition: color var(--t-fast);
}
.rightbar-edge-toggle:hover { color: var(--color-text); }
.rightbar-edge-toggle svg { width: 14px; height: 14px; }
.rightbar-resize-handle {
position: absolute;
left: -2px;
top: 0;
bottom: 0;
width: 4px;
cursor: col-resize;
z-index: 11;
}
.rightbar-resize-handle:hover {
background: var(--color-primary);
}
.rightbar-content { padding-left: 24px; }
.rightbar-tabs { margin-left: 20px; }
.rightbar-header { margin-left: 20px; }
.app-body.right-collapsed .rightbar-header,
.app-body.right-collapsed .rightbar-tabs, .app-body.right-collapsed .rightbar-tabs,
.app-body.right-collapsed .rightbar-content { display: none; } .app-body.right-collapsed .rightbar-content { display: none; }
.app-body.right-collapsed .rightbar-toggle { display: grid !important; transform: rotate(180deg); } .app-body.right-collapsed .rightbar-edge-toggle svg { transform: rotate(180deg); }
.leftbar-collapsed .tool-section, .leftbar-collapsed .tool-section,
.leftbar-collapsed .leftbar-title, .leftbar-collapsed .leftbar-title,
+2
View File
@@ -209,6 +209,8 @@ export interface RightSidebarProps {
onToggleCollapse?: () => void; onToggleCollapse?: () => void;
unit?: UnitType; unit?: UnitType;
scaleFactor?: number; scaleFactor?: number;
rightbarWidth?: number;
onWidthChange?: (width: number) => void;
} }
export interface PropertiesPanelProps { export interface PropertiesPanelProps {