task(A4.12): v2 history panel reading undo manager behind flag
This commit is contained in:
@@ -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<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
onApply={handleBgApply}
|
||||
backgroundService={bgServiceRef.current}
|
||||
/>
|
||||
{historyPanelOpen && (
|
||||
{historyPanelOpen && (import.meta.env.VITE_TOOL_DISPATCHER === 'v2' ? (
|
||||
<V2HistoryPanel
|
||||
onClose={() => setHistoryPanelOpen(false)}
|
||||
/>
|
||||
) : (
|
||||
<HistoryPanel
|
||||
entries={historyEntries}
|
||||
onJumpTo={(entryId: string) => {
|
||||
@@ -1741,7 +1746,7 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
}}
|
||||
onClose={() => setHistoryPanelOpen(false)}
|
||||
/>
|
||||
)}
|
||||
))}
|
||||
{exportFormatOpen && (
|
||||
<div className="export-format-overlay" onClick={() => setExportFormatOpen(false)}>
|
||||
<div className="export-format-modal" onClick={(e) => e.stopPropagation()}>
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user