From e50f90f277004141e3098be7d176184db8f48ba2 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Mon, 25 Aug 2025 19:05:14 -0600 Subject: [PATCH] Faster event updates (avoid full re-render) --- src/components/CalendarView.vue | 10 +++- src/plugins/virtualWeeks.js | 85 +++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/src/components/CalendarView.vue b/src/components/CalendarView.vue index f0c4c8d..cb278c4 100644 --- a/src/components/CalendarView.vue +++ b/src/components/CalendarView.vue @@ -87,7 +87,7 @@ const vwm = createVirtualWeekManager({ contentHeight, }) const visibleWeeks = vwm.visibleWeeks -const { scheduleWindowUpdate, resetWeeks } = vwm +const { scheduleWindowUpdate, resetWeeks, refreshEvents } = vwm // Scroll managers (after scheduleWindowUpdate available) const scrollManager = createScrollManager({ viewport, scheduleRebuild: scheduleWindowUpdate }) @@ -395,11 +395,17 @@ watch( watch( () => calendarStore.events, () => { - resetWeeks('events') + refreshEvents('events') }, { deep: true }, ) +// Reflect selection & events by rebuilding day objects in-place +watch( + () => [selection.value.startDate, selection.value.dayCount], + () => refreshEvents('selection'), +) + // Rebuild if viewport height changes (e.g., resize) window.addEventListener('resize', () => { if (viewport.value) viewportHeight.value = viewport.value.clientHeight diff --git a/src/plugins/virtualWeeks.js b/src/plugins/virtualWeeks.js index a9dbdc9..ad6dcbf 100644 --- a/src/plugins/virtualWeeks.js +++ b/src/plugins/virtualWeeks.js @@ -69,17 +69,14 @@ export function createVirtualWeekManager({ } } - for (let i = 0; i < 7; i++) { - const dateStr = toLocalString(cur, DEFAULT_TZ) + const collectEventsForDate = (dateStr, curDateObj) => { const storedEvents = [] - for (const ev of calendarStore.events.values()) { if (!ev.isRepeating && dateStr >= ev.startDate && dateStr <= ev.endDate) { storedEvents.push(ev) } } const dayEvents = [...storedEvents] - // Expand repeating events for (const base of repeatingBases) { if (dateStr >= base.startDate && dateStr <= base.endDate) { dayEvents.push({ ...base, _recurrenceIndex: 0, _baseId: base.id }) @@ -88,7 +85,7 @@ export function createVirtualWeekManager({ const baseStart = fromLocalString(base.startDate, DEFAULT_TZ) const baseEnd = fromLocalString(base.endDate, DEFAULT_TZ) const spanDays = Math.max(0, differenceInCalendarDays(baseEnd, baseStart)) - const currentDate = fromLocalString(dateStr, DEFAULT_TZ) + const currentDate = curDateObj let occurrenceFound = false for (let offset = 0; offset <= spanDays && !occurrenceFound; offset++) { const candidateStart = addDays(currentDate, -offset) @@ -114,6 +111,12 @@ export function createVirtualWeekManager({ } } } + return dayEvents + } + + for (let i = 0; i < 7; i++) { + const dateStr = toLocalString(cur, DEFAULT_TZ) + const dayEvents = collectEventsForDate(dateStr, fromLocalString(dateStr, DEFAULT_TZ)) const dow = cur.getDay() const isFirst = cur.getDate() === 1 if (isFirst) { @@ -281,6 +284,77 @@ export function createVirtualWeekManager({ scheduleWindowUpdate(reason) } + // Reflective update of only events inside currently visible weeks (keeps week objects stable) + function refreshEvents(reason = 'events-refresh') { + if (!visibleWeeks.value.length) return + const repeatingBases = [] + if (calendarStore.events) { + for (const ev of calendarStore.events.values()) if (ev.isRepeating) repeatingBases.push(ev) + } + const selStart = selection.value.startDate + const selCount = selection.value.dayCount + const selEnd = selStart && selCount > 0 ? addDaysStr(selStart, selCount - 1) : null + for (const week of visibleWeeks.value) { + for (const day of week.days) { + const dateStr = day.date + // Update selection flag + if (selStart && selEnd) day.isSelected = dateStr >= selStart && dateStr <= selEnd + else day.isSelected = false + // Rebuild events list for this day + const storedEvents = [] + for (const ev of calendarStore.events.values()) { + if (!ev.isRepeating && dateStr >= ev.startDate && dateStr <= ev.endDate) { + storedEvents.push(ev) + } + } + const dayEvents = [...storedEvents] + for (const base of repeatingBases) { + if (dateStr >= base.startDate && dateStr <= base.endDate) { + dayEvents.push({ ...base, _recurrenceIndex: 0, _baseId: base.id }) + continue + } + const baseStart = fromLocalString(base.startDate, DEFAULT_TZ) + const baseEnd = fromLocalString(base.endDate, DEFAULT_TZ) + const spanDays = Math.max(0, differenceInCalendarDays(baseEnd, baseStart)) + const currentDate = fromLocalString(dateStr, DEFAULT_TZ) + let occurrenceFound = false + for (let offset = 0; offset <= spanDays && !occurrenceFound; offset++) { + const candidateStart = addDays(currentDate, -offset) + const candidateStartStr = toLocalString(candidateStart, DEFAULT_TZ) + const occurrenceIndex = getOccurrenceIndex(base, candidateStartStr, DEFAULT_TZ) + if (occurrenceIndex !== null) { + const virtualEndDate = getVirtualOccurrenceEndDate( + base, + candidateStartStr, + DEFAULT_TZ, + ) + if (dateStr >= candidateStartStr && dateStr <= virtualEndDate) { + const virtualId = base.id + '_v_' + candidateStartStr + const alreadyExists = dayEvents.some((ev) => ev.id === virtualId) + if (!alreadyExists) { + dayEvents.push({ + ...base, + id: virtualId, + startDate: candidateStartStr, + endDate: virtualEndDate, + _recurrenceIndex: occurrenceIndex, + _baseId: base.id, + }) + } + occurrenceFound = true + } + } + } + } + day.events = dayEvents + } + } + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.debug('[VirtualWeeks] refreshEvents', reason, { weeks: visibleWeeks.value.length }) + } + } + function goToToday() { const top = addDays(new Date(calendarStore.now), -21) const targetWeekIndex = getWeekIndex(top) @@ -300,6 +374,7 @@ export function createVirtualWeekManager({ scheduleWindowUpdate, resetWeeks, updateVisibleWeeks, + refreshEvents, getWeekIndex, getFirstDayForVirtualWeek, goToToday,