Skip to content

Commit

Permalink
feat: History mode, undo and redo of actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Xeyos88 committed Jan 27, 2025
1 parent 1f47bae commit 08d7bda
Show file tree
Hide file tree
Showing 5 changed files with 558 additions and 38 deletions.
70 changes: 65 additions & 5 deletions src/components/GGanttChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { useConnections } from "../composables/useConnections"
import { useTooltip } from "../composables/useTooltip"
import { useChartNavigation } from "../composables/useChartNavigation"
import { useKeyboardNavigation } from "../composables/useKeyboardNavigation"
import { useRows } from "../composables/useRows"
import { useRows, findBarInRows } from "../composables/useRows"
// Types and Constants
import { colorSchemes, type ColorSchemeKey } from "../color-schemes"
Expand Down Expand Up @@ -424,13 +424,73 @@ const updateRangeBackground = () => {
}
}
const redo = () => {
rowManager.redo()
const undo = () => {
const changes = rowManager.undo()
if (!changes) return
changes.rowChanges.forEach((rowChange) => {
emit("row-drop", {
sourceRow: rowChange.sourceRow,
targetRow: undefined,
newIndex: rowChange.newIndex,
parentId: rowChange.newParentId
})
})
changes.barChanges.forEach((barChange) => {
const bar = findBarInRows(rowManager.rows.value, barChange.barId)
if (!bar) return
emit("dragend-bar", {
bar,
e: new MouseEvent("mouseup"),
movedBars: new Map([
[
bar,
{
oldStart: barChange.newStart!,
oldEnd: barChange.newEnd!
}
]
])
})
})
updateBarPositions()
}
const undo = () => {
rowManager.undo()
const redo = () => {
const changes = rowManager.redo()
if (!changes) return
changes.rowChanges.forEach((rowChange) => {
emit("row-drop", {
sourceRow: rowChange.sourceRow,
targetRow: undefined,
newIndex: rowChange.newIndex,
parentId: rowChange.newParentId
})
})
changes.barChanges.forEach((barChange) => {
const bar = findBarInRows(rowManager.rows.value, barChange.barId)
if (!bar) return
emit("dragend-bar", {
bar,
e: new MouseEvent("mouseup"),
movedBars: new Map([
[
bar,
{
oldStart: barChange.oldStart!,
oldEnd: barChange.oldEnd!
}
]
])
})
})
updateBarPositions()
}
Expand Down
33 changes: 33 additions & 0 deletions src/composables/useColumnTouchResize.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { ref } from "vue"

/**
* Interface defining the state for touch-based column resizing
*/
interface TouchResizeState {
isResizing: boolean
startX: number
currentColumn: string | null
initialWidth: number
}

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

/**
* Resets resize state to initial values
* Called when resize operation ends or is cancelled
*/
const resetTouchState = () => {
touchState.value = {
isResizing: false,
Expand All @@ -24,6 +36,13 @@ export function useColumnTouchResize() {
}
}

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

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

/**
* Finalizes touch resize operation
* Cleans up state and event listeners
*/
const handleTouchEnd = () => {
if (touchState.value.isResizing) {
resetTouchState()
}
}

/**
* Handles touch cancel event
* Behaves same as touch end
*/
const handleTouchCancel = handleTouchEnd

return {
Expand Down
62 changes: 47 additions & 15 deletions src/composables/useRowTouchDrag.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { ref } from 'vue'
import type { ChartRow } from '../types'
import { ref } from "vue"
import type { ChartRow } from "../types"

/**
* Interface defining the state for touch-based row dragging
*/
interface TouchDragState {
isDragging: boolean
startY: number
currentY: number
draggedRow: ChartRow | null
dropTarget: {
row: ChartRow | null
position: 'before' | 'after' | 'child'
position: "before" | "after" | "child"
}
dragElement: HTMLElement | null
initialTransform: string
}

/**
* A composable that manages touch-based drag and drop functionality for rows
* Handles touch events, visual feedback, and drop position detection
* @returns Object containing touch state and event handlers
*/
export function useRowTouchDrag() {
const touchState = ref<TouchDragState>({
isDragging: false,
Expand All @@ -22,12 +30,16 @@ export function useRowTouchDrag() {
draggedRow: null,
dropTarget: {
row: null,
position: 'before'
position: "before"
},
dragElement: null,
initialTransform: ''
initialTransform: ""
})

/**
* Resets touch drag state and restores original element position
* Called when drag operation ends or is cancelled
*/
const resetTouchState = () => {
if (touchState.value.dragElement) {
touchState.value.dragElement.style.transform = touchState.value.initialTransform
Expand All @@ -40,13 +52,20 @@ export function useRowTouchDrag() {
draggedRow: null,
dropTarget: {
row: null,
position: 'before'
position: "before"
},
dragElement: null,
initialTransform: ''
initialTransform: ""
}
}

/**
* Initializes touch drag operation
* Sets up initial positions and state for dragging
* @param event - Touch event that started the drag
* @param row - Row being dragged
* @param element - DOM element being dragged
*/
const handleTouchStart = (event: TouchEvent, row: ChartRow, element: HTMLElement) => {
const touch = event.touches[0]
if (!touch) return
Expand All @@ -56,21 +75,28 @@ export function useRowTouchDrag() {
event.preventDefault()
}
}, 100)

touchState.value = {
isDragging: true,
startY: touch.clientY,
currentY: touch.clientY,
draggedRow: row,
dropTarget: {
row: null,
position: 'before'
position: "before"
},
dragElement: element,
initialTransform: element.style.transform || ''
initialTransform: element.style.transform || ""
}
}

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

const deltaY = touch.clientY - touchState.value.startY
touchState.value.dragElement.style.transform = `translateY(${deltaY}px)`

const rect = rowElement.getBoundingClientRect()
const relativeY = touch.clientY - rect.top
const position = relativeY / rect.height

if (touchState.value.draggedRow !== targetRow) {
if (targetRow.children?.length) {
if (position < 0.25) {
touchState.value.dropTarget = { row: targetRow, position: 'before' }
touchState.value.dropTarget = { row: targetRow, position: "before" }
} else if (position > 0.75) {
touchState.value.dropTarget = { row: targetRow, position: 'after' }
touchState.value.dropTarget = { row: targetRow, position: "after" }
} else {
touchState.value.dropTarget = { row: targetRow, position: 'child' }
touchState.value.dropTarget = { row: targetRow, position: "child" }
}
} else {
touchState.value.dropTarget = {
row: targetRow,
position: position < 0.5 ? 'before' : 'after'
position: position < 0.5 ? "before" : "after"
}
}
}
}

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

Expand Down
Loading

0 comments on commit 08d7bda

Please sign in to comment.