Major new version #2
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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 -->
|
||||||
|
Loading…
x
Reference in New Issue
Block a user