Major refactoring for cleanup, with various bugfixes. Weekday selector in Settings now shows workdays/weekend bars based on current locale (also used to choose default values). Weekday selector in event dialog uses the days set in settings, as expected.
150 lines
4.3 KiB
Vue
150 lines
4.3 KiB
Vue
<script setup>
|
|
import { computed } from 'vue'
|
|
import { useCalendarStore } from '@/stores/CalendarStore'
|
|
import {
|
|
getLocalizedWeekdayNames,
|
|
reorderByFirstDay,
|
|
getISOWeek,
|
|
getISOWeekYear,
|
|
MIN_YEAR,
|
|
MAX_YEAR,
|
|
} from '@/utils/date'
|
|
import Numeric from '@/components/Numeric.vue'
|
|
import { addDays } from 'date-fns'
|
|
|
|
const props = defineProps({
|
|
scrollTop: { type: Number, default: 0 },
|
|
rowHeight: { type: Number, default: 64 },
|
|
minVirtualWeek: { type: Number, default: 0 },
|
|
})
|
|
|
|
const calendarStore = useCalendarStore()
|
|
|
|
// Emits year-change events
|
|
const emit = defineEmits(['year-change'])
|
|
|
|
const baseDate = computed(() => new Date(1970, 0, 4 + calendarStore.config.first_day))
|
|
const WEEK_MS = 7 * 24 * 60 * 60 * 1000
|
|
|
|
const topVirtualWeek = computed(() => {
|
|
const topDisplayIndex = Math.floor(props.scrollTop / props.rowHeight)
|
|
return topDisplayIndex + props.minVirtualWeek
|
|
})
|
|
|
|
const currentYear = computed(() => {
|
|
const weekStart = addDays(baseDate.value, topVirtualWeek.value * 7)
|
|
const anchor = addDays(weekStart, (4 - weekStart.getDay() + 7) % 7)
|
|
return getISOWeekYear(anchor)
|
|
})
|
|
|
|
function virtualWeekOf(d) {
|
|
const o = (d.getDay() - calendarStore.config.first_day + 7) % 7
|
|
const fd = addDays(d, -o)
|
|
return Math.floor((fd.getTime() - baseDate.value.getTime()) / WEEK_MS)
|
|
}
|
|
|
|
function isoWeekMonday(isoYear, isoWeek) {
|
|
const jan4 = new Date(isoYear, 0, 4)
|
|
const week1Mon = addDays(jan4, -((jan4.getDay() + 6) % 7))
|
|
return addDays(week1Mon, (isoWeek - 1) * 7)
|
|
}
|
|
|
|
function changeYear(y) {
|
|
if (y == null) return
|
|
y = Math.round(Math.max(MIN_YEAR, Math.min(MAX_YEAR, y)))
|
|
if (y === currentYear.value) return
|
|
const vw = topVirtualWeek.value
|
|
// Fraction within current row
|
|
const weekStartScroll = (vw - props.minVirtualWeek) * props.rowHeight
|
|
const frac = Math.max(0, Math.min(1, (props.scrollTop - weekStartScroll) / props.rowHeight))
|
|
// Anchor Thursday of current calendar week
|
|
const curCalWeekStart = addDays(baseDate.value, vw * 7)
|
|
const curAnchorThu = addDays(curCalWeekStart, (4 - curCalWeekStart.getDay() + 7) % 7)
|
|
let isoW = getISOWeek(curAnchorThu)
|
|
// Build Monday of ISO week
|
|
let weekMon = isoWeekMonday(y, isoW)
|
|
if (getISOWeekYear(weekMon) !== y) {
|
|
isoW--
|
|
weekMon = isoWeekMonday(y, isoW)
|
|
}
|
|
// Align to configured first day
|
|
const shift = (weekMon.getDay() - calendarStore.config.first_day + 7) % 7
|
|
const calWeekStart = addDays(weekMon, -shift)
|
|
const targetVW = virtualWeekOf(calWeekStart)
|
|
let scrollTop = (targetVW - props.minVirtualWeek) * props.rowHeight + frac * props.rowHeight
|
|
if (Math.abs(scrollTop - props.scrollTop) < 0.5) scrollTop += 0.25 * props.rowHeight
|
|
emit('year-change', { year: y, scrollTop })
|
|
}
|
|
|
|
const weekdayNames = computed(() => {
|
|
// Reorder names & weekend flags
|
|
const sundayFirstNames = getLocalizedWeekdayNames()
|
|
const reorderedNames = reorderByFirstDay(sundayFirstNames, calendarStore.config.first_day)
|
|
const reorderedWeekend = reorderByFirstDay(calendarStore.weekend, calendarStore.config.first_day)
|
|
|
|
return reorderedNames.map((name, i) => ({
|
|
name,
|
|
isWeekend: reorderedWeekend[i],
|
|
}))
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="calendar-header">
|
|
<div class="year-label">
|
|
<Numeric
|
|
:model-value="currentYear"
|
|
@update:modelValue="changeYear"
|
|
:min="MIN_YEAR"
|
|
:max="MAX_YEAR"
|
|
:step="1"
|
|
aria-label="Year"
|
|
number-prefix=""
|
|
number-postfix=""
|
|
/>
|
|
</div>
|
|
<div
|
|
v-for="day in weekdayNames"
|
|
:key="day.name"
|
|
class="dow"
|
|
:class="{ workday: !day.isWeekend, weekend: day.isWeekend }"
|
|
>
|
|
{{ day.name }}
|
|
</div>
|
|
<div class="overlay-header-spacer"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.calendar-header {
|
|
display: grid;
|
|
grid-template-columns: var(--week-w) repeat(7, 1fr) var(--month-w);
|
|
border-bottom: 2px solid var(--muted);
|
|
align-items: last baseline;
|
|
flex-shrink: 0;
|
|
width: 100%;
|
|
/* Prevent text selection */
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
user-select: none;
|
|
-webkit-touch-callout: none;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
.dow {
|
|
text-transform: uppercase;
|
|
text-align: center;
|
|
font-weight: 600;
|
|
font-size: 1.2em;
|
|
}
|
|
.dow.weekend {
|
|
color: var(--weekend);
|
|
}
|
|
.dow.workday {
|
|
color: var(--workday);
|
|
}
|
|
.overlay-header-spacer {
|
|
grid-area: auto;
|
|
}
|
|
</style>
|