Skip to content

Commit 5648f00

Browse files
author
Korolev, Alexey (ak3140)
committed
feat: add resizable flow column width with state persistence and delete confirmation dialog
1 parent 0d108e9 commit 5648f00

5 files changed

Lines changed: 68 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
55
## [Unreleased]
66

77
### Added
8+
- **Flow Column Resizing**: Navigational flow row columns on the left can now be fully resized by horizontally dragging their edges, and are permanently tracked via `localStorage` alongside JSON outputs.
89
- **Event Duplication**: Added a 'Duplicate' button when hovering over events that seamlessly clones the event and places it sequentially into the earliest non-overlapping slot.
910
- **Cross-Flow Drag**: Allowed dragging and dropping events vertically between different flows seamlessly.
1011
- **Undo/Redo**: Integrated `zundo` for state management and created `UndoRedoControls` component.

src/components/FlowRow.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface FlowRowProps {
1515
export const FlowRow: React.FC<FlowRowProps> = (
1616
{ flow, events, maxEndTime }
1717
) => {
18-
const { removeFlow, updateFlowTitle, toggleFlowVisibility } =
18+
const { removeFlow, updateFlowTitle, toggleFlowVisibility, flowColumnWidth } =
1919
useTimelineStore();
2020
const [isRenaming, setIsRenaming] = useState(false);
2121
const [renameValue, setRenameValue] = useState(flow.title);
@@ -49,9 +49,10 @@ export const FlowRow: React.FC<FlowRowProps> = (
4949
transition-colors ${hidden ? 'h-8' : 'h-14'}
5050
${hidden ? 'bg-gray-100/50 opacity-60' : 'hover:bg-gray-50/50'}`}
5151
>
52-
<div className="w-48 shrink-0 bg-gray-100 border-r border-gray-200
53-
flex items-center px-2 py-1 relative z-10
54-
shadow-[1px_0_0_rgba(0,0,0,0.1)] justify-between">
52+
<div
53+
className="shrink-0 bg-gray-100 border-r border-gray-200 flex items-center px-2 py-1 relative z-10 justify-between shadow-[1px_0_0_rgba(0,0,0,0.1)] bg-gray-100 sticky left-0"
54+
style={{ width: flowColumnWidth, minWidth: flowColumnWidth, maxWidth: flowColumnWidth }}
55+
>
5556
<div className="flex items-center gap-1 overflow-hidden">
5657
<div {...attributes} {...listeners}
5758
className="cursor-grab text-gray-400 hover:text-gray-600">
@@ -86,7 +87,11 @@ export const FlowRow: React.FC<FlowRowProps> = (
8687
title={hidden ? 'Show Flow' : 'Hide Flow'}>
8788
{hidden ? <EyeOff size={14} /> : <Eye size={14} />}
8889
</button>
89-
<button onClick={() => removeFlow(flow.id)}
90+
<button onClick={() => {
91+
if (window.confirm('Are you sure you want to delete this flow and all its events?')) {
92+
removeFlow(flow.id);
93+
}
94+
}}
9095
className="text-gray-400 hover:text-red-500
9196
opacity-0 group-hover:opacity-100 transition-opacity p-1"
9297
title="Delete Flow">

src/components/Timeline/TimelineAxis.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,58 @@
1-
import React from 'react';
1+
import React, { useRef, useEffect } from 'react';
2+
import { useTimelineStore } from '../../store/timelineStore';
23

34
interface TimelineAxisProps {
45
ticks: number[];
56
maxEndTime: number;
67
}
78

89
export const TimelineAxis: React.FC<TimelineAxisProps> = ({ ticks, maxEndTime }) => {
10+
const { flowColumnWidth, setFlowColumnWidth } = useTimelineStore();
11+
const draggingRef = useRef<boolean>(false);
12+
const startXRef = useRef<number>(0);
13+
const startWidthRef = useRef<number>(0);
14+
15+
useEffect(() => {
16+
const handlePointerMove = (e: PointerEvent) => {
17+
if (!draggingRef.current) return;
18+
19+
const delta = e.pageX - startXRef.current;
20+
const newWidth = Math.max(100, Math.min(600, startWidthRef.current + delta));
21+
setFlowColumnWidth(newWidth);
22+
};
23+
24+
const handlePointerUp = () => {
25+
if (draggingRef.current) {
26+
draggingRef.current = false;
27+
document.body.style.cursor = '';
28+
}
29+
};
30+
31+
window.addEventListener('pointermove', handlePointerMove);
32+
window.addEventListener('pointerup', handlePointerUp);
33+
return () => {
34+
window.removeEventListener('pointermove', handlePointerMove);
35+
window.removeEventListener('pointerup', handlePointerUp);
36+
};
37+
}, [setFlowColumnWidth]);
38+
939
return (
1040
<div className="flex bg-gray-50 border-b border-gray-200 sticky top-0 z-10">
11-
<div className="w-48 shrink-0 bg-gray-100 border-r border-gray-200 flex items-center p-2 text-gray-700 font-semibold sticky left-0 z-20 shadow-[1px_0_0_rgba(0,0,0,0.1)]">
12-
Flows
41+
<div
42+
className="shrink-0 bg-gray-100 border-r border-gray-200 flex items-center p-2 text-gray-700 font-semibold sticky left-0 z-20 shadow-[1px_0_0_rgba(0,0,0,0.1)] relative"
43+
style={{ width: flowColumnWidth, minWidth: flowColumnWidth, maxWidth: flowColumnWidth }}
44+
>
45+
<span>Flows</span>
46+
<div
47+
className="absolute top-0 right-0 bottom-0 w-2 cursor-col-resize hover:bg-blue-400 z-30 opacity-50 transition-colors translate-x-1/2"
48+
onPointerDown={(e) => {
49+
e.preventDefault();
50+
draggingRef.current = true;
51+
startXRef.current = e.pageX;
52+
startWidthRef.current = flowColumnWidth;
53+
document.body.style.cursor = 'col-resize';
54+
}}
55+
/>
1356
</div>
1457
<div className="flex-grow relative h-10 min-w-[600px]">
1558
{ticks.map((tick) => (

src/components/Timeline/TimelineGrid.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import React from 'react';
2+
import { useTimelineStore } from '../../store/timelineStore';
23

34
interface TimelineGridProps {
45
maxEndTime: number;
56
}
67

78
export const TimelineGrid: React.FC<TimelineGridProps> = ({ maxEndTime }) => {
9+
const { flowColumnWidth } = useTimelineStore();
810
const numMajorTicks = Math.ceil(maxEndTime / 1000);
911
const majorTicks = Array.from({ length: numMajorTicks + 1 }, (_, i) => i * 1000);
1012

1113
const numMinorTicks = Math.ceil(maxEndTime / 100);
1214
const minorTicks = Array.from({ length: numMinorTicks + 1 }, (_, i) => i * 100);
1315

1416
return (
15-
<div className="absolute top-0 bottom-0 left-48 right-0 pointer-events-none z-0 overflow-hidden">
17+
<div
18+
className="absolute top-0 bottom-0 right-0 pointer-events-none z-0 overflow-hidden"
19+
style={{ left: flowColumnWidth }}
20+
>
1621
{minorTicks.map((tick) => (
1722
<div
1823
key={`minor-${tick}`}

src/store/timelineStore.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ interface TimelineState {
1010
events: TimelineEvent[];
1111
maxDuration: number;
1212
zoom: number;
13+
flowColumnWidth: number;
14+
setFlowColumnWidth: (width: number) => void;
1315
addFlow: (title: string) => void;
1416
removeFlow: (id: string) => void;
1517
updateFlowTitle: (id: string, title: string) => void;
@@ -32,7 +34,9 @@ export const useTimelineStore = create<TimelineState>()(
3234
events: [],
3335
maxDuration: 10000,
3436
zoom: 1,
37+
flowColumnWidth: 192,
3538

39+
setFlowColumnWidth: (flowColumnWidth) => set({ flowColumnWidth }),
3640
setMaxDuration: (maxDuration) => set({ maxDuration }),
3741
setZoom: (zoom) => set({ zoom }),
3842
importData: (data) =>
@@ -119,6 +123,7 @@ export const useTimelineStore = create<TimelineState>()(
119123
events: state.events,
120124
maxDuration: state.maxDuration,
121125
zoom: state.zoom,
126+
flowColumnWidth: state.flowColumnWidth,
122127
}),
123128
}
124129
)

0 commit comments

Comments
 (0)