task(A4.12): v2 history panel reading undo manager behind flag

This commit is contained in:
Agent Zero
2026-08-27 07:17:46 +02:00
parent 25e721d3d3
commit 216bf6360a
2 changed files with 171 additions and 2 deletions
+7 -2
View File
@@ -18,6 +18,7 @@ import StatusBar from './components/StatusBar';
import MobileDrawers from './components/MobileDrawers'; import MobileDrawers from './components/MobileDrawers';
import BackgroundImport from './components/BackgroundImport'; import BackgroundImport from './components/BackgroundImport';
import HistoryPanel from './components/HistoryPanel'; import HistoryPanel from './components/HistoryPanel';
import V2HistoryPanel from './components/V2HistoryPanel';
import SettingsModal from './components/SettingsModal'; import SettingsModal from './components/SettingsModal';
import InlineTextEditor from './components/InlineTextEditor'; import InlineTextEditor from './components/InlineTextEditor';
import { BackgroundService, type BackgroundConfig } from './services/backgroundService'; import { BackgroundService, type BackgroundConfig } from './services/backgroundService';
@@ -1728,7 +1729,11 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
onApply={handleBgApply} onApply={handleBgApply}
backgroundService={bgServiceRef.current} backgroundService={bgServiceRef.current}
/> />
{historyPanelOpen && ( {historyPanelOpen && (import.meta.env.VITE_TOOL_DISPATCHER === 'v2' ? (
<V2HistoryPanel
onClose={() => setHistoryPanelOpen(false)}
/>
) : (
<HistoryPanel <HistoryPanel
entries={historyEntries} entries={historyEntries}
onJumpTo={(entryId: string) => { onJumpTo={(entryId: string) => {
@@ -1741,7 +1746,7 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
}} }}
onClose={() => setHistoryPanelOpen(false)} onClose={() => setHistoryPanelOpen(false)}
/> />
)} ))}
{exportFormatOpen && ( {exportFormatOpen && (
<div className="export-format-overlay" onClick={() => setExportFormatOpen(false)}> <div className="export-format-overlay" onClick={() => setExportFormatOpen(false)}>
<div className="export-format-modal" onClick={(e) => e.stopPropagation()}> <div className="export-format-modal" onClick={(e) => e.stopPropagation()}>
+164
View File
@@ -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<CADDocument | null>(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<StackItemInfo & { isCurrent: boolean }> = [];
// 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 (
<div style={shellStyle}>
<Header onClose={onClose} count={items.length} />
<p style={{ fontSize: '13px', color: '#888' }}>
{doc ? 'Keine Historie vorhanden.' : 'Kein V2-Dokument aktiv (Flag aus?).'}
</p>
<UndoRedoButtons doc={doc} />
</div>
);
}
return (
<div style={shellStyle}>
<Header onClose={onClose} count={items.length} />
<UndoRedoButtons doc={doc} />
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
{items.map((item) => (
<button
key={`${item.kind}-${item.index}`}
onClick={() => item.kind === 'undo' && undoUntilSlot(item.index)}
title={item.kind === 'undo'
? 'Klicken: alles danach rückgängig machen'
: 'Redo-Eintrag (nur anzeigbar)'}
style={{
display: 'flex', alignItems: 'center', gap: '8px',
padding: '6px 8px', borderRadius: '4px',
cursor: item.kind === 'undo' ? 'pointer' : 'default',
border: item.isCurrent && item.kind === 'undo'
? '1px solid rgba(80,250,123,0.4)' : 'none',
textAlign: 'left', width: '100%',
background: item.isCurrent && item.kind === 'undo'
? 'rgba(80, 250, 123, 0.12)' : item.kind === 'redo'
? 'rgba(255,255,255,0.03)' : 'transparent',
color: item.kind === 'redo' ? '#888' : 'var(--color-text, #e0e0e0)',
fontWeight: item.isCurrent && item.kind === 'undo' ? 600 : 400,
fontSize: '13px', opacity: item.kind === 'redo' ? 0.6 : 1,
}}
>
<span style={{ fontSize: '10px', minWidth: '44px', color: '#666' }}>
{item.kind === 'undo' ? `Schritt ${item.index + 1}` : 'Redo'}
</span>
{item.isCurrent && item.kind === 'undo' && (
<span style={{ fontSize: '10px', color: '#50fa7b' }}> aktuell</span>
)}
</button>
))
}
</div>
</div>
);
};
function Header({ onClose, count }: { onClose: () => void; count: number }): JSX.Element {
return (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
<h3 style={{ margin: 0, fontSize: '15px' }}>Historie V2{count > 0 ? ` (${count})` : ''}</h3>
<button onClick={onClose} style={{ background: 'none', border: 'none', color: 'inherit', cursor: 'pointer', fontSize: '18px' }}>×</button>
</div>
);
}
function UndoRedoButtons({ doc }: { doc: CADDocument | null }): JSX.Element {
return (
<div style={{ display: 'flex', gap: '8px', marginBottom: doc && (doc.canUndo() || doc.canRedo()) ? '10px' : '4px' }}>
<button
onClick={() => undoActive()}
disabled={!doc?.canUndo()}
title="Strg+Z"
style={btnStyle(doc?.canUndo() ?? false)}
> Undo</button>
<button
onClick={() => redoActive()}
disabled={!doc?.canRedo()}
title="Strg+Y"
style={btnStyle(doc?.canRedo() ?? false)}
> Redo</button>
</div>
);
}
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;