165 lines
6.1 KiB
TypeScript
165 lines
6.1 KiB
TypeScript
/**
|
||
* 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;
|