fix(CAD-13): remove duplicate tool ribbon - drawing tools only in left sidebar (like real CAD), ribbon reduced to Start/Ansicht/KI, Stueckliste moved to Ansicht extras
This commit is contained in:
@@ -1,72 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useUIMode } from '../services/uiModeService';
|
|
||||||
import { useT } from '../i18n/strings';
|
|
||||||
import type { RibbonBarProps, RibbonTab } from '../types/ui.types';
|
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 }> = [
|
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: '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: '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> },
|
{ 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> = ({
|
const RibbonBar: React.FC<RibbonBarProps> = ({
|
||||||
activeTab, onTabChange, onAction,
|
activeTab, onTabChange, onAction,
|
||||||
canvasBgColor = '#1e1e2e', gridColor = '#3a3a4a', rulerEnabled = true,
|
canvasBgColor = '#1e1e2e', gridColor = '#3a3a4a', rulerEnabled = true,
|
||||||
onCanvasBgColorChange, onGridColorChange, onToggleRuler,
|
onCanvasBgColorChange, onGridColorChange, onToggleRuler,
|
||||||
onIsV2ToolActive,
|
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 (
|
return (
|
||||||
<nav className="ribbon" role="navigation" aria-label="Hauptwerkzeuge">
|
<nav className="ribbon" role="navigation" aria-label="Hauptwerkzeuge">
|
||||||
<div className="ribbon-tabs" role="tablist">
|
<div className="ribbon-tabs" role="tablist">
|
||||||
@@ -147,15 +93,6 @@ const RibbonBar: React.FC<RibbonBarProps> = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ===== ZEICHNEN TAB ===== */}
|
|
||||||
{activeTab === 'draw' && V2_TOOLS_ENABLED && (
|
|
||||||
<div className="ribbon-draw-content">
|
|
||||||
{renderV2Group('canvas')}
|
|
||||||
{renderV2Group('format')}
|
|
||||||
{renderV2Group('insert')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ===== ANSICHT TAB ===== */}
|
{/* ===== ANSICHT TAB ===== */}
|
||||||
{activeTab === 'view' && (
|
{activeTab === 'view' && (
|
||||||
<>
|
<>
|
||||||
@@ -215,6 +152,10 @@ const RibbonBar: React.FC<RibbonBarProps> = ({
|
|||||||
<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>
|
<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>
|
<span>Gäste</span>
|
||||||
</button>
|
</button>
|
||||||
|
<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>
|
||||||
<div className="ribbon-group-label">Extras</div>
|
<div className="ribbon-group-label">Extras</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { UserCursor } from '../crdt';
|
|||||||
import type { UnitType } from '../utils/format';
|
import type { UnitType } from '../utils/format';
|
||||||
|
|
||||||
export type ViewMode = '2d' | 'iso' | 'front' | 'top';
|
export type ViewMode = '2d' | 'iso' | 'front' | 'top';
|
||||||
export type RibbonTab = 'start' | 'draw' | 'view' | 'ki';
|
export type RibbonTab = 'start' | 'view' | 'ki';
|
||||||
export type RightPanel = 'tool' | 'layer' | 'library' | 'ki';
|
export type RightPanel = 'tool' | 'layer' | 'library' | 'ki';
|
||||||
export type Theme = 'light' | 'dark';
|
export type Theme = 'light' | 'dark';
|
||||||
export type DrawerTab = 'tool' | 'layer' | 'library' | 'ki';
|
export type DrawerTab = 'tool' | 'layer' | 'library' | 'ki';
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ describe('RibbonBar', () => {
|
|||||||
it('should call onTabChange when clicking a tab', () => {
|
it('should call onTabChange when clicking a tab', () => {
|
||||||
const onTabChange = vi.fn();
|
const onTabChange = vi.fn();
|
||||||
render(<RibbonBar activeTab="start" onTabChange={onTabChange} onAction={() => {}} />);
|
render(<RibbonBar activeTab="start" onTabChange={onTabChange} onAction={() => {}} />);
|
||||||
// CAD-10: Klick auf 'Zeichnen'
|
// CAD-13-fix: Werkzeuge nur noch in Sidebar — Ribbon hat Start/Ansicht/KI
|
||||||
fireEvent.click(screen.getByText('Zeichnen'));
|
fireEvent.click(screen.getByText('Ansicht'));
|
||||||
expect(onTabChange).toHaveBeenCalledWith('draw');
|
expect(onTabChange).toHaveBeenCalledWith('view');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call onAction when clicking an action button', () => {
|
it('should call onAction when clicking an action button', () => {
|
||||||
|
|||||||
@@ -57,42 +57,37 @@ describe('H1: uiModeService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── RibbonBar simple-Filter ─────────────────────────────
|
// ─── simple/pro-Filter (Registry-Logik) ─────────────────
|
||||||
|
|
||||||
describe('H1: RibbonBar simple-Filter', () => {
|
describe('H1: simple/pro-Werkzeug-Filter', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
setUIMode('pro');
|
setUIMode('pro');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pro-Modus: alle V2-Tools sichtbar (basic + pro-Tags)', async () => {
|
it('pro-Modus: alle V2-Tools registriert (basic + pro-Tags)', async () => {
|
||||||
const { registerBuiltinPlugins, pluginRegistry } = await import('../src/plugins');
|
const { registerBuiltinPlugins, pluginRegistry } = await import('../src/plugins');
|
||||||
registerBuiltinPlugins();
|
registerBuiltinPlugins();
|
||||||
pluginRegistry.initDefaults();
|
pluginRegistry.initDefaults();
|
||||||
const RibbonBar = (await import('../src/components/RibbonBar')).default;
|
const all = pluginRegistry.getToolsV2();
|
||||||
render(<RibbonBar activeTab="draw" onTabChange={() => {}} onAction={() => {}} />);
|
expect(all.length).toBeGreaterThan(10);
|
||||||
const all = screen.getAllByRole('button').length;
|
const basics = all.filter((t) => (t.manifest.tags ?? []).includes('basic'));
|
||||||
expect(all).toBeGreaterThan(10);
|
expect(basics.length).toBeGreaterThan(0);
|
||||||
cleanup();
|
expect(basics.length).toBeLessThan(all.length);
|
||||||
setUIMode('simple');
|
|
||||||
render(<RibbonBar activeTab="draw" onTabChange={() => {}} onAction={() => {}} />);
|
|
||||||
const simpleCount = screen.getAllByRole('button').length;
|
|
||||||
expect(simpleCount).toBeLessThan(all);
|
|
||||||
expect(simpleCount).toBeGreaterThan(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('simple-Modus: pro-Tools (rotate/scale/mirror) fehlen, basic (line) bleibt', async () => {
|
it('simple-Modus: nur basic-Tools (rotate/scale/mirror gefiltert, line bleibt)', async () => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
setUIMode('simple');
|
setUIMode('simple');
|
||||||
const { registerBuiltinPlugins, pluginRegistry } = await import('../src/plugins');
|
const { registerBuiltinPlugins, pluginRegistry } = await import('../src/plugins');
|
||||||
registerBuiltinPlugins();
|
registerBuiltinPlugins();
|
||||||
pluginRegistry.initDefaults();
|
pluginRegistry.initDefaults();
|
||||||
const RibbonBar = (await import('../src/components/RibbonBar')).default;
|
const visible = pluginRegistry.getToolsV2()
|
||||||
render(<RibbonBar activeTab="draw" onTabChange={() => {}} onAction={() => {}} />);
|
.filter((t) => (t.manifest.tags ?? []).includes('basic'))
|
||||||
const html = document.body.innerHTML;
|
.map((t) => t.manifest.id);
|
||||||
expect(html).toContain('data-v2tool="line"');
|
expect(visible).toContain('line');
|
||||||
expect(html).not.toContain('data-v2tool="rotate"');
|
expect(visible).not.toContain('rotate');
|
||||||
expect(html).not.toContain('data-v2tool="mirror"');
|
expect(visible).not.toContain('mirror');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('CommandLine-Komponente: pro-only rendering via useUIMode', async () => {
|
it('CommandLine-Komponente: pro-only rendering via useUIMode', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user