Much simpler undo/redo handling, bugs fixed and less code.

This commit is contained in:
Leo Vasanko
2025-08-27 13:54:10 -06:00
parent 45939939f2
commit 57aefc5b4c
9 changed files with 177 additions and 268 deletions

View File

@@ -22,10 +22,18 @@ import { shallowRef } from 'vue'
const eventDialogRef = shallowRef(null)
function openCreateEventDialog(eventData) {
if (!eventDialogRef.value) return
// Capture baseline before dialog opens (new event creation flow)
try {
calendarStore.$history?._baselineIfNeeded?.(true)
} catch {}
const selectionData = { startDate: eventData.startDate, dayCount: eventData.dayCount }
setTimeout(() => eventDialogRef.value?.openCreateDialog(selectionData), 30)
}
function openEditEventDialog(eventClickPayload) {
// Capture baseline before editing existing event
try {
calendarStore.$history?._baselineIfNeeded?.(true)
} catch {}
eventDialogRef.value?.openEditDialog(eventClickPayload)
}
const viewport = ref(null)

View File

@@ -22,6 +22,8 @@ const props = defineProps({
const emit = defineEmits(['clear-selection'])
const calendarStore = useCalendarStore()
// Track baseline signature when dialog opens to decide if we need an undo snapshot on close
let dialogBaselineSig = null
const showDialog = ref(false)
// Anchoring: element of the DayCell representing the event's start date.
@@ -208,7 +210,8 @@ function resolveAnchorFromDate(dateStr) {
}
function openCreateDialog(selectionData = null) {
calendarStore.$history?.beginCompound()
// Pre-change snapshot (before creating stub event)
calendarStore.$history?.push?.()
if (unsavedCreateId.value && !eventSaved.value) {
if (calendarStore.events?.has(unsavedCreateId.value)) {
calendarStore.deleteEvent(unsavedCreateId.value)
@@ -272,6 +275,7 @@ function openCreateDialog(selectionData = null) {
// anchor to the starting day cell
anchorElement.value = resolveAnchorFromDate(start)
showDialog.value = true
// (Pre snapshot already taken before stub creation)
nextTick(() => {
if (titleInput.value) {
@@ -284,7 +288,6 @@ function openCreateDialog(selectionData = null) {
}
function openEditDialog(payload) {
calendarStore.$history?.beginCompound()
if (
dialogMode.value === 'create' &&
unsavedCreateId.value &&
@@ -348,6 +351,8 @@ function openEditDialog(payload) {
// anchor to base event start date
anchorElement.value = resolveAnchorFromDate(event.startDate)
showDialog.value = true
// Pre-change snapshot (only once when dialog opens)
calendarStore.$history?.push?.()
nextTick(() => {
if (titleInput.value) {
@@ -360,7 +365,6 @@ function openEditDialog(payload) {
}
function closeDialog() {
calendarStore.$history?.endCompound()
showDialog.value = false
}
@@ -392,13 +396,11 @@ function saveEvent() {
unsavedCreateId.value = null
}
if (dialogMode.value === 'create') emit('clear-selection')
calendarStore.$history?.endCompound()
closeDialog()
}
function deleteEventAll() {
if (editingEventId.value) calendarStore.deleteEvent(editingEventId.value)
calendarStore.$history?.endCompound()
closeDialog()
}
@@ -408,14 +410,12 @@ function deleteEventOne() {
} else if (isRepeatingBaseEdit.value && editingEventId.value) {
calendarStore.deleteFirstOccurrence(editingEventId.value)
}
calendarStore.$history?.endCompound()
closeDialog()
}
function deleteEventFrom() {
if (!occurrenceContext.value) return
calendarStore.deleteFromOccurrence(occurrenceContext.value)
calendarStore.$history?.endCompound()
closeDialog()
}
@@ -431,8 +431,6 @@ watch([recurrenceEnabled, recurrenceInterval, recurrenceFrequency], () => {
})
watch(showDialog, (val, oldVal) => {
if (oldVal && !val) {
// Closed (cancel, escape, outside click) -> end compound session
calendarStore.$history?.endCompound()
if (dialogMode.value === 'create' && unsavedCreateId.value && !eventSaved.value) {
if (calendarStore.events?.has(unsavedCreateId.value)) {
calendarStore.deleteEvent(unsavedCreateId.value)

View File

@@ -314,7 +314,46 @@ function startLocalDrag(init, evt) {
realizedId: null,
}
store.$history?.beginCompound()
// If history is empty (no baseline), create a baseline snapshot BEFORE any movement mutations
try {
const isResize = init.mode === 'resize-left' || init.mode === 'resize-right'
// Move: only baseline if history empty. Resize: force baseline (so undo returns to pre-resize) but only once.
store.$history?._baselineIfNeeded?.(isResize)
const evs = []
if (store.events instanceof Map) {
for (const [id, ev] of store.events) {
evs.push({
id,
start: ev.startDate,
days: ev.days,
title: ev.title,
color: ev.colorId,
recur: ev.recur
? {
f: ev.recur.freq,
i: ev.recur.interval,
c: ev.recur.count,
w: Array.isArray(ev.recur.weekdays) ? ev.recur.weekdays.join('') : null,
}
: null,
})
}
}
console.debug(
isResize ? '[history] pre-resize baseline snapshot' : '[history] pre-drag baseline snapshot',
{
mode: init.mode,
events: evs,
weekend: store.weekend,
firstDay: store.config?.first_day,
},
)
} catch {}
// Enter drag suppression (prevent intermediate pushes)
try {
store.$history?._beginDrag?.()
} catch {}
if (evt.currentTarget && evt.pointerId !== undefined) {
try {
@@ -453,7 +492,10 @@ function onDragPointerUp(e) {
justDragged.value = false
}, 120)
}
store.$history?.endCompound()
// End drag suppression regardless; no post snapshot (pre-only model)
try {
store.$history?._endDrag?.()
} catch {}
}
const min = (a, b) => (a < b ? a : b)

View File

@@ -95,6 +95,10 @@ function toggleVisibility() {
// Settings dialog integration
const settingsDialog = ref(null)
function openSettings() {
// Capture baseline before opening settings
try {
calendarStore.$history?._baselineIfNeeded?.(true)
} catch {}
settingsDialog.value?.open()
}