Major new version #2

Merged
LeoVasanko merged 86 commits from vol002 into main 2025-08-26 05:58:24 +01:00
3 changed files with 77 additions and 13 deletions
Showing only changes of commit 50c79ff99f - Show all commits

View File

@ -114,22 +114,49 @@ const todayString = computed(() => {
return formatTodayString(d) 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 buffer = 10
const startIdx = Math.floor((scrollTop.value - buffer * rowHeight.value) / rowHeight.value) const startIdx = Math.floor((scrollTop.value - buffer * rowHeight.value) / rowHeight.value)
const endIdx = Math.ceil( const endIdx = Math.ceil(
(scrollTop.value + viewportHeight.value + buffer * rowHeight.value) / rowHeight.value, (scrollTop.value + viewportHeight.value + buffer * rowHeight.value) / rowHeight.value,
) )
const startVW = Math.max(minVirtualWeek.value, startIdx + minVirtualWeek.value) const startVW = Math.max(minVirtualWeek.value, startIdx + minVirtualWeek.value)
const endVW = Math.min(maxVirtualWeek.value, endIdx + minVirtualWeek.value) const endVW = Math.min(maxVirtualWeek.value, endIdx + minVirtualWeek.value)
if (
const weeks = [] reason === 'scroll' &&
for (let vw = startVW; vw <= endVW; vw++) { lastScrollRange.startVW === startVW &&
weeks.push(createWeek(vw)) 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(() => { const contentHeight = computed(() => {
return totalVirtualWeeks.value * rowHeight.value return totalVirtualWeeks.value * rowHeight.value
@ -448,9 +475,8 @@ function calculateSelection(anchorStr, otherStr) {
} }
const onScroll = () => { const onScroll = () => {
if (viewport.value) { if (viewport.value) scrollTop.value = viewport.value.scrollTop
scrollTop.value = viewport.value.scrollTop scheduleRebuild('scroll')
}
} }
const handleJogwheelScrollTo = (newScrollTop) => { const handleJogwheelScrollTo = (newScrollTop) => {
@ -473,6 +499,9 @@ onMounted(() => {
calendarStore.updateCurrentDate() calendarStore.updateCurrentDate()
}, 60000) }, 60000)
// Initial build after mount & measurement
scheduleRebuild('init')
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearInterval(timer) clearInterval(timer)
}) })
@ -532,9 +561,25 @@ watch(
const newScroll = (newTopWeekIndex - minVirtualWeek.value) * rowHeight.value const newScroll = (newTopWeekIndex - minVirtualWeek.value) * rowHeight.value
scrollTop.value = newScroll scrollTop.value = newScroll
if (viewport.value) viewport.value.scrollTop = 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> </script>
<template> <template>

View File

@ -15,6 +15,9 @@ export const useCalendarStore = defineStore('calendar', {
today: toLocalString(new Date(), DEFAULT_TZ), today: toLocalString(new Date(), DEFAULT_TZ),
now: new Date().toISOString(), now: new Date().toISOString(),
events: new Map(), 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(), weekend: getLocaleWeekendDays(),
_holidayConfigSignature: null, _holidayConfigSignature: null,
_holidaysInitialized: false, _holidaysInitialized: false,
@ -107,6 +110,14 @@ export const useCalendarStore = defineStore('calendar', {
return 'e-' + Math.random().toString(36).slice(2, 10) + '-' + Date.now().toString(36) 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) { createEvent(eventData) {
const singleDay = eventData.startDate === eventData.endDate const singleDay = eventData.startDate === eventData.endDate
const event = { const event = {
@ -125,6 +136,7 @@ export const useCalendarStore = defineStore('calendar', {
isRepeating: eventData.repeat && eventData.repeat !== 'none', isRepeating: eventData.repeat && eventData.repeat !== 'none',
} }
this.events.set(event.id, { ...event, isSpanning: event.startDate < event.endDate }) this.events.set(event.id, { ...event, isSpanning: event.startDate < event.endDate })
this.notifyEventsChanged()
return event.id return event.id
}, },
@ -155,6 +167,7 @@ export const useCalendarStore = defineStore('calendar', {
deleteEvent(eventId) { deleteEvent(eventId) {
this.events.delete(eventId) this.events.delete(eventId)
this.notifyEventsChanged()
}, },
deleteFirstOccurrence(baseId) { deleteFirstOccurrence(baseId) {
@ -186,6 +199,7 @@ export const useCalendarStore = defineStore('calendar', {
base.endDate = newEndStr base.endDate = newEndStr
if (numericCount !== Infinity) base.repeatCount = String(Math.max(1, numericCount - 1)) if (numericCount !== Infinity) base.repeatCount = String(Math.max(1, numericCount - 1))
this.events.set(baseId, { ...base, isSpanning: base.startDate < base.endDate }) this.events.set(baseId, { ...base, isSpanning: base.startDate < base.endDate })
this.notifyEventsChanged()
}, },
deleteSingleOccurrence(ctx) { deleteSingleOccurrence(ctx) {
@ -231,6 +245,7 @@ export const useCalendarStore = defineStore('calendar', {
repeatCount: remainingCount, repeatCount: remainingCount,
repeatWeekdays: snapshot.repeatWeekdays, repeatWeekdays: snapshot.repeatWeekdays,
}) })
this.notifyEventsChanged()
}, },
deleteFromOccurrence(ctx) { deleteFromOccurrence(ctx) {
@ -242,6 +257,7 @@ export const useCalendarStore = defineStore('calendar', {
return return
} }
this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex) this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex)
this.notifyEventsChanged()
}, },
setEventRange(eventId, newStartStr, newEndStr, { mode = 'auto' } = {}) { 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.events.set(eventId, { ...snapshot, isSpanning: snapshot.startDate < snapshot.endDate })
this.notifyEventsChanged()
}, },
splitMoveVirtualOccurrence(baseId, occurrenceDateStr, newStartStr, newEndStr) { splitMoveVirtualOccurrence(baseId, occurrenceDateStr, newStartStr, newEndStr) {
@ -359,6 +376,7 @@ export const useCalendarStore = defineStore('calendar', {
repeatCount: remainingCount, repeatCount: remainingCount,
repeatWeekdays, repeatWeekdays,
}) })
this.notifyEventsChanged()
}, },
splitRepeatSeries(baseId, occurrenceIndex, newStartStr, newEndStr) { splitRepeatSeries(baseId, occurrenceIndex, newStartStr, newEndStr) {
@ -395,6 +413,7 @@ export const useCalendarStore = defineStore('calendar', {
const rc = parseInt(ev.repeatCount, 10) const rc = parseInt(ev.repeatCount, 10)
if (!isNaN(rc)) ev.repeatCount = String(Math.min(rc, index)) if (!isNaN(rc)) ev.repeatCount = String(Math.min(rc, index))
} }
this.notifyEventsChanged()
}, },
}, },
persist: { persist: {