@railtownai/railtracks-visualizer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +23 -0
  3. package/dist/AgenticFlowVisualizer.d.ts +12 -0
  4. package/dist/AgenticFlowVisualizer.d.ts.map +1 -0
  5. package/dist/AgenticFlowVisualizer.js +710 -0
  6. package/dist/App.d.ts +4 -0
  7. package/dist/App.d.ts.map +1 -0
  8. package/dist/App.js +89 -0
  9. package/dist/components/Edge.d.ts +30 -0
  10. package/dist/components/Edge.d.ts.map +1 -0
  11. package/dist/components/Edge.js +74 -0
  12. package/dist/components/FileSelector.d.ts +13 -0
  13. package/dist/components/FileSelector.d.ts.map +1 -0
  14. package/dist/components/FileSelector.js +24 -0
  15. package/dist/components/Node.d.ts +20 -0
  16. package/dist/components/Node.d.ts.map +1 -0
  17. package/dist/components/Node.js +100 -0
  18. package/dist/components/Timeline.d.ts +15 -0
  19. package/dist/components/Timeline.d.ts.map +1 -0
  20. package/dist/components/Timeline.js +118 -0
  21. package/dist/components/VerticalTimeline.d.ts +14 -0
  22. package/dist/components/VerticalTimeline.d.ts.map +1 -0
  23. package/dist/components/VerticalTimeline.js +156 -0
  24. package/dist/hooks/index.d.ts +5 -0
  25. package/dist/hooks/index.d.ts.map +1 -0
  26. package/dist/hooks/index.js +2 -0
  27. package/dist/hooks/useApi.d.ts +17 -0
  28. package/dist/hooks/useApi.d.ts.map +1 -0
  29. package/dist/hooks/useApi.js +95 -0
  30. package/dist/hooks/useFlowData.d.ts +82 -0
  31. package/dist/hooks/useFlowData.d.ts.map +1 -0
  32. package/dist/hooks/useFlowData.js +66 -0
  33. package/dist/index.d.ts +10 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +13 -0
  36. package/dist/test/setup.d.ts +2 -0
  37. package/dist/test/setup.d.ts.map +1 -0
  38. package/dist/test/setup.js +22 -0
  39. package/package.json +78 -0
package/dist/App.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ declare const App: React.FC;
3
+ export default App;
4
+ //# sourceMappingURL=App.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,QAAA,MAAM,GAAG,EAAE,KAAK,CAAC,EA6HhB,CAAC;AAEF,eAAe,GAAG,CAAC"}
package/dist/App.js ADDED
@@ -0,0 +1,89 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ReactFlowProvider } from "reactflow";
3
+ import { useFlowData } from "./hooks";
4
+ import { FileSelector } from "./components/FileSelector";
5
+ import AgenticFlowVisualizer from "./AgenticFlowVisualizer";
6
+ const App = () => {
7
+ const { availableFiles, currentFile, flowData, loading, error, loadFile, refreshFiles } = useFlowData();
8
+ const handleFileSelect = (filename) => {
9
+ loadFile(filename);
10
+ };
11
+ return (_jsxs("div", { className: "app", children: [_jsxs("div", { className: "app-header", children: [_jsx("h1", { children: "RailTracks Visualizer" }), _jsx("div", { className: "file-selector-container", children: _jsx(FileSelector, { files: availableFiles, currentFile: currentFile, onFileSelect: handleFileSelect, onRefresh: refreshFiles, loading: loading, disabled: loading }) })] }), error && (_jsx("div", { className: "error-message", children: _jsxs("p", { children: ["Error: ", error] }) })), _jsx("div", { className: "visualizer-container", children: loading ? (_jsxs("div", { className: "loading-state", children: [_jsx("div", { className: "loading-spinner" }), _jsx("p", { children: "Loading flow data..." })] })) : flowData ? (_jsx(ReactFlowProvider, { children: _jsx(AgenticFlowVisualizer, { flowData: flowData }) })) : (_jsx("div", { className: "no-data-state", children: _jsx("p", { children: "Please select a file to visualize the flow data" }) })) }), _jsx("style", { children: `
12
+ .app {
13
+ min-height: 100vh;
14
+ background: #f9fafb;
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ }
17
+
18
+ .app-header {
19
+ background: white;
20
+ border-bottom: 1px solid #e5e7eb;
21
+ padding: 5px;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: space-between;
25
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
26
+ }
27
+
28
+ .app-header h1 {
29
+ margin: 0;
30
+ font-size: 18px;
31
+ font-weight: 600;
32
+ color: #1f2937;
33
+ }
34
+
35
+ .file-selector-container {
36
+ display: flex;
37
+ align-items: center;
38
+ }
39
+
40
+ .error-message {
41
+ background: #fee2e2;
42
+ border: 1px solid #fecaca;
43
+ color: #991b1b;
44
+ padding: 12px 20px;
45
+ margin: 20px;
46
+ border-radius: 6px;
47
+ font-size: 14px;
48
+ }
49
+
50
+ .visualizer-container {
51
+ padding: 20px;
52
+ min-height: calc(100vh - 100px);
53
+ }
54
+
55
+ .loading-state {
56
+ display: flex;
57
+ flex-direction: column;
58
+ align-items: center;
59
+ justify-content: center;
60
+ height: 400px;
61
+ color: #6b7280;
62
+ }
63
+
64
+ .loading-spinner {
65
+ width: 40px;
66
+ height: 40px;
67
+ border: 4px solid #e5e7eb;
68
+ border-top: 4px solid #6366f1;
69
+ border-radius: 50%;
70
+ animation: spin 1s linear infinite;
71
+ margin-bottom: 16px;
72
+ }
73
+
74
+ @keyframes spin {
75
+ 0% { transform: rotate(0deg); }
76
+ 100% { transform: rotate(360deg); }
77
+ }
78
+
79
+ .no-data-state {
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ height: 400px;
84
+ color: #6b7280;
85
+ font-size: 16px;
86
+ }
87
+ ` })] }));
88
+ };
89
+ export default App;
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ interface EdgeProps {
3
+ id: string;
4
+ sourceX: number;
5
+ sourceY: number;
6
+ targetX: number;
7
+ targetY: number;
8
+ sourcePosition: any;
9
+ targetPosition: any;
10
+ style?: React.CSSProperties;
11
+ markerEnd?: string;
12
+ bidirectional?: boolean;
13
+ data?: {
14
+ label?: string;
15
+ source?: string;
16
+ target?: string;
17
+ step?: number;
18
+ time?: number;
19
+ details?: any;
20
+ };
21
+ clientToSvgCoords?: (clientX: number, clientY: number) => {
22
+ x: number;
23
+ y: number;
24
+ };
25
+ svgRef?: React.RefObject<SVGSVGElement>;
26
+ onInspect?: (edgeData: any) => void;
27
+ }
28
+ export declare const Edge: React.FC<EdgeProps>;
29
+ export {};
30
+ //# sourceMappingURL=Edge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Edge.d.ts","sourceRoot":"","sources":["../../src/components/Edge.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAE5D,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,GAAG,CAAC;IACpB,cAAc,EAAE,GAAG,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,GAAG,CAAC;KACf,CAAC;IACF,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnF,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,CAAC;CACrC;AAED,eAAO,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,SAAS,CA8JpC,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ export const Edge = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style = {}, markerEnd, bidirectional = false, data, clientToSvgCoords, svgRef, onInspect }) => {
4
+ const [isHovered, setIsHovered] = useState(false);
5
+ // Function to determine stroke color based on edge state
6
+ const getStrokeColor = () => {
7
+ const state = data?.details?.state;
8
+ switch (state) {
9
+ case "Open":
10
+ return "#000000"; // Black
11
+ case "Completed":
12
+ return "#15803d"; // Darker Green
13
+ case "Error":
14
+ return "#ef4444"; // Red
15
+ default:
16
+ return style.stroke || "#6366f1"; // Default color
17
+ }
18
+ };
19
+ const [edgePath, arrowMarkers] = useMemo(() => {
20
+ // Always use curved paths for better visual appeal
21
+ const centerX = (sourceX + targetX) / 2;
22
+ const centerY = (sourceY + targetY) / 2;
23
+ const path = `M ${sourceX} ${sourceY} Q ${centerX} ${centerY} ${targetX} ${targetY}`;
24
+ // Create arrow markers for bidirectional edges
25
+ const markers = [];
26
+ if (bidirectional) {
27
+ const strokeColor = getStrokeColor();
28
+ // Forward arrow (source to target)
29
+ markers.push(_jsxs("defs", { children: [_jsx("marker", { id: `${id}-arrowhead-forward`, markerWidth: "10", markerHeight: "7", refX: "9", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: _jsx("polygon", { points: "0 0, 10 3.5, 0 7", fill: strokeColor }) }), _jsx("marker", { id: `${id}-arrowhead-backward`, markerWidth: "10", markerHeight: "7", refX: "1", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: _jsx("polygon", { points: "10 0, 0 3.5, 10 7", fill: strokeColor }) })] }, `${id}-markers`));
30
+ }
31
+ return [path, markers];
32
+ }, [sourceX, sourceY, targetX, targetY, id, bidirectional, data?.details?.state]);
33
+ const hoverStyle = {
34
+ ...style,
35
+ stroke: getStrokeColor(),
36
+ strokeWidth: style.strokeWidth || 4,
37
+ cursor: "pointer"
38
+ };
39
+ const handleInspectClick = (event) => {
40
+ event.stopPropagation();
41
+ if (onInspect && data) {
42
+ onInspect(data);
43
+ }
44
+ };
45
+ return (_jsxs("g", { "data-edge-id": id, children: [arrowMarkers, _jsx("path", { id: id, className: "react-flow__edge-path", d: edgePath, markerEnd: bidirectional ? `url(#${id}-arrowhead-forward)` : markerEnd, markerStart: bidirectional ? `url(#${id}-arrowhead-backward)` : undefined, style: hoverStyle }), data?.label && (_jsx("foreignObject", { x: (sourceX + targetX) / 2 - 50, y: (sourceY + targetY) / 2 - 20, width: "100", height: "40", style: { overflow: "visible", pointerEvents: "none" }, children: _jsx("div", { className: "edge-label-renderer", style: {
46
+ display: "flex",
47
+ alignItems: "center",
48
+ justifyContent: "center",
49
+ width: "100%",
50
+ height: "100%",
51
+ pointerEvents: "auto"
52
+ }, children: _jsx("button", { className: "edge-label-button", onClick: handleInspectClick, style: {
53
+ background: "#6366f1",
54
+ color: "white",
55
+ border: "none",
56
+ borderRadius: 6,
57
+ padding: "4px 12px",
58
+ fontSize: 12,
59
+ fontWeight: 600,
60
+ cursor: "pointer",
61
+ boxShadow: "0 2px 8px rgba(99,102,241,0.10)",
62
+ transition: "background 0.2s"
63
+ }, children: "Inspect" }) }) })), _jsx("style", { children: `
64
+ .edge-label-renderer {
65
+ z-index: 8001;
66
+ user-select: none;
67
+ pointer-events: auto;
68
+ }
69
+
70
+ .edge-label-button {
71
+ pointer-events: auto;
72
+ }
73
+ ` })] }));
74
+ };
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import { JsonFile } from "../hooks";
3
+ interface FileSelectorProps {
4
+ files: JsonFile[];
5
+ currentFile: string | null;
6
+ onFileSelect: (filename: string) => void;
7
+ onRefresh: () => void;
8
+ loading?: boolean;
9
+ disabled?: boolean;
10
+ }
11
+ export declare const FileSelector: React.FC<FileSelectorProps>;
12
+ export {};
13
+ //# sourceMappingURL=FileSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileSelector.d.ts","sourceRoot":"","sources":["../../src/components/FileSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC,UAAU,iBAAiB;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAsDpD,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { RefreshCw } from "lucide-react";
3
+ export const FileSelector = ({ files, currentFile, onFileSelect, onRefresh, loading = false, disabled = false }) => {
4
+ const handleRefresh = (e) => {
5
+ e.stopPropagation();
6
+ onRefresh();
7
+ };
8
+ return (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [_jsxs("select", { value: currentFile || "", onChange: (e) => onFileSelect(e.target.value), disabled: disabled, style: {
9
+ cursor: "pointer",
10
+ padding: "8px 16px 8px 16px",
11
+ borderRadius: "4px",
12
+ fontSize: "14px",
13
+ border: "1px solid #ccc",
14
+ appearance: "none",
15
+ WebkitAppearance: "none",
16
+ MozAppearance: "none"
17
+ }, children: [_jsx("option", { value: "", children: "Select a RailTracks Run..." }), files.map((file) => (_jsx("option", { value: file.name, children: file.name }, file.name)))] }), _jsx("button", { onClick: handleRefresh, disabled: loading || disabled, title: "Refresh files", style: {
18
+ background: "none",
19
+ border: "none",
20
+ cursor: "pointer",
21
+ padding: "4px",
22
+ borderRadius: "4px"
23
+ }, children: _jsx(RefreshCw, { size: 16, style: { animation: loading ? "spin 1s linear infinite" : "none" } }) })] }));
24
+ };
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ interface NodeData {
3
+ label: string;
4
+ description?: string;
5
+ nodeType?: string;
6
+ step?: number;
7
+ time?: number;
8
+ icon?: string;
9
+ onInspect?: (nodeData: any) => void;
10
+ id?: string;
11
+ edges?: any[];
12
+ }
13
+ interface NodeProps {
14
+ data: NodeData;
15
+ id: string;
16
+ }
17
+ declare const Node: React.FC<NodeProps>;
18
+ export { Node };
19
+ export type { NodeData };
20
+ //# sourceMappingURL=Node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Node.d.ts","sourceRoot":"","sources":["../../src/components/Node.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,QAAQ;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;CACf;AAED,UAAU,SAAS;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,QAAA,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,SAAS,CA0H7B,CAAC;AAEF,OAAO,EAAE,IAAI,EAAE,CAAC;AAChB,YAAY,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,100 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Handle, Position, useReactFlow } from "reactflow";
3
+ const Node = ({ data, id }) => {
4
+ const { fitView } = useReactFlow();
5
+ // Check if this node has any outgoing edges (source edges)
6
+ const hasOutgoingEdges = data.edges?.some((edge) => edge.source === id) || false;
7
+ const handleNodeClick = () => {
8
+ // Zoom to the node
9
+ fitView({
10
+ nodes: [{ id }],
11
+ duration: 800,
12
+ padding: 0.1,
13
+ minZoom: 0.5,
14
+ maxZoom: 1.5
15
+ });
16
+ // Open inspection drawer
17
+ if (data.onInspect) {
18
+ data.onInspect({
19
+ label: data.label,
20
+ description: data.description,
21
+ nodeType: data.nodeType,
22
+ step: data.step,
23
+ time: data.time,
24
+ icon: data.icon
25
+ });
26
+ }
27
+ };
28
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "agent-node", onClick: handleNodeClick, style: { cursor: "pointer" }, children: [_jsx(Handle, { type: "target", position: Position.Top, style: { background: "#6366f1" } }), _jsxs("div", { className: "agent-header", children: [_jsx("div", { className: "agent-icon", children: data.icon || "📋" }), _jsx("div", { className: "agent-label", children: data.label })] }), data.description && _jsx("div", { className: "agent-description", children: data.description }), data.step && (_jsxs("div", { className: "agent-meta", children: [_jsxs("span", { className: "step", children: ["Step: ", data.step] }), data.time && _jsx("span", { className: "time", children: new Date(data.time * 1000).toLocaleTimeString() })] })), hasOutgoingEdges && _jsx(Handle, { type: "source", position: Position.Bottom, style: { background: "#6366f1" } })] }), _jsx("style", { children: `
29
+ .agent-node {
30
+ padding: 12px;
31
+ border-radius: 8px;
32
+ background: white;
33
+ border: 2px solid #e5e7eb;
34
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
35
+ min-width: 200px;
36
+ max-width: 250px;
37
+ transition: all 0.2s ease;
38
+ position: relative;
39
+ z-index: -5;
40
+ }
41
+
42
+ .agent-node:hover {
43
+ border-color: #6366f1;
44
+ box-shadow: 0 4px 8px rgba(99, 102, 241, 0.2);
45
+ }
46
+
47
+ .agent-header {
48
+ display: flex;
49
+ align-items: center;
50
+ gap: 8px;
51
+ margin-bottom: 8px;
52
+ }
53
+
54
+ .agent-icon {
55
+ font-size: 20px;
56
+ }
57
+
58
+ .agent-label {
59
+ font-weight: 600;
60
+ color: #1f2937;
61
+ font-size: 14px;
62
+ word-break: break-word;
63
+ }
64
+
65
+ .agent-description {
66
+ color: #6b7280;
67
+ font-size: 12px;
68
+ line-height: 1.4;
69
+ word-break: break-word;
70
+ white-space: pre-line;
71
+ }
72
+
73
+ .agent-meta {
74
+ margin-top: 8px;
75
+ padding-top: 8px;
76
+ border-top: 1px solid #e5e7eb;
77
+ display: flex;
78
+ justify-content: space-between;
79
+ font-size: 10px;
80
+ color: #9ca3af;
81
+ }
82
+
83
+ .step {
84
+ background: #f3f4f6;
85
+ padding: 2px 6px;
86
+ border-radius: 4px;
87
+ }
88
+
89
+ .time {
90
+ font-family: monospace;
91
+ }
92
+
93
+ .react-flow__handle {
94
+ width: 8px;
95
+ height: 8px;
96
+ border: 2px solid #6366f1;
97
+ }
98
+ ` })] }));
99
+ };
100
+ export { Node };
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ interface TimelineProps {
3
+ stamps: Array<{
4
+ step: number;
5
+ time: number;
6
+ identifier: string;
7
+ }>;
8
+ currentStep: number;
9
+ isPlaying: boolean;
10
+ onStepChange: (step: number) => void;
11
+ onPlayPause: () => void;
12
+ }
13
+ declare const Timeline: React.FC<TimelineProps>;
14
+ export { Timeline };
15
+ //# sourceMappingURL=Timeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Timeline.d.ts","sourceRoot":"","sources":["../../src/components/Timeline.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAEzC,UAAU,aAAa;IACrB,MAAM,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,QAAA,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CA4KrC,CAAC;AAEF,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,118 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
3
+ const Timeline = ({ stamps, currentStep, isPlaying, onStepChange, onPlayPause }) => {
4
+ const maxStep = stamps.length > 0 ? Math.max(...stamps.map((s) => s.step)) : 0;
5
+ const minStep = stamps.length > 0 ? Math.min(...stamps.map((s) => s.step)) : 0;
6
+ const totalSteps = maxStep - minStep + 1;
7
+ // Initialize Bootstrap tooltips
8
+ useEffect(() => {
9
+ // Initialize all tooltips
10
+ const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
11
+ const tooltipList = Array.from(tooltipTriggerList).map((tooltipTriggerEl) => new window.bootstrap.Tooltip(tooltipTriggerEl));
12
+ // Cleanup function to dispose tooltips
13
+ return () => {
14
+ tooltipList.forEach((tooltip) => tooltip.dispose());
15
+ };
16
+ }, [stamps, currentStep]); // Re-initialize when stamps or currentStep changes
17
+ return (_jsxs("div", { style: {
18
+ position: "absolute",
19
+ bottom: 0,
20
+ left: 0,
21
+ right: 0,
22
+ height: "60px",
23
+ backgroundColor: "white",
24
+ borderTop: "1px solid #e5e7eb",
25
+ display: "flex",
26
+ alignItems: "center",
27
+ padding: "0 16px",
28
+ gap: "12px",
29
+ zIndex: 10
30
+ }, children: [_jsx("button", { onClick: onPlayPause, style: {
31
+ width: "32px",
32
+ height: "32px",
33
+ borderRadius: "50%",
34
+ border: "1px solid #d1d5db",
35
+ backgroundColor: "white",
36
+ display: "flex",
37
+ alignItems: "center",
38
+ justifyContent: "center",
39
+ cursor: "pointer",
40
+ transition: "all 0.2s ease"
41
+ }, onMouseEnter: (e) => {
42
+ e.currentTarget.style.backgroundColor = "#f3f4f6";
43
+ }, onMouseLeave: (e) => {
44
+ e.currentTarget.style.backgroundColor = "white";
45
+ }, children: isPlaying ? (_jsxs("div", { style: { display: "flex", gap: "2px" }, children: [_jsx("div", { style: {
46
+ width: "3px",
47
+ height: "12px",
48
+ backgroundColor: "#374151"
49
+ } }), _jsx("div", { style: {
50
+ width: "3px",
51
+ height: "12px",
52
+ backgroundColor: "#374151"
53
+ } })] })) : (_jsx("div", { style: {
54
+ width: 0,
55
+ height: 0,
56
+ borderLeft: "8px solid #374151",
57
+ borderTop: "6px solid transparent",
58
+ borderBottom: "6px solid transparent",
59
+ marginLeft: "2px"
60
+ } })) }), _jsx("div", { style: {
61
+ flex: 1,
62
+ display: "flex",
63
+ alignItems: "center",
64
+ gap: "8px",
65
+ padding: "0 8px"
66
+ }, children: Array.from({ length: totalSteps }, (_, index) => {
67
+ const step = minStep + index;
68
+ const isActive = step === currentStep;
69
+ const isPast = step < currentStep;
70
+ const hasStep = stamps.some((s) => s.step === step);
71
+ // Determine background color based on step state
72
+ let backgroundColor = "white";
73
+ if (isActive) {
74
+ backgroundColor = "#6366f1";
75
+ }
76
+ else if (isPast) {
77
+ backgroundColor = hasStep ? "#fef3c7" : "#fef3c7"; // Light yellow for past steps
78
+ }
79
+ else if (hasStep) {
80
+ backgroundColor = "#e5e7eb";
81
+ }
82
+ const tooltipText = `Step ${step}${hasStep ? ` - ${stamps.find((s) => s.step === step)?.identifier || ""}` : " - No activity"}`;
83
+ return (_jsx("button", { onClick: () => onStepChange(step), "data-bs-toggle": "tooltip", "data-bs-placement": "top", "data-bs-title": tooltipText, style: {
84
+ width: "16px",
85
+ height: "16px",
86
+ borderRadius: "50%",
87
+ border: isActive ? "2px solid #6366f1" : "1px solid #d1d5db",
88
+ backgroundColor: backgroundColor,
89
+ cursor: "pointer",
90
+ transition: "all 0.2s ease",
91
+ position: "relative"
92
+ }, onMouseEnter: (e) => {
93
+ if (!isActive) {
94
+ if (isPast) {
95
+ e.currentTarget.style.backgroundColor = hasStep ? "#fde68a" : "#fde68a"; // Darker yellow on hover for past steps
96
+ }
97
+ else {
98
+ e.currentTarget.style.backgroundColor = hasStep ? "#d1d5db" : "#f3f4f6";
99
+ }
100
+ }
101
+ }, onMouseLeave: (e) => {
102
+ if (!isActive) {
103
+ if (isPast) {
104
+ e.currentTarget.style.backgroundColor = hasStep ? "#fef3c7" : "#fef3c7"; // Back to light yellow for past steps
105
+ }
106
+ else {
107
+ e.currentTarget.style.backgroundColor = hasStep ? "#e5e7eb" : "white";
108
+ }
109
+ }
110
+ } }, step));
111
+ }) }), _jsxs("div", { style: {
112
+ fontSize: "12px",
113
+ color: "#6b7280",
114
+ minWidth: "60px",
115
+ textAlign: "right"
116
+ }, children: [currentStep, " / ", maxStep] })] }));
117
+ };
118
+ export { Timeline };
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ interface IProps {
3
+ stamps: Array<{
4
+ step: number;
5
+ time: number;
6
+ identifier: string;
7
+ }>;
8
+ currentStep: number;
9
+ onStepChange: (step: number) => void;
10
+ onToggle?: () => void;
11
+ }
12
+ declare const VerticalTimeline: React.FC<IProps>;
13
+ export { VerticalTimeline };
14
+ //# sourceMappingURL=VerticalTimeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VerticalTimeline.d.ts","sourceRoot":"","sources":["../../src/components/VerticalTimeline.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,MAAM;IACd,MAAM,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,QAAA,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,CAkPtC,CAAC;AAEF,OAAO,EAAE,gBAAgB,EAAE,CAAC"}