Skip to content

Commit 89bac1a

Browse files
committed
feat: History mode, undo and redo of actions
1 parent 5df4b8e commit 89bac1a

File tree

5 files changed

+558
-38
lines changed

5 files changed

+558
-38
lines changed

src/components/GGanttChart.vue

+65-5
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { useConnections } from "../composables/useConnections"
4545
import { useTooltip } from "../composables/useTooltip"
4646
import { useChartNavigation } from "../composables/useChartNavigation"
4747
import { useKeyboardNavigation } from "../composables/useKeyboardNavigation"
48-
import { useRows } from "../composables/useRows"
48+
import { useRows, findBarInRows } from "../composables/useRows"
4949
5050
// Types and Constants
5151
import { colorSchemes, type ColorSchemeKey } from "../color-schemes"
@@ -424,13 +424,73 @@ const updateRangeBackground = () => {
424424
}
425425
}
426426
427-
const redo = () => {
428-
rowManager.redo()
427+
const undo = () => {
428+
const changes = rowManager.undo()
429+
if (!changes) return
430+
431+
changes.rowChanges.forEach((rowChange) => {
432+
emit("row-drop", {
433+
sourceRow: rowChange.sourceRow,
434+
targetRow: undefined,
435+
newIndex: rowChange.newIndex,
436+
parentId: rowChange.newParentId
437+
})
438+
})
439+
440+
changes.barChanges.forEach((barChange) => {
441+
const bar = findBarInRows(rowManager.rows.value, barChange.barId)
442+
if (!bar) return
443+
444+
emit("dragend-bar", {
445+
bar,
446+
e: new MouseEvent("mouseup"),
447+
movedBars: new Map([
448+
[
449+
bar,
450+
{
451+
oldStart: barChange.newStart!,
452+
oldEnd: barChange.newEnd!
453+
}
454+
]
455+
])
456+
})
457+
})
458+
429459
updateBarPositions()
430460
}
431461
432-
const undo = () => {
433-
rowManager.undo()
462+
const redo = () => {
463+
const changes = rowManager.redo()
464+
if (!changes) return
465+
466+
changes.rowChanges.forEach((rowChange) => {
467+
emit("row-drop", {
468+
sourceRow: rowChange.sourceRow,
469+
targetRow: undefined,
470+
newIndex: rowChange.newIndex,
471+
parentId: rowChange.newParentId
472+
})
473+
})
474+
475+
changes.barChanges.forEach((barChange) => {
476+
const bar = findBarInRows(rowManager.rows.value, barChange.barId)
477+
if (!bar) return
478+
479+
emit("dragend-bar", {
480+
bar,
481+
e: new MouseEvent("mouseup"),
482+
movedBars: new Map([
483+
[
484+
bar,
485+
{
486+
oldStart: barChange.oldStart!,
487+
oldEnd: barChange.oldEnd!
488+
}
489+
]
490+
])
491+
})
492+
})
493+
434494
updateBarPositions()
435495
}
436496

src/composables/useColumnTouchResize.ts

+33
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import { ref } from "vue"
22

3+
/**
4+
* Interface defining the state for touch-based column resizing
5+
*/
36
interface TouchResizeState {
47
isResizing: boolean
58
startX: number
69
currentColumn: string | null
710
initialWidth: number
811
}
912

13+
/**
14+
* A composable that manages touch-based column resizing functionality
15+
* Handles touch events and width calculations for responsive column sizing
16+
* @returns Object containing resize state and event handlers
17+
*/
1018
export function useColumnTouchResize() {
1119
const touchState = ref<TouchResizeState>({
1220
isResizing: false,
@@ -15,6 +23,10 @@ export function useColumnTouchResize() {
1523
initialWidth: 0
1624
})
1725

26+
/**
27+
* Resets resize state to initial values
28+
* Called when resize operation ends or is cancelled
29+
*/
1830
const resetTouchState = () => {
1931
touchState.value = {
2032
isResizing: false,
@@ -24,6 +36,13 @@ export function useColumnTouchResize() {
2436
}
2537
}
2638

39+
/**
40+
* Initializes touch resize operation
41+
* Sets up initial positions and state for resizing
42+
* @param e - Touch event that started the resize
43+
* @param column - Column being resized
44+
* @param currentWidth - Current width of the column
45+
*/
2746
const handleTouchStart = (e: TouchEvent, column: string, currentWidth: number) => {
2847
const touch = e.touches[0]
2948
if (!touch) return
@@ -38,6 +57,12 @@ export function useColumnTouchResize() {
3857
}
3958
}
4059

60+
/**
61+
* Handles ongoing touch resize movement
62+
* Calculates and applies new column width
63+
* @param e - Touch move event
64+
* @param onResize - Callback function to update column width
65+
*/
4166
const handleTouchMove = (e: TouchEvent, onResize: (column: string, newWidth: number) => void) => {
4267
const touch = e.touches[0]
4368
if (!touch || !touchState.value.isResizing) return
@@ -52,12 +77,20 @@ export function useColumnTouchResize() {
5277
}
5378
}
5479

80+
/**
81+
* Finalizes touch resize operation
82+
* Cleans up state and event listeners
83+
*/
5584
const handleTouchEnd = () => {
5685
if (touchState.value.isResizing) {
5786
resetTouchState()
5887
}
5988
}
6089

90+
/**
91+
* Handles touch cancel event
92+
* Behaves same as touch end
93+
*/
6194
const handleTouchCancel = handleTouchEnd
6295

6396
return {

src/composables/useRowTouchDrag.ts

+47-15
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
1-
import { ref } from 'vue'
2-
import type { ChartRow } from '../types'
1+
import { ref } from "vue"
2+
import type { ChartRow } from "../types"
33

4+
/**
5+
* Interface defining the state for touch-based row dragging
6+
*/
47
interface TouchDragState {
58
isDragging: boolean
69
startY: number
710
currentY: number
811
draggedRow: ChartRow | null
912
dropTarget: {
1013
row: ChartRow | null
11-
position: 'before' | 'after' | 'child'
14+
position: "before" | "after" | "child"
1215
}
1316
dragElement: HTMLElement | null
1417
initialTransform: string
1518
}
1619

20+
/**
21+
* A composable that manages touch-based drag and drop functionality for rows
22+
* Handles touch events, visual feedback, and drop position detection
23+
* @returns Object containing touch state and event handlers
24+
*/
1725
export function useRowTouchDrag() {
1826
const touchState = ref<TouchDragState>({
1927
isDragging: false,
@@ -22,12 +30,16 @@ export function useRowTouchDrag() {
2230
draggedRow: null,
2331
dropTarget: {
2432
row: null,
25-
position: 'before'
33+
position: "before"
2634
},
2735
dragElement: null,
28-
initialTransform: ''
36+
initialTransform: ""
2937
})
3038

39+
/**
40+
* Resets touch drag state and restores original element position
41+
* Called when drag operation ends or is cancelled
42+
*/
3143
const resetTouchState = () => {
3244
if (touchState.value.dragElement) {
3345
touchState.value.dragElement.style.transform = touchState.value.initialTransform
@@ -40,13 +52,20 @@ export function useRowTouchDrag() {
4052
draggedRow: null,
4153
dropTarget: {
4254
row: null,
43-
position: 'before'
55+
position: "before"
4456
},
4557
dragElement: null,
46-
initialTransform: ''
58+
initialTransform: ""
4759
}
4860
}
4961

62+
/**
63+
* Initializes touch drag operation
64+
* Sets up initial positions and state for dragging
65+
* @param event - Touch event that started the drag
66+
* @param row - Row being dragged
67+
* @param element - DOM element being dragged
68+
*/
5069
const handleTouchStart = (event: TouchEvent, row: ChartRow, element: HTMLElement) => {
5170
const touch = event.touches[0]
5271
if (!touch) return
@@ -56,21 +75,28 @@ export function useRowTouchDrag() {
5675
event.preventDefault()
5776
}
5877
}, 100)
59-
78+
6079
touchState.value = {
6180
isDragging: true,
6281
startY: touch.clientY,
6382
currentY: touch.clientY,
6483
draggedRow: row,
6584
dropTarget: {
6685
row: null,
67-
position: 'before'
86+
position: "before"
6887
},
6988
dragElement: element,
70-
initialTransform: element.style.transform || ''
89+
initialTransform: element.style.transform || ""
7190
}
7291
}
7392

93+
/**
94+
* Handles ongoing touch drag movement
95+
* Updates visual position and calculates drop targets
96+
* @param event - Touch move event
97+
* @param targetRow - Row being dragged over
98+
* @param rowElement - DOM element being dragged over
99+
*/
74100
const handleTouchMove = (event: TouchEvent, targetRow: ChartRow, rowElement: HTMLElement) => {
75101
const touch = event.touches[0]
76102
if (!touch || !touchState.value.isDragging || !touchState.value.dragElement) return
@@ -80,29 +106,35 @@ export function useRowTouchDrag() {
80106

81107
const deltaY = touch.clientY - touchState.value.startY
82108
touchState.value.dragElement.style.transform = `translateY(${deltaY}px)`
83-
109+
84110
const rect = rowElement.getBoundingClientRect()
85111
const relativeY = touch.clientY - rect.top
86112
const position = relativeY / rect.height
87113

88114
if (touchState.value.draggedRow !== targetRow) {
89115
if (targetRow.children?.length) {
90116
if (position < 0.25) {
91-
touchState.value.dropTarget = { row: targetRow, position: 'before' }
117+
touchState.value.dropTarget = { row: targetRow, position: "before" }
92118
} else if (position > 0.75) {
93-
touchState.value.dropTarget = { row: targetRow, position: 'after' }
119+
touchState.value.dropTarget = { row: targetRow, position: "after" }
94120
} else {
95-
touchState.value.dropTarget = { row: targetRow, position: 'child' }
121+
touchState.value.dropTarget = { row: targetRow, position: "child" }
96122
}
97123
} else {
98124
touchState.value.dropTarget = {
99125
row: targetRow,
100-
position: position < 0.5 ? 'before' : 'after'
126+
position: position < 0.5 ? "before" : "after"
101127
}
102128
}
103129
}
104130
}
105131

132+
/**
133+
* Finalizes touch drag operation
134+
* Determines final drop position and triggers updates
135+
* @param event - Touch end event
136+
* @returns Object containing drag result information or null if invalid
137+
*/
106138
const handleTouchEnd = (event: TouchEvent) => {
107139
if (!touchState.value.isDragging) return null
108140

0 commit comments

Comments
 (0)