Calendar view lazily updated instead of reflectivity, for improved performance.
This commit is contained in:
parent
cb7a111020
commit
50c79ff99f
@ -114,22 +114,49 @@ const todayString = computed(() => {
|
||||
return formatTodayString(d)
|
||||
})
|
||||
|
||||
const visibleWeeks = computed(() => {
|
||||
// PERFORMANCE: Maintain a manual cache of computed weeks instead of relying on
|
||||
// deep reactive tracking of every event & day object. We rebuild lazily when
|
||||
// (a) scrolling changes the needed range or (b) eventsMutation counter bumps.
|
||||
const visibleWeeks = ref([])
|
||||
let lastScrollRange = { startVW: null, endVW: null }
|
||||
let pendingRebuild = false
|
||||
|
||||
function scheduleRebuild(reason) {
|
||||
if (pendingRebuild) return
|
||||
pendingRebuild = true
|
||||
// Use requestIdleCallback when available, else fallback to rAF
|
||||
const cb = () => {
|
||||
pendingRebuild = false
|
||||
rebuildVisibleWeeks(reason)
|
||||
}
|
||||
if ('requestIdleCallback' in window) {
|
||||
requestIdleCallback(cb, { timeout: 120 })
|
||||
} else {
|
||||
requestAnimationFrame(cb)
|
||||
}
|
||||
}
|
||||
|
||||
function rebuildVisibleWeeks(reason) {
|
||||
const buffer = 10
|
||||
const startIdx = Math.floor((scrollTop.value - buffer * rowHeight.value) / rowHeight.value)
|
||||
const endIdx = Math.ceil(
|
||||
(scrollTop.value + viewportHeight.value + buffer * rowHeight.value) / rowHeight.value,
|
||||
)
|
||||
|
||||
const startVW = Math.max(minVirtualWeek.value, startIdx + minVirtualWeek.value)
|
||||
const endVW = Math.min(maxVirtualWeek.value, endIdx + minVirtualWeek.value)
|
||||
|
||||
const weeks = []
|
||||
for (let vw = startVW; vw <= endVW; vw++) {
|
||||
weeks.push(createWeek(vw))
|
||||
if (
|
||||
reason === 'scroll' &&
|
||||
lastScrollRange.startVW === startVW &&
|
||||
lastScrollRange.endVW === endVW &&
|
||||
visibleWeeks.value.length
|
||||
) {
|
||||
return
|
||||
}
|
||||
return weeks
|
||||
})
|
||||
const weeks = []
|
||||
for (let vw = startVW; vw <= endVW; vw++) weeks.push(createWeek(vw))
|
||||
visibleWeeks.value = weeks
|
||||
lastScrollRange = { startVW, endVW }
|
||||
}
|
||||
|
||||
const contentHeight = computed(() => {
|
||||
return totalVirtualWeeks.value * rowHeight.value
|
||||
@ -448,9 +475,8 @@ function calculateSelection(anchorStr, otherStr) {
|
||||
}
|
||||
|
||||
const onScroll = () => {
|
||||
if (viewport.value) {
|
||||
scrollTop.value = viewport.value.scrollTop
|
||||
}
|
||||
if (viewport.value) scrollTop.value = viewport.value.scrollTop
|
||||
scheduleRebuild('scroll')
|
||||
}
|
||||
|
||||
const handleJogwheelScrollTo = (newScrollTop) => {
|
||||
@ -473,6 +499,9 @@ onMounted(() => {
|
||||
calendarStore.updateCurrentDate()
|
||||
}, 60000)
|
||||
|
||||
// Initial build after mount & measurement
|
||||
scheduleRebuild('init')
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
@ -532,9 +561,25 @@ watch(
|
||||
const newScroll = (newTopWeekIndex - minVirtualWeek.value) * rowHeight.value
|
||||
scrollTop.value = newScroll
|
||||
if (viewport.value) viewport.value.scrollTop = newScroll
|
||||
scheduleRebuild('first-day-change')
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
// Watch lightweight mutation counter only (not deep events map) and rebuild lazily
|
||||
watch(
|
||||
() => calendarStore.events,
|
||||
() => {
|
||||
scheduleRebuild('events')
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
// Rebuild if viewport height changes (e.g., resize)
|
||||
window.addEventListener('resize', () => {
|
||||
if (viewport.value) viewportHeight.value = viewport.value.clientHeight
|
||||
scheduleRebuild('resize')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -15,6 +15,9 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
today: toLocalString(new Date(), DEFAULT_TZ),
|
||||
now: new Date().toISOString(),
|
||||
events: new Map(),
|
||||
// Lightweight mutation counter so views can rebuild in a throttled / idle way
|
||||
// without tracking deep reactivity on every event object.
|
||||
eventsMutation: 0,
|
||||
weekend: getLocaleWeekendDays(),
|
||||
_holidayConfigSignature: null,
|
||||
_holidaysInitialized: false,
|
||||
@ -107,6 +110,14 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
return 'e-' + Math.random().toString(36).slice(2, 10) + '-' + Date.now().toString(36)
|
||||
},
|
||||
|
||||
notifyEventsChanged() {
|
||||
// Bump simple counter (wrapping to avoid overflow in extreme long sessions)
|
||||
this.eventsMutation = (this.eventsMutation + 1) % 1_000_000_000
|
||||
},
|
||||
touchEvents() {
|
||||
this.notifyEventsChanged()
|
||||
},
|
||||
|
||||
createEvent(eventData) {
|
||||
const singleDay = eventData.startDate === eventData.endDate
|
||||
const event = {
|
||||
@ -125,6 +136,7 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
isRepeating: eventData.repeat && eventData.repeat !== 'none',
|
||||
}
|
||||
this.events.set(event.id, { ...event, isSpanning: event.startDate < event.endDate })
|
||||
this.notifyEventsChanged()
|
||||
return event.id
|
||||
},
|
||||
|
||||
@ -155,6 +167,7 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
|
||||
deleteEvent(eventId) {
|
||||
this.events.delete(eventId)
|
||||
this.notifyEventsChanged()
|
||||
},
|
||||
|
||||
deleteFirstOccurrence(baseId) {
|
||||
@ -186,6 +199,7 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
base.endDate = newEndStr
|
||||
if (numericCount !== Infinity) base.repeatCount = String(Math.max(1, numericCount - 1))
|
||||
this.events.set(baseId, { ...base, isSpanning: base.startDate < base.endDate })
|
||||
this.notifyEventsChanged()
|
||||
},
|
||||
|
||||
deleteSingleOccurrence(ctx) {
|
||||
@ -231,6 +245,7 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
repeatCount: remainingCount,
|
||||
repeatWeekdays: snapshot.repeatWeekdays,
|
||||
})
|
||||
this.notifyEventsChanged()
|
||||
},
|
||||
|
||||
deleteFromOccurrence(ctx) {
|
||||
@ -242,6 +257,7 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
return
|
||||
}
|
||||
this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex)
|
||||
this.notifyEventsChanged()
|
||||
},
|
||||
|
||||
setEventRange(eventId, newStartStr, newEndStr, { mode = 'auto' } = {}) {
|
||||
@ -283,6 +299,7 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
}
|
||||
}
|
||||
this.events.set(eventId, { ...snapshot, isSpanning: snapshot.startDate < snapshot.endDate })
|
||||
this.notifyEventsChanged()
|
||||
},
|
||||
|
||||
splitMoveVirtualOccurrence(baseId, occurrenceDateStr, newStartStr, newEndStr) {
|
||||
@ -359,6 +376,7 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
repeatCount: remainingCount,
|
||||
repeatWeekdays,
|
||||
})
|
||||
this.notifyEventsChanged()
|
||||
},
|
||||
|
||||
splitRepeatSeries(baseId, occurrenceIndex, newStartStr, newEndStr) {
|
||||
@ -395,6 +413,7 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
const rc = parseInt(ev.repeatCount, 10)
|
||||
if (!isNaN(rc)) ev.repeatCount = String(Math.min(rc, index))
|
||||
}
|
||||
this.notifyEventsChanged()
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user