219 lines
7.2 KiB
JavaScript
219 lines
7.2 KiB
JavaScript
import calendarUtil from "../calendarUtil.js";
|
|
|
|
css(`
|
|
monthgrid- {
|
|
scrollbar-width: none;
|
|
-ms-overflow-style: none;
|
|
}
|
|
monthgrid-::-webkit-scrollbar {
|
|
display: none;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
`)
|
|
|
|
class MonthGrid extends Shadow {
|
|
constructor(weeks, calendars, onDayTap = null) {
|
|
super()
|
|
this.weeks = weeks;
|
|
this.calendars = calendars;
|
|
this.onDayTap = onDayTap;
|
|
this.maxVisible = 3; // caps both rendering and row height calculation
|
|
|
|
// em
|
|
this.dateFontSize = 1.2;
|
|
this.dateLineHeight = 1;
|
|
this.paddingTop = 1.5;
|
|
this.paddingBottom = 0.55;
|
|
|
|
// em
|
|
this.pillHeight = 1.15;
|
|
this.pillGap = 0.2;
|
|
this.rowBottomPadding = 0.2;
|
|
|
|
this.headerHeight = this.paddingTop + (this.dateFontSize * this.dateLineHeight) + this.paddingBottom;
|
|
this.rowHeight = this.headerHeight + this.maxVisible * (this.pillHeight + this.pillGap) + this.rowBottomPadding;
|
|
}
|
|
|
|
render() {
|
|
VStack(() => {
|
|
this.weeks.forEach((week, wi) => {
|
|
this.renderWeekRow(week, wi === this.weeks.length - 1);
|
|
})
|
|
})
|
|
.width(100, pct)
|
|
.height(this.rowHeight * this.weeks.length, em)
|
|
.flexShrink(0)
|
|
.flex(1)
|
|
.overflowY("scroll")
|
|
}
|
|
|
|
renderWeekRow(week, isLastWeek) {
|
|
ZStack(() => {
|
|
this.renderCellLayer(week, isLastWeek)
|
|
this.renderPillLayer(week)
|
|
})
|
|
.position("relative")
|
|
.width(100, pct)
|
|
.height(this.rowHeight + 0.5, em)
|
|
.flexShrink(0)
|
|
.alignItems("stretch")
|
|
}
|
|
|
|
renderCellLayer(week, isLastWeek) {
|
|
HStack(() => {
|
|
week.days.forEach((dayData, di) => {
|
|
this.renderDayCell(dayData, di === 6, isLastWeek)
|
|
})
|
|
})
|
|
.width(100, pct)
|
|
.alignItems("stretch")
|
|
.height(100, pct)
|
|
}
|
|
|
|
renderDayCell(dayData, isLast, isLastWeek) {
|
|
const { day, isCurrentMonth } = dayData;
|
|
const today = calendarUtil.isToday(day);
|
|
|
|
VStack(() => {
|
|
HStack(() => {
|
|
p(day.getDate())
|
|
.margin(0)
|
|
.fontSize(this.dateFontSize, em)
|
|
.fontWeight(today ? "700" : "500")
|
|
.color(today ? "white" : "var(--headertext)")
|
|
.background(today ? "var(--quillred)" : "transparent")
|
|
.paddingHorizontal(0.2, em)
|
|
.paddingVertical(0.125, em)
|
|
.borderRadius(25, pct)
|
|
.textAlign("center")
|
|
.opacity(isCurrentMonth ? 1 : 0)
|
|
.lineHeight(`${this.dateLineHeight}`)
|
|
|
|
})
|
|
.position("relative")
|
|
.justifyContent("center")
|
|
.paddingTop(this.paddingTop, em)
|
|
.paddingHorizontal(0.4, em)
|
|
.paddingBottom(this.paddingBottom, em)
|
|
})
|
|
.flex(1)
|
|
.width(0, px)
|
|
.minWidth(0)
|
|
.height(100, pct)
|
|
.borderBottom(isLastWeek ? "1px solid transparent" : "0.5px solid var(--divider)")
|
|
.boxSizing("border-box")
|
|
.overflow("hidden")
|
|
.alignItems("stretch")
|
|
.cursor("pointer")
|
|
.onTap(() => { this.onDayTap(day) })
|
|
}
|
|
|
|
renderPillLayer(week) {
|
|
ZStack(() => {
|
|
const maxSlots = Math.max(0, ...week.slotMap.map(s => s.length));
|
|
|
|
for (let row = 0; row < Math.min(maxSlots, this.maxVisible); row++) {
|
|
this.collectSpans(week, row).forEach(span => this.renderPill(span, week, row))
|
|
}
|
|
|
|
// Overflow labels
|
|
week.days.forEach((dayData, col) => {
|
|
if (!dayData.isCurrentMonth) return;
|
|
const overflow = Math.max(0, dayData.events.length - this.maxVisible);
|
|
if (overflow === 0) return;
|
|
|
|
const leftPct = (col / 7) * 100;
|
|
|
|
p(`+${overflow} more`)
|
|
.margin(0)
|
|
.fontSize(0.62, em)
|
|
.fontWeight("600")
|
|
.color("var(--headertext)")
|
|
.opacity(0.5)
|
|
.position("absolute")
|
|
.bottom(this.rowBottomPadding, em)
|
|
.left(leftPct, pct)
|
|
.width(100 / 7, pct)
|
|
.paddingHorizontal(0.4, em)
|
|
.zIndex(2)
|
|
})
|
|
})
|
|
.position("absolute")
|
|
.top(0).left(0).right(0).bottom(0)
|
|
.pointerEvents("none")
|
|
}
|
|
|
|
renderPill({ startCol, endCol, event }, week, row) {
|
|
const color = calendarUtil.getCalendarColor(this.calendars, event.calendars.find(id => this.calendars.some(c => c.id === id)) ?? event.calendars[0]);
|
|
const leftPct = (startCol / 7) * 100;
|
|
const widthPct = ((endCol - startCol + 1) / 7) * 100;
|
|
const topEm = this.headerHeight + row * (this.pillHeight + this.pillGap);
|
|
|
|
const clippedLeft = startCol === 0 && calendarUtil.startOfDay(event.time_start) < calendarUtil.startOfDay(week.days[0].day);
|
|
const clippedRight = endCol === 6 && calendarUtil.endOfDay(event.time_end) > calendarUtil.endOfDay(week.days[6].day);
|
|
|
|
const colWidthPct = 100 / 7;
|
|
const leftInsetPct = clippedLeft ? 0 : 0.025 * colWidthPct;
|
|
const rightInsetPct = clippedRight ? 0 : 0.025 * colWidthPct;
|
|
|
|
const brLeft = clippedLeft ? 0 : 5;
|
|
const brRight = clippedRight ? 0 : 5;
|
|
|
|
HStack(() => {
|
|
p(event.title || "Untitled")
|
|
.margin(0)
|
|
.fontSize(0.9, em)
|
|
.fontWeight("600")
|
|
.color("white")
|
|
.whiteSpace("nowrap")
|
|
.overflow("hidden")
|
|
})
|
|
.position("absolute")
|
|
.top(topEm, em)
|
|
.left(leftPct + leftInsetPct, pct)
|
|
.width(widthPct - leftInsetPct - rightInsetPct, pct)
|
|
.height(this.pillHeight, em)
|
|
.paddingHorizontal(0.4, em)
|
|
.background(color)
|
|
.borderTopLeftRadius(`${brLeft}px`)
|
|
.borderBottomLeftRadius(`${brLeft}px`)
|
|
.borderTopRightRadius(`${brRight}px`)
|
|
.borderBottomRightRadius(`${brRight}px`)
|
|
.alignItems("center")
|
|
.overflow("hidden")
|
|
.boxSizing("border-box")
|
|
.zIndex(1)
|
|
.pointerEvents("auto")
|
|
.cursor("pointer")
|
|
.onTap(() => $("bottomsheet-").showEvent(event))
|
|
}
|
|
|
|
collectSpans(week, row) {
|
|
const spans = [];
|
|
let current = null;
|
|
|
|
for (let col = 0; col < 7; col++) {
|
|
const slot = (week.slotMap[col] || [])[row] ?? null;
|
|
|
|
if (slot && current && slot.event === current.event) {
|
|
current.endCol = col;
|
|
} else {
|
|
if (current) spans.push(current);
|
|
current = slot ? { startCol: col, endCol: col, event: slot.event } : null;
|
|
}
|
|
}
|
|
|
|
if (current) spans.push(current);
|
|
|
|
// Only render spans that touch at least one current-month day
|
|
return spans.filter(span => {
|
|
for (let col = span.startCol; col <= span.endCol; col++) {
|
|
if (week.days[col]?.isCurrentMonth) return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
register(MonthGrid) |