UNPKG

signalforge

Version:

Fine-grained reactive state management with automatic dependency tracking - Ultra-optimized, zero dependencies

132 lines (131 loc) 10.5 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useState, useEffect } from 'react'; import { calculateMemoryUsage, formatMemorySize, } from '../../plugins/timeTravel'; export const TimeTravelTimeline = ({ plugin, refreshInterval = 1000, showDetails = true, className = '', style = {}, }) => { const [timeline, setTimeline] = useState(null); const [selectedSnapshot, setSelectedSnapshot] = useState(null); const [memoryUsage, setMemoryUsage] = useState('0 B'); const refreshTimeline = () => { const state = plugin.getTimelineState(); setTimeline(state); const snapshots = plugin.getSnapshots(); const bytes = calculateMemoryUsage(snapshots); setMemoryUsage(formatMemorySize(bytes)); }; useEffect(() => { refreshTimeline(); if (refreshInterval > 0) { const timer = setInterval(refreshTimeline, refreshInterval); return () => clearInterval(timer); } }, [refreshInterval]); const handleUndo = () => { plugin.undo(); refreshTimeline(); }; const handleRedo = () => { plugin.redo(); refreshTimeline(); }; const handleJumpTo = (index) => { plugin.jumpTo(index); refreshTimeline(); }; const handleSliderChange = (e) => { const index = parseInt(e.target.value, 10); handleJumpTo(index); }; const handleSnapshotClick = (index) => { const snapshot = plugin.getSnapshotAt(index); setSelectedSnapshot(snapshot || null); }; const handleExport = () => { const session = plugin.exportSession(); const json = JSON.stringify(session, null, 2); const blob = new Blob([json], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `time-travel-session-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); }; const handleImport = () => { const input = document.createElement('input'); input.type = 'file'; input.accept = 'application/json'; input.onchange = (e) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { try { const session = JSON.parse(event.target?.result); plugin.importSession(session); refreshTimeline(); } catch (error) { alert('Failed to import session: ' + error); } }; reader.readAsText(file); }; input.click(); }; const handleClear = () => { if (confirm('Clear all history?')) { plugin.clear(); setSelectedSnapshot(null); refreshTimeline(); } }; const handleToggleRecording = () => { plugin.setEnabled(!plugin.isEnabled()); refreshTimeline(); }; if (!timeline) { return _jsx("div", { style: { padding: '20px' }, children: "Loading..." }); } return (_jsxs("div", { className: `time-travel-timeline ${className}`, style: { padding: '20px', fontFamily: 'system-ui, sans-serif', ...style }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }, children: [_jsx("h2", { style: { margin: 0 }, children: "\u23F1\uFE0F Time Travel" }), _jsxs("div", { style: { display: 'flex', gap: '10px' }, children: [_jsx("button", { onClick: handleToggleRecording, style: buttonStyle, children: plugin.isEnabled() ? '⏸️ Pause' : '▶️ Record' }), _jsx("button", { onClick: handleExport, style: buttonStyle, disabled: timeline.total === 0, children: "\uD83D\uDCE5 Export" }), _jsx("button", { onClick: handleImport, style: buttonStyle, children: "\uD83D\uDCE4 Import" }), _jsx("button", { onClick: handleClear, style: buttonStyle, disabled: timeline.total === 0, children: "\uD83D\uDDD1\uFE0F Clear" })] })] }), _jsxs("div", { style: { display: 'flex', gap: '20px', marginBottom: '20px', fontSize: '14px', color: '#666' }, children: [_jsxs("div", { children: [_jsx("strong", { children: "Snapshots:" }), " ", timeline.total] }), _jsxs("div", { children: [_jsx("strong", { children: "Position:" }), " ", timeline.current + 1, " / ", timeline.total] }), _jsxs("div", { children: [_jsx("strong", { children: "Memory:" }), " ", memoryUsage] }), _jsxs("div", { children: [_jsx("strong", { children: "Status:" }), " ", plugin.isEnabled() ? '🟢 Recording' : '⏸️ Paused'] })] }), _jsxs("div", { style: { display: 'flex', gap: '10px', marginBottom: '20px' }, children: [_jsx("button", { onClick: handleUndo, disabled: !timeline.canUndo, style: { ...buttonStyle, flex: 1 }, children: "\u23EA Undo" }), _jsx("button", { onClick: handleRedo, disabled: !timeline.canRedo, style: { ...buttonStyle, flex: 1 }, children: "\u23E9 Redo" }), _jsx("button", { onClick: () => handleJumpTo(0), disabled: timeline.total === 0 || timeline.current === 0, style: { ...buttonStyle, flex: 1 }, children: "\u23EE\uFE0F Start" }), _jsx("button", { onClick: () => handleJumpTo(timeline.total - 1), disabled: timeline.total === 0 || timeline.current === timeline.total - 1, style: { ...buttonStyle, flex: 1 }, children: "\u23ED\uFE0F End" })] }), timeline.total > 0 && (_jsxs("div", { style: { marginBottom: '30px' }, children: [_jsx("input", { type: "range", min: "0", max: timeline.total - 1, value: timeline.current, onChange: handleSliderChange, style: { width: '100%', height: '8px', borderRadius: '4px', outline: 'none', cursor: 'pointer', } }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', fontSize: '12px', color: '#999', marginTop: '5px' }, children: [_jsx("span", { children: "Start" }), _jsxs("span", { children: ["Position: ", timeline.current + 1] }), _jsx("span", { children: "End" })] })] })), _jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h3", { style: { marginBottom: '10px' }, children: "Snapshots" }), timeline.total === 0 ? (_jsx("p", { style: { color: '#666' }, children: "No snapshots recorded yet. Start using signals to see history." })) : (_jsx("div", { style: { maxHeight: '400px', overflowY: 'auto', border: '1px solid #e0e0e0', borderRadius: '4px' }, children: timeline.snapshots.map((snapshot, index) => (_jsxs("div", { onClick: () => handleSnapshotClick(index), style: { padding: '12px', borderBottom: '1px solid #f0f0f0', cursor: 'pointer', backgroundColor: index === timeline.current ? '#e3f2fd' : 'transparent', transition: 'background-color 0.2s', }, onMouseEnter: (e) => { if (index !== timeline.current) { e.currentTarget.style.backgroundColor = '#f5f5f5'; } }, onMouseLeave: (e) => { if (index !== timeline.current) { e.currentTarget.style.backgroundColor = 'transparent'; } }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [_jsxs("div", { children: [_jsxs("strong", { children: ["#", snapshot.id] }), " ", snapshot.signalLabel || snapshot.signalId] }), _jsx("div", { style: { fontSize: '12px', color: '#666' }, children: new Date(snapshot.timestamp).toLocaleTimeString() })] }), _jsx("div", { style: { fontSize: '14px', color: '#555', marginTop: '4px' }, children: snapshot.preview })] }, snapshot.id))) }))] }), showDetails && selectedSnapshot && (_jsxs("div", { style: { padding: '15px', backgroundColor: '#f9f9f9', borderRadius: '8px', border: '1px solid #e0e0e0' }, children: [_jsx("h3", { style: { marginTop: 0 }, children: "Snapshot Details" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px', fontSize: '14px' }, children: [_jsxs("div", { children: [_jsx("strong", { children: "ID:" }), " #", selectedSnapshot.id] }), _jsxs("div", { children: [_jsx("strong", { children: "Index:" }), " ", selectedSnapshot.index] }), _jsxs("div", { children: [_jsx("strong", { children: "Signal:" }), " ", selectedSnapshot.signalLabel || selectedSnapshot.signalId] }), _jsxs("div", { children: [_jsx("strong", { children: "Type:" }), " ", selectedSnapshot.signalType] }), _jsxs("div", { children: [_jsx("strong", { children: "Time:" }), " ", new Date(selectedSnapshot.timestamp).toLocaleString()] }), _jsxs("div", { children: [_jsx("strong", { children: "Is Full:" }), " ", selectedSnapshot.isFull ? 'Yes' : 'No (Diff)'] })] }), _jsxs("div", { style: { marginTop: '15px' }, children: [_jsx("div", { style: { marginBottom: '5px' }, children: _jsx("strong", { children: "Old Value:" }) }), _jsx("pre", { style: codeStyle, children: JSON.stringify(selectedSnapshot.oldValue, null, 2) })] }), _jsxs("div", { style: { marginTop: '15px' }, children: [_jsx("div", { style: { marginBottom: '5px' }, children: _jsx("strong", { children: "New Value:" }) }), _jsx("pre", { style: codeStyle, children: JSON.stringify(selectedSnapshot.newValue, null, 2) })] }), selectedSnapshot.diff && (_jsxs("div", { style: { marginTop: '15px' }, children: [_jsx("div", { style: { marginBottom: '5px' }, children: _jsx("strong", { children: "Diff:" }) }), _jsx("pre", { style: codeStyle, children: JSON.stringify(selectedSnapshot.diff, null, 2) })] })), _jsx("button", { onClick: () => setSelectedSnapshot(null), style: { ...buttonStyle, marginTop: '15px' }, children: "Close" })] }))] })); }; const buttonStyle = { padding: '8px 16px', backgroundColor: '#2196F3', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '14px', transition: 'background-color 0.2s', }; const codeStyle = { backgroundColor: '#f5f5f5', padding: '10px', borderRadius: '4px', fontSize: '12px', fontFamily: 'monospace', overflow: 'auto', maxHeight: '200px', }; export default TimeTravelTimeline;