160 lines
6.3 KiB
JavaScript
160 lines
6.3 KiB
JavaScript
import calendarUtil from "../calendarUtil.js";
|
|
|
|
class WeekHeaderRow extends Shadow {
|
|
constructor(groupedDays, calendars, onDayTap = null) {
|
|
super()
|
|
this.groupedDays = groupedDays;
|
|
this.calendars = calendars;
|
|
this.onDayTap = onDayTap;
|
|
}
|
|
|
|
render() {
|
|
const allDayEvents = this.collectAllDayEvents();
|
|
const maxEventsPerDay = Math.max(0, ...this.groupedDays.map(g => g.allDay.length))
|
|
|
|
VStack(() => {
|
|
HStack(() => {
|
|
this.groupedDays.forEach((group, index) => {
|
|
const day = group.day;
|
|
const today = calendarUtil.isToday(day);
|
|
const isLast = index === this.groupedDays.length - 1;
|
|
|
|
VStack(() => {
|
|
h3(day.getDate())
|
|
.margin(0)
|
|
.fontSize(1.35, em)
|
|
.fontWeight("700")
|
|
.lineHeight("1")
|
|
.textAlign("center")
|
|
|
|
p(day.toLocaleDateString("en-US", { weekday: "short" }).toUpperCase())
|
|
.margin(0)
|
|
.fontSize(0.72, em)
|
|
.fontWeight("600")
|
|
.letterSpacing(0.04, em)
|
|
.opacity(today ? 1 : 0.5)
|
|
.textAlign("center")
|
|
})
|
|
.color(today ? "var(--quillred)" : "var(--headertext)")
|
|
.flex(1)
|
|
.width(0, px)
|
|
.minWidth(0)
|
|
.justifyContent("center")
|
|
.alignItems("center")
|
|
.gap(0.5, em)
|
|
.paddingTop(0.85, em)
|
|
.background(today ? "var(--desktop-item-background)" : "transparent")
|
|
.borderRight(isLast ? "1px solid transparent" : "1px solid var(--divider)")
|
|
.boxSizing("border-box")
|
|
.paddingBottom(maxEventsPerDay > 0 ? (maxEventsPerDay * 2.0) + 0.7 : 0.35, em)
|
|
.cursor("pointer")
|
|
.onTap(() => { this.onDayTap(day) })
|
|
})
|
|
})
|
|
.width(100, pct)
|
|
.alignItems("stretch")
|
|
|
|
this.allDayRow(allDayEvents, maxEventsPerDay);
|
|
})
|
|
.width(100, pct)
|
|
.position("relative")
|
|
.background("var(--sidebottombars)")
|
|
.borderBottom("1px solid var(--divider)")
|
|
}
|
|
|
|
allDayRow(allDayEvents, maxEventsPerDay) {
|
|
if (allDayEvents.length === 0) return;
|
|
|
|
const rowHeight = 1.75;
|
|
const gap = 0.25;
|
|
const totalHeight = maxEventsPerDay * rowHeight + (maxEventsPerDay - 1) * gap;
|
|
|
|
ZStack(() => {
|
|
allDayEvents.forEach(({ event, startIndex, endIndex, clippedLeft, clippedRight }) => {
|
|
this.spanningEvent(event, startIndex, endIndex, rowHeight, gap, clippedLeft, clippedRight);
|
|
})
|
|
})
|
|
.position("absolute")
|
|
.bottom(0.25, em)
|
|
.left(0, px)
|
|
.width(100, pct)
|
|
.height(totalHeight, em)
|
|
.boxSizing("border-box")
|
|
.pointerEvents("none")
|
|
}
|
|
|
|
spanningEvent(event, startIndex, endIndex, rowHeight, gap, clippedLeft, clippedRight) {
|
|
const totalDays = this.groupedDays.length;
|
|
const spanCount = endIndex - startIndex + 1;
|
|
const leftPct = (startIndex / totalDays) * 100;
|
|
const widthPct = (spanCount / totalDays) * 100;
|
|
const color = calendarUtil.getCalendarColor(this.calendars, event.calendars.find(id => this.calendars.some(c => c.id === id)) ?? event.calendars[0]);
|
|
|
|
const id = event.id ?? event.title;
|
|
const slot = this.groupedDays[startIndex].allDay.findIndex(e => (e.id ?? e.title) === id);
|
|
const topEm = slot * (rowHeight + gap);
|
|
|
|
const borderLeft = clippedLeft ? 0 : 0.25;
|
|
const borderRight = clippedRight ? 0 : 0.25;
|
|
const leftPad = clippedLeft ? 0 : 1.25;
|
|
const rightPad = clippedRight ? 0 : 1.25;
|
|
|
|
HStack(() => {
|
|
p(event.title)
|
|
.margin(0)
|
|
.fontSize(0.72, em)
|
|
.fontWeight("600")
|
|
.color("white")
|
|
.whiteSpace("nowrap")
|
|
.overflow("hidden")
|
|
})
|
|
.position("absolute")
|
|
.top(topEm, em)
|
|
.left(leftPct + leftPad, pct)
|
|
.width(widthPct - leftPad - rightPad, pct)
|
|
.height(rowHeight, em)
|
|
.padding(0.35, em)
|
|
.background(color)
|
|
.borderTopLeftRadius(`${borderLeft}em`)
|
|
.borderBottomLeftRadius(`${borderLeft}em`)
|
|
.borderTopRightRadius(`${borderRight}em`)
|
|
.borderBottomRightRadius(`${borderRight}em`)
|
|
.alignItems("center")
|
|
.boxSizing("border-box")
|
|
.overflow("hidden")
|
|
.pointerEvents("auto")
|
|
.cursor("pointer")
|
|
.onTap(() => $("bottomsheet-").showEvent(event))
|
|
}
|
|
|
|
collectAllDayEvents() {
|
|
const seen = new Map();
|
|
// Key by id + time_start date: events spanning multiple days share the same time_start
|
|
// so they merge into one bar; different occurrences of the same recurring template
|
|
// have different time_start dates and render as separate bars.
|
|
const eventKey = (event) => {
|
|
const d = event.time_start instanceof Date ? event.time_start : new Date(event.time_start);
|
|
return `${event.id ?? event.title}_${calendarUtil.toDateInput(d)}`;
|
|
};
|
|
|
|
this.groupedDays.forEach((group, index) => {
|
|
group.allDay.forEach(event => {
|
|
const key = eventKey(event);
|
|
if (!seen.has(key)) {
|
|
seen.set(key, { event, startIndex: index, endIndex: index });
|
|
} else {
|
|
seen.get(key).endIndex = index;
|
|
}
|
|
});
|
|
});
|
|
|
|
const lastIndex = this.groupedDays.length - 1;
|
|
return Array.from(seen.values()).map(entry => ({
|
|
...entry,
|
|
clippedLeft: entry.startIndex === 0 && calendarUtil.startOfDay(entry.event.time_start) < calendarUtil.startOfDay(this.groupedDays[0].day),
|
|
clippedRight: entry.endIndex === lastIndex && calendarUtil.startOfDay(entry.event.time_end) > calendarUtil.startOfDay(this.groupedDays[lastIndex].day)
|
|
}));
|
|
}
|
|
}
|
|
|
|
register(WeekHeaderRow) |