Major new version #2
@ -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
|
||||||
|
}
|
||||||
|
const weeks = []
|
||||||
|
for (let vw = startVW; vw <= endVW; vw++) weeks.push(createWeek(vw))
|
||||||
|
visibleWeeks.value = weeks
|
||||||
|
lastScrollRange = { startVW, endVW }
|
||||||
}
|
}
|
||||||
return weeks
|
|
||||||
})
|
|
||||||
|
|
||||||
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>
|
||||||
|
@ -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: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user