Files
web-cad/frontend/src/components/V2HistoryPanel.tsx
T

165 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;