init
This commit is contained in:
160
calendar/Week/WeekHeaderRow.js
Normal file
160
calendar/Week/WeekHeaderRow.js
Normal file
@@ -0,0 +1,160 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user