From 216bf6360aa9f4ecd65c896e13d9d8a229648f26 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Thu, 27 Aug 2026 07:17:46 +0200 Subject: [PATCH] task(A4.12): v2 history panel reading undo manager behind flag --- frontend/src/App.tsx | 9 +- frontend/src/components/V2HistoryPanel.tsx | 164 +++++++++++++++++++++ 2 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/V2HistoryPanel.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 625f8be..acb9ef7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,6 +18,7 @@ import StatusBar from './components/StatusBar'; import MobileDrawers from './components/MobileDrawers'; import BackgroundImport from './components/BackgroundImport'; import HistoryPanel from './components/HistoryPanel'; +import V2HistoryPanel from './components/V2HistoryPanel'; import SettingsModal from './components/SettingsModal'; import InlineTextEditor from './components/InlineTextEditor'; import { BackgroundService, type BackgroundConfig } from './services/backgroundService'; @@ -1728,7 +1729,11 @@ const CADEditor: React.FC = ({ projectId, token, onNavigateBack onApply={handleBgApply} backgroundService={bgServiceRef.current} /> - {historyPanelOpen && ( + {historyPanelOpen && (import.meta.env.VITE_TOOL_DISPATCHER === 'v2' ? ( + setHistoryPanelOpen(false)} + /> + ) : ( { @@ -1741,7 +1746,7 @@ const CADEditor: React.FC = ({ projectId, token, onNavigateBack }} onClose={() => setHistoryPanelOpen(false)} /> - )} + ))} {exportFormatOpen && (
setExportFormatOpen(false)}>
e.stopPropagation()}> diff --git a/frontend/src/components/V2HistoryPanel.tsx b/frontend/src/components/V2HistoryPanel.tsx new file mode 100644 index 0000000..ddf99c0 --- /dev/null +++ b/frontend/src/components/V2HistoryPanel.tsx @@ -0,0 +1,164 @@ +/** + * V2HistoryPanel – History-Anzeige für das V2-Dokument (Task A4.12). + * + * Liest die Undo/Redo-Stacks des aktiven Dokuments (documentService). + * Klick auf einen aktiven Eintrag: macht alles DANACH rückgängig. + * Redo-Einträge sind rein informativ (gezielter Sprung nicht nativ). + * + * Wird statt des Legacy HistoryPanel gerendert, wenn + * VITE_TOOL_DISPATCHER=v2 gesetzt ist (App.tsx entscheidet). + */ +import React, { useEffect, useState } from 'react'; +import { getActiveDocument, undoActive, redoActive } from '../kernel/document/documentService'; +import type { CADDocument } from '../kernel/document/CADDocument'; + +type StackKind = 'undo' | 'redo'; +interface StackItemInfo { + kind: StackKind; + index: number; // Position im jeweiligen Stack (0 = ältester) +} + +const V2HistoryPanel: React.FC<{ onClose: () => void }> = ({ onClose }) => { + const [doc, setDoc] = useState(getActiveDocument()); + const [, bump] = useState(0); + + // Auf Dokumentwechsel reagieren + bei Doc-Änderung neu rendern + useEffect(() => { + const unsubChange = doc?.onChanged(() => bump((b) => b + 1)); + const interval = setInterval(() => { + const active = getActiveDocument(); + if (active !== doc) setDoc(active); + }, 500); // einfacher Registry-Poll bis A5 einen Hook liefert + return () => { + unsubChange?.(); + clearInterval(interval); + }; + }, [doc]); + + const um = doc?.getUndoManager(); + const undoCount = um ? um.undoStack.length : 0; + const redoCount = um ? um.redoStack.length : 0; + const items: Array = []; + // Neueste zuerst auflisten: + for (let i = undoCount - 1; i >= 0; i--) { + items.push({ kind: 'undo', index: i, isCurrent: i === undoCount - 1 }); + } + for (let i = redoCount - 1; i >= 0; i--) { + items.push({ kind: 'redo', index: i, isCurrent: false }); + } + + /** Macht alle Schritte nach slotIndex rückgängig (slot 0 = ältester). */ + const undoUntilSlot = (slotIndex: number): void => { + if (!doc) return; + const targetLen = slotIndex + 1; + let guard = 0; + while ( + doc.canUndo() && + doc.getUndoManager().undoStack.length > targetLen && + guard < 500 + ) { + doc.undo(); + guard++; + } + }; + + const shellStyle: React.CSSProperties = { + position: 'fixed', top: '50%', right: '320px', transform: 'translateY(-50%)', + background: 'var(--color-bg-primary, #1e1e2e)', borderRadius: '8px', + padding: '16px', width: '280px', maxHeight: '60vh', overflow: 'auto', + border: '1px solid var(--color-border, #333)', zIndex: 900, + color: 'var(--color-text, #e0e0e0)', + }; + + if (!doc || items.length === 0) { + return ( +
+
+

+ {doc ? 'Keine Historie vorhanden.' : 'Kein V2-Dokument aktiv (Flag aus?).'} +

+ +
+ ); + } + + return ( +
+
+ +
+ {items.map((item) => ( + + )) + } +
+
+ ); +}; + +function Header({ onClose, count }: { onClose: () => void; count: number }): JSX.Element { + return ( +
+

Historie V2{count > 0 ? ` (${count})` : ''}

+ +
+ ); +} + +function UndoRedoButtons({ doc }: { doc: CADDocument | null }): JSX.Element { + return ( +
+ + +
+ ); +} + +function btnStyle(enabled: boolean): React.CSSProperties { + return { + padding: '5px 12px', borderRadius: '4px', fontSize: '12px', + cursor: enabled ? 'pointer' : 'not-allowed', + border: '1px solid var(--color-border, #333)', + background: 'var(--color-bg-secondary, #2a2a3a)', + color: enabled ? 'inherit' : '#555', + }; +} + +export default V2HistoryPanel;