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 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()}>
|
||||||
|
|||||||
@@ -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