Settings now work immediately.

This commit is contained in:
Leo Vasanko 2025-08-23 11:21:39 -06:00
parent d555523f42
commit 83145d9b2a
3 changed files with 67 additions and 75 deletions

View File

@ -12,10 +12,10 @@ const props = defineProps({
const calendarStore = useCalendarStore() const calendarStore = useCalendarStore()
// Emits: year-change -> { year, scrollTop } // Emits year-change events
const emit = defineEmits(['year-change']) const emit = defineEmits(['year-change'])
const baseDate = new Date(1970, 0, 4 + calendarStore.config.first_day) const baseDate = computed(() => new Date(1970, 0, 4 + calendarStore.config.first_day))
const WEEK_MS = 7 * 24 * 60 * 60 * 1000 const WEEK_MS = 7 * 24 * 60 * 60 * 1000
const topVirtualWeek = computed(() => { const topVirtualWeek = computed(() => {
@ -24,9 +24,9 @@ const topVirtualWeek = computed(() => {
}) })
const currentYear = computed(() => { const currentYear = computed(() => {
const weekStart = new Date(baseDate) const weekStart = new Date(baseDate.value)
weekStart.setDate(weekStart.getDate() + topVirtualWeek.value * 7) weekStart.setDate(weekStart.getDate() + topVirtualWeek.value * 7)
// ISO anchor: Thursday of current calendar week // ISO anchor Thursday
const anchor = new Date(weekStart) const anchor = new Date(weekStart)
anchor.setDate(anchor.getDate() + ((4 - anchor.getDay() + 7) % 7)) anchor.setDate(anchor.getDate() + ((4 - anchor.getDay() + 7) % 7))
return isoWeekInfo(anchor).year return isoWeekInfo(anchor).year
@ -36,11 +36,10 @@ function virtualWeekOf(d) {
const o = (d.getDay() - calendarStore.config.first_day + 7) % 7 const o = (d.getDay() - calendarStore.config.first_day + 7) % 7
const fd = new Date(d) const fd = new Date(d)
fd.setDate(d.getDate() - o) fd.setDate(d.getDate() - o)
return Math.floor((fd - baseDate) / WEEK_MS) return Math.floor((fd - baseDate.value) / WEEK_MS)
} }
function isoWeekMonday(isoYear, isoWeek) { function isoWeekMonday(isoYear, isoWeek) {
// Monday of ISO week 1
const jan4 = new Date(isoYear, 0, 4) const jan4 = new Date(isoYear, 0, 4)
const week1Mon = new Date(jan4) const week1Mon = new Date(jan4)
week1Mon.setDate(week1Mon.getDate() - ((week1Mon.getDay() + 6) % 7)) week1Mon.setDate(week1Mon.getDate() - ((week1Mon.getDay() + 6) % 7))
@ -54,16 +53,16 @@ function changeYear(y) {
y = Math.round(Math.max(calendarStore.minYear, Math.min(calendarStore.maxYear, y))) y = Math.round(Math.max(calendarStore.minYear, Math.min(calendarStore.maxYear, y)))
if (y === currentYear.value) return if (y === currentYear.value) return
const vw = topVirtualWeek.value const vw = topVirtualWeek.value
// Fraction within current row for smooth vertical position preservation // Fraction within current row
const weekStartScroll = (vw - props.minVirtualWeek) * props.rowHeight const weekStartScroll = (vw - props.minVirtualWeek) * props.rowHeight
const frac = Math.max(0, Math.min(1, (props.scrollTop - weekStartScroll) / props.rowHeight)) const frac = Math.max(0, Math.min(1, (props.scrollTop - weekStartScroll) / props.rowHeight))
// Derive current ISO week via anchor Thursday // Anchor Thursday of current calendar week
const curCalWeekStart = new Date(baseDate) const curCalWeekStart = new Date(baseDate.value)
curCalWeekStart.setDate(curCalWeekStart.getDate() + vw * 7) curCalWeekStart.setDate(curCalWeekStart.getDate() + vw * 7)
const curAnchorThu = new Date(curCalWeekStart) const curAnchorThu = new Date(curCalWeekStart)
curAnchorThu.setDate(curAnchorThu.getDate() + ((4 - curAnchorThu.getDay() + 7) % 7)) curAnchorThu.setDate(curAnchorThu.getDate() + ((4 - curAnchorThu.getDay() + 7) % 7))
let { week: isoW } = isoWeekInfo(curAnchorThu) let { week: isoW } = isoWeekInfo(curAnchorThu)
// Build Monday of that ISO week in target year; fallback if week absent (53) // Build Monday of ISO week
let weekMon = isoWeekMonday(y, isoW) let weekMon = isoWeekMonday(y, isoW)
if (isoWeekInfo(weekMon).year !== y) { if (isoWeekInfo(weekMon).year !== y) {
isoW-- isoW--
@ -80,7 +79,7 @@ function changeYear(y) {
} }
const weekdayNames = computed(() => { const weekdayNames = computed(() => {
// Get Monday-first names, then reorder by first day, then add weekend info // Reorder names & weekend flags
const mondayFirstNames = getLocalizedWeekdayNames() const mondayFirstNames = getLocalizedWeekdayNames()
const sundayFirstNames = [mondayFirstNames[6], ...mondayFirstNames.slice(0, 6)] const sundayFirstNames = [mondayFirstNames[6], ...mondayFirstNames.slice(0, 6)]
const reorderedNames = reorderByFirstDay(sundayFirstNames, calendarStore.config.first_day) const reorderedNames = reorderByFirstDay(sundayFirstNames, calendarStore.config.first_day)

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onMounted, onBeforeUnmount, computed } from 'vue' import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue'
import { useCalendarStore } from '@/stores/CalendarStore' import { useCalendarStore } from '@/stores/CalendarStore'
import CalendarHeader from '@/components/CalendarHeader.vue' import CalendarHeader from '@/components/CalendarHeader.vue'
import CalendarWeek from '@/components/CalendarWeek.vue' import CalendarWeek from '@/components/CalendarWeek.vue'
@ -39,7 +39,7 @@ function createEventFromSelection() {
const scrollTop = ref(0) const scrollTop = ref(0)
const viewportHeight = ref(600) const viewportHeight = ref(600)
const rowHeight = ref(64) const rowHeight = ref(64)
const baseDate = new Date(1970, 0, 4 + calendarStore.config.first_day) const baseDate = computed(() => new Date(1970, 0, 4 + calendarStore.config.first_day))
const selection = ref({ startDate: null, dayCount: 0 }) const selection = ref({ startDate: null, dayCount: 0 })
const isDragging = ref(false) const isDragging = ref(false)
@ -52,7 +52,7 @@ const minVirtualWeek = computed(() => {
const firstDayOfWeek = new Date(date) const firstDayOfWeek = new Date(date)
const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7 const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7
firstDayOfWeek.setDate(date.getDate() - dayOffset) firstDayOfWeek.setDate(date.getDate() - dayOffset)
return Math.floor((firstDayOfWeek - baseDate) / WEEK_MS) return Math.floor((firstDayOfWeek - baseDate.value) / WEEK_MS)
}) })
const maxVirtualWeek = computed(() => { const maxVirtualWeek = computed(() => {
@ -60,7 +60,7 @@ const maxVirtualWeek = computed(() => {
const firstDayOfWeek = new Date(date) const firstDayOfWeek = new Date(date)
const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7 const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7
firstDayOfWeek.setDate(date.getDate() - dayOffset) firstDayOfWeek.setDate(date.getDate() - dayOffset)
return Math.floor((firstDayOfWeek - baseDate) / WEEK_MS) return Math.floor((firstDayOfWeek - baseDate.value) / WEEK_MS)
}) })
const totalVirtualWeeks = computed(() => { const totalVirtualWeeks = computed(() => {
@ -126,18 +126,17 @@ function getWeekIndex(date) {
const firstDayOfWeek = new Date(date) const firstDayOfWeek = new Date(date)
const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7 const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7
firstDayOfWeek.setDate(date.getDate() - dayOffset) firstDayOfWeek.setDate(date.getDate() - dayOffset)
return Math.floor((firstDayOfWeek - baseDate) / WEEK_MS) return Math.floor((firstDayOfWeek - baseDate.value) / WEEK_MS)
} }
function getFirstDayForVirtualWeek(virtualWeek) { function getFirstDayForVirtualWeek(virtualWeek) {
const firstDay = new Date(baseDate) const firstDay = new Date(baseDate.value)
firstDay.setDate(firstDay.getDate() + virtualWeek * 7) firstDay.setDate(firstDay.getDate() + virtualWeek * 7)
return firstDay return firstDay
} }
function createWeek(virtualWeek) { function createWeek(virtualWeek) {
const firstDay = getFirstDayForVirtualWeek(virtualWeek) const firstDay = getFirstDayForVirtualWeek(virtualWeek)
// ISO week number should be based on the Thursday of this week (anchor) to avoid off-by-one when week starts Sunday or other days
const isoAnchor = new Date(firstDay) const isoAnchor = new Date(firstDay)
isoAnchor.setDate(isoAnchor.getDate() + ((4 - isoAnchor.getDay() + 7) % 7)) isoAnchor.setDate(isoAnchor.getDate() + ((4 - isoAnchor.getDay() + 7) % 7))
const weekNumber = isoWeekInfo(isoAnchor).week const weekNumber = isoWeekInfo(isoAnchor).week
@ -147,7 +146,7 @@ function createWeek(virtualWeek) {
let monthToLabel = null let monthToLabel = null
let labelYear = null let labelYear = null
// Precollect unique repeating base events once (avoid nested loops for each day) // Collect repeating base events once
const repeatingBases = [] const repeatingBases = []
if (calendarStore.events) { if (calendarStore.events) {
for (const ev of calendarStore.events.values()) { for (const ev of calendarStore.events.values()) {
@ -168,10 +167,10 @@ function createWeek(virtualWeek) {
// Build day events starting with stored (base/spanning) then virtual occurrences // Build day events starting with stored (base/spanning) then virtual occurrences
const dayEvents = [...storedEvents] const dayEvents = [...storedEvents]
for (const base of repeatingBases) { for (const base of repeatingBases) {
// Skip if the base itself already on this date (already in storedEvents) // Skip if base spans this date
if (dateStr >= base.startDate && dateStr <= base.endDate) continue if (dateStr >= base.startDate && dateStr <= base.endDate) continue
// Check if any occurrence of this repeating event spans through this date // Check if any virtual occurrence spans this date
const baseStart = fromLocalString(base.startDate) const baseStart = fromLocalString(base.startDate)
const baseEnd = fromLocalString(base.endDate) const baseEnd = fromLocalString(base.endDate)
const spanDays = Math.max(0, Math.round((baseEnd - baseStart) / (24 * 60 * 60 * 1000))) const spanDays = Math.max(0, Math.round((baseEnd - baseStart) / (24 * 60 * 60 * 1000)))
@ -179,7 +178,7 @@ function createWeek(virtualWeek) {
let occurrenceFound = false let occurrenceFound = false
// Check dates going backwards to find an occurrence that might span to this date // Walk backwards within span to find occurrence start
for (let offset = 0; offset <= spanDays && !occurrenceFound; offset++) { for (let offset = 0; offset <= spanDays && !occurrenceFound; offset++) {
const candidateStart = new Date(currentDate) const candidateStart = new Date(currentDate)
candidateStart.setDate(candidateStart.getDate() - offset) candidateStart.setDate(candidateStart.getDate() - offset)
@ -430,6 +429,20 @@ const handleHeaderYearChange = ({ scrollTop: st }) => {
function openSettings() { function openSettings() {
settingsDialog.value?.open() settingsDialog.value?.open()
} }
// Preserve approximate top visible date when first_day changes
watch(
() => calendarStore.config.first_day,
() => {
const currentTopVW = Math.floor(scrollTop.value / rowHeight.value) + minVirtualWeek.value
const currentTopDate = getFirstDayForVirtualWeek(currentTopVW)
requestAnimationFrame(() => {
const newTopWeekIndex = getWeekIndex(currentTopDate)
const newScroll = (newTopWeekIndex - minVirtualWeek.value) * rowHeight.value
scrollTop.value = newScroll
if (viewport.value) viewport.value.scrollTop = newScroll
})
},
)
</script> </script>
<template> <template>

View File

@ -1,33 +1,28 @@
<script setup> <script setup>
import { ref, watch } from 'vue' import { ref, computed } from 'vue'
import BaseDialog from './BaseDialog.vue' import BaseDialog from './BaseDialog.vue'
import { useCalendarStore } from '@/stores/CalendarStore' import { useCalendarStore } from '@/stores/CalendarStore'
import WeekdaySelector from './WeekdaySelector.vue'
const show = ref(false) const show = ref(false)
const calendarStore = useCalendarStore() const calendarStore = useCalendarStore()
// Local copies for editing // Reactive bindings to store
const firstDay = ref(calendarStore.config.first_day) const firstDay = computed({
const weekend = ref([...calendarStore.weekend]) get: () => calendarStore.config.first_day,
set: (v) => (calendarStore.config.first_day = v),
})
const weekend = computed({
get: () => calendarStore.weekend,
set: (v) => (calendarStore.weekend = [...v]),
})
function open() { function open() {
firstDay.value = calendarStore.config.first_day
weekend.value = [...calendarStore.weekend]
show.value = true show.value = true
} }
function close() { function close() {
show.value = false show.value = false
} }
function save() {
calendarStore.config.first_day = firstDay.value
calendarStore.weekend = [...weekend.value]
show.value = false
}
function toggleWeekend(idx) {
weekend.value[idx] = !weekend.value[idx]
}
defineExpose({ open }) defineExpose({ open })
</script> </script>
@ -48,23 +43,12 @@ defineExpose({ open })
</label> </label>
<div class="weekend-select ec-field"> <div class="weekend-select ec-field">
<span>Weekend days</span> <span>Weekend days</span>
<div class="weekday-pills"> <WeekdaySelector v-model="weekend" :first-day="firstDay" />
<button
v-for="(isW, i) in weekend"
:key="i"
type="button"
@click="toggleWeekend(i)"
:class="['pill', { active: isW }]"
>
{{ ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'][i] }}
</button>
</div>
</div> </div>
</div> </div>
<template #footer> <template #footer>
<div class="footer-row"> <div class="footer-row">
<button type="button" class="ec-btn" @click="close">Cancel</button> <button type="button" class="ec-btn close-btn" @click="close">Close</button>
<button type="button" class="ec-btn save-btn" @click="save">Save</button>
</div> </div>
</template> </template>
</BaseDialog> </BaseDialog>
@ -90,26 +74,7 @@ select {
padding: 0.4rem 0.5rem; padding: 0.4rem 0.5rem;
border-radius: 0.4rem; border-radius: 0.4rem;
} }
.weekday-pills { /* WeekdaySelector display tweaks */
display: flex;
gap: 0.35rem;
flex-wrap: wrap;
}
.pill {
border: 1px solid var(--muted);
background: var(--panel-alt, transparent);
color: var(--ink);
padding: 0.35rem 0.6rem;
border-radius: 0.5rem;
font-size: 0.7rem;
cursor: pointer;
}
.pill.active {
background: var(--today);
color: #000;
border-color: var(--today);
font-weight: 600;
}
.footer-row { .footer-row {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
@ -124,10 +89,25 @@ select {
border-radius: 0.4rem; border-radius: 0.4rem;
cursor: pointer; cursor: pointer;
} }
.ec-btn.save-btn { .ec-btn.close-btn {
background: var(--today); background: var(--panel-alt);
color: #000; border-color: var(--muted);
border-color: transparent;
font-weight: 500; font-weight: 500;
} }
/* Adjust WeekdaySelector size inside settings */
::v-deep(.weekgrid) {
margin-top: 0.25rem;
}
::v-deep(.day) {
font-size: 0.6rem;
padding: 0.45rem 0.25rem;
}
::v-deep(.workday-weekend div.workday) {
background: var(--muted);
}
::v-deep(.workday-weekend div.weekend) {
background: var(--strong);
}
</style> </style>
<!-- settings dialog -->