243 lines
16 KiB
TypeScript
243 lines
16 KiB
TypeScript
import React from 'react';
|
|
import { useUIMode } from '../services/uiModeService';
|
|
import { useT } from '../i18n/strings';
|
|
import type { RibbonBarProps, RibbonTab } from '../types/ui.types';
|
|
import { pluginRegistry } from '../plugins';
|
|
import { V2_TOOLS_ENABLED } from '../kernel/featureFlags';
|
|
|
|
const tabs: Array<{ id: RibbonTab; label: string; svg: React.ReactNode }> = [
|
|
{ id: 'start', label: 'Start', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg> },
|
|
{ id: 'draw', label: 'Zeichnen', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg> },
|
|
{ id: 'view', label: 'Ansicht', svg: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></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> },
|
|
];
|
|
|
|
/** V2-Werkzeug-Kategorien → Gruppen-Label im Zeichnen-Tab. */
|
|
const V2_GROUP_LABELS: Record<string, string> = {
|
|
canvas: 'Zeichnen & Ändern',
|
|
format: 'Bemaßung & Text',
|
|
insert: 'Bestuhlung & Traversen',
|
|
};
|
|
|
|
const RibbonBar: React.FC<RibbonBarProps> = ({
|
|
activeTab, onTabChange, onAction,
|
|
canvasBgColor = '#1e1e2e', gridColor = '#3a3a4a', rulerEnabled = true,
|
|
onCanvasBgColorChange, onGridColorChange, onToggleRuler,
|
|
onIsV2ToolActive,
|
|
}) => {
|
|
const uiMode = useUIMode();
|
|
|
|
/** Rendert die V2-Werkzeuge einer Kategorie als Button-Gruppe. */
|
|
const renderV2Group = (category: string) => {
|
|
const tools = pluginRegistry.getToolsV2()
|
|
.filter((tool) => tool.manifest.ribbonTab === category)
|
|
.filter((tool) => uiMode === 'pro' || (tool.manifest.tags ?? []).includes('basic'));
|
|
if (tools.length === 0) return null;
|
|
return (
|
|
<div className="ribbon-group">
|
|
<div className="ribbon-group-btns">
|
|
{tools.map((tool) => {
|
|
const isActive = onIsV2ToolActive?.(tool.manifest.id);
|
|
return (
|
|
<button
|
|
key={tool.manifest.id}
|
|
data-v2tool={tool.manifest.id}
|
|
title={tool.manifest.description ?? tool.manifest.label}
|
|
onClick={() => onAction(`v2tool:${tool.manifest.id}`)}
|
|
className={`ribbon-v2-btn${isActive ? ' active' : ''}`}
|
|
>
|
|
<span className="ribbon-v2-icon">{tool.manifest.icon}</span>
|
|
<span className="ribbon-v2-label">{tool.manifest.label}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
{category === 'insert' && (
|
|
<button
|
|
className="ribbon-btn"
|
|
title="Stückliste Traversen"
|
|
onClick={() => onAction('bom')}
|
|
>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3h18v18H3z"/><path d="M3 9h18"/><path d="M3 15h18"/><path d="M9 3v18"/></svg>
|
|
<span>Stückliste</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
<div className="ribbon-group-label">{V2_GROUP_LABELS[category] ?? category}</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<nav className="ribbon" role="navigation" aria-label="Hauptwerkzeuge">
|
|
<div className="ribbon-tabs" role="tablist">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
className={`ribbon-tab${activeTab === tab.id ? ' active' : ''}`}
|
|
role="tab"
|
|
aria-selected={activeTab === tab.id}
|
|
data-tab={tab.id}
|
|
style={tab.id === 'ki' ? { color: 'var(--color-ki)' } : undefined}
|
|
onClick={() => onTabChange(tab.id)}
|
|
>
|
|
{tab.svg}
|
|
<span>{tab.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<div className="ribbon-content" id="ribbon-content">
|
|
{/* ===== START TAB ===== */}
|
|
{activeTab === 'start' && (
|
|
<>
|
|
<div className="ribbon-group">
|
|
<div className="ribbon-group-btns">
|
|
<button className="ribbon-btn" title="Neues Projekt (Strg+N)" onClick={() => onAction('new')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
|
|
<span>Neu</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Öffnen (Strg+O)" onClick={() => onAction('open')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
|
<span>Öffnen</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Speichern (Strg+S)" onClick={() => onAction('save')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
|
<span>Speichern</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Import (DXF, SVG, JSON)" onClick={() => onAction('import')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
|
<span>Import</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Export (DXF, SVG, PDF, PNG, JSON)" onClick={() => onAction('export')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
|
<span>Export</span>
|
|
</button>
|
|
</div>
|
|
<div className="ribbon-group-label">Datei</div>
|
|
</div>
|
|
<div className="ribbon-group">
|
|
<div className="ribbon-group-btns">
|
|
<button className="ribbon-btn" title="Rückgängig (Strg+Z)" onClick={() => onAction('undo')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-15-6.7L3 13"/></svg>
|
|
<span>Rückgängig</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Wiederherstellen (Strg+Y)" onClick={() => onAction('redo')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 15-6.7L21 13"/></svg>
|
|
<span>Wiederherstellen</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Kopieren (Strg+C)" onClick={() => onAction('copy')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
<span>Kopieren</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Einfügen (Strg+V)" onClick={() => onAction('paste')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1"/></svg>
|
|
<span>Einfügen</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Gruppieren (G)" onClick={() => onAction('group')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M8 12h8"/><path d="M12 8v8"/></svg>
|
|
<span>Gruppieren</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Degruppieren (Strg+G)" onClick={() => onAction('ungroup')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>
|
|
<span>Degruppieren</span>
|
|
</button>
|
|
</div>
|
|
<div className="ribbon-group-label">Bearbeiten</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* ===== ZEICHNEN TAB ===== */}
|
|
{activeTab === 'draw' && V2_TOOLS_ENABLED && (
|
|
<div className="ribbon-draw-content">
|
|
{renderV2Group('canvas')}
|
|
{renderV2Group('format')}
|
|
{renderV2Group('insert')}
|
|
</div>
|
|
)}
|
|
|
|
{/* ===== ANSICHT TAB ===== */}
|
|
{activeTab === 'view' && (
|
|
<>
|
|
<div className="ribbon-group">
|
|
<div className="ribbon-group-btns">
|
|
<button className="ribbon-btn" title="Zoom anpassen (F)" onClick={() => onAction('zoom-fit')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>
|
|
<span>Zoom Fit</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="100% (1:1)" onClick={() => onAction('zoom-100')}>
|
|
<span>100%</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Raster ein/aus (F7)" onClick={() => onAction('grid')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/><path d="M9 3v18"/><path d="M15 3v18"/></svg>
|
|
<span>Raster</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Layer-Manager" onClick={() => onAction('layer')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" 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>
|
|
<span>Layer</span>
|
|
</button>
|
|
</div>
|
|
<div className="ribbon-group-label">Ansicht</div>
|
|
</div>
|
|
<div className="ribbon-group">
|
|
<div className="ribbon-group-btns">
|
|
<button className="ribbon-btn" title="Hintergrund-Grundriss importieren" onClick={() => onAction('background-import')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
|
|
<span>Hintergrund</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Bild einfügen" onClick={() => onAction('insert-image')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
|
<span>Bild</span>
|
|
</button>
|
|
</div>
|
|
<div className="ribbon-group-label">Hintergrund</div>
|
|
</div>
|
|
<div className="ribbon-group">
|
|
<div className="ribbon-group-btns">
|
|
<label className="ribbon-color-label">Canvas</label>
|
|
<input type="color" value={canvasBgColor} onChange={(e) => onCanvasBgColorChange?.(e.target.value)} aria-label="Canvas-Hintergrundfarbe" />
|
|
<label className="ribbon-color-label">Raster</label>
|
|
<input type="color" value={gridColor} onChange={(e) => onGridColorChange?.(e.target.value)} aria-label="Rasterfarbe" />
|
|
<button className="ribbon-btn" title="Lineal ein/aus" onClick={() => onToggleRuler?.()}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21.3 8.7l-7.6-7.6a1 1 0 0 0-1.4 0l-9 9a1 1 0 0 0 0 1.4l7.6 7.6a1 1 0 0 0 1.4 0l9-9a1 1 0 0 0 0-1.4z"/></svg>
|
|
<span>Lineal</span>
|
|
</button>
|
|
</div>
|
|
<div className="ribbon-group-label">Darstellung</div>
|
|
</div>
|
|
<div className="ribbon-group">
|
|
<div className="ribbon-group-btns">
|
|
<button className="ribbon-btn" title="Historie" onClick={() => onAction('history')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3v5h5"/><path d="M3.05 13A9 9 0 1 0 6 5.3L3 8"/><path d="M12 7v5l4 2"/></svg>
|
|
<span>Historie</span>
|
|
</button>
|
|
<button className="ribbon-btn" title="Gäste-Verwaltung" onClick={() => onAction('guests')}>
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
|
<span>Gäste</span>
|
|
</button>
|
|
</div>
|
|
<div className="ribbon-group-label">Extras</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* ===== KI TAB ===== */}
|
|
{activeTab === 'ki' && (
|
|
<>
|
|
<div className="ribbon-group">
|
|
<div className="ribbon-group-btns">
|
|
<span style={{ fontSize: '12px', color: 'var(--color-ki, #8a8aff)', padding: '4px 8px' }}>
|
|
KI Copilot — Befehle über die Befehlszeile unten (Strg+K)
|
|
</span>
|
|
</div>
|
|
<div className="ribbon-group-label">Künstliche Intelligenz</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</nav>
|
|
);
|
|
};
|
|
|
|
export default RibbonBar;
|