Merge branch 'dev'
CI/CD / deploy (push) Failing after 2m54s Details

This commit is contained in:
Jeff Emmett 2026-04-09 12:44:08 -04:00
commit b89e247e6c
1 changed files with 76 additions and 9 deletions

View File

@ -841,6 +841,75 @@ class FolkCalendarView extends HTMLElement {
return Array.from(labels);
}
/**
* Render day cell content individual event labels when few events,
* spatial summary chips when events start to overlap.
*/
private renderDayCellContent(cellEvents: any[]): string {
// Few events: show individual labels (original behavior)
if (cellEvents.length <= 2) {
return cellEvents.map(e => {
const evColor = e.source_color || "#6366f1";
const es = this.getEventStyles(e);
const city = this.getSpatialLabel(e.location_breadcrumb, 5);
const cityHtml = city ? `<span class="ev-loc">${this.esc(city)}</span>` : "";
const virtualHtml = e.is_virtual ? `<span class="ev-virtual" title="Virtual">\u{1F4BB}</span>` : "";
const likelihoodHtml = es.isTentative ? `<span class="ev-likelihood">${es.likelihoodLabel}</span>` : "";
return `<div class="ev-label" style="border-left:2px ${es.borderStyle} ${evColor};background:${es.bgColor};opacity:${es.opacity}" data-event-id="${e.id}">${e.rToolSource === "rSchedule" ? '<span class="ev-bell">&#128276;</span>' : ""}${virtualHtml}<span class="ev-time">${this.formatTime(e.start_time)}</span>${this.esc(e.title)}${likelihoodHtml}${cityHtml}</div>`;
}).join("");
}
// Many events: semantic zoom — show spatial summary chips instead
// Group by location: count events per city, then decide label granularity
const located = cellEvents.filter(e => e.location_breadcrumb);
const virtualCount = cellEvents.filter(e => e.is_virtual).length;
// Try city-level grouping first
const cityGroups = new Map<string, number>();
for (const e of located) {
const city = this.getSpatialLabel(e.location_breadcrumb, 5);
if (city) cityGroups.set(city, (cityGroups.get(city) || 0) + 1);
}
// If too many cities (5+), zoom out to country level
let groups: Map<string, number>;
let levelLabel: string;
if (cityGroups.size > 4) {
groups = new Map<string, number>();
for (const e of located) {
const country = this.getSpatialLabel(e.location_breadcrumb, 3);
if (country) groups.set(country, (groups.get(country) || 0) + 1);
}
levelLabel = "country";
// If still too many countries, zoom to continent
if (groups.size > 4) {
groups = new Map<string, number>();
for (const e of located) {
const continent = this.getSpatialLabel(e.location_breadcrumb, 1);
if (continent) groups.set(continent, (groups.get(continent) || 0) + 1);
}
levelLabel = "continent";
}
} else {
groups = cityGroups;
levelLabel = "city";
}
// Render spatial chips
let html = "";
const sorted = Array.from(groups.entries()).sort((a, b) => b[1] - a[1]);
for (const [label, count] of sorted.slice(0, 3)) {
html += `<div class="ev-spatial-chip" title="${count} event${count > 1 ? "s" : ""} in ${label}"><span class="ev-spatial-name">${this.esc(label)}</span><span class="ev-spatial-count">${count}</span></div>`;
}
if (sorted.length > 3) {
html += `<div class="ev-spatial-chip ev-spatial-more">+${sorted.length - 3}</div>`;
}
if (virtualCount > 0) {
html += `<div class="ev-spatial-chip ev-spatial-virtual" title="${virtualCount} virtual">\u{1F4BB} ${virtualCount}</div>`;
}
return html;
}
private toggleSource(name: string) {
if (this.filteredSources.has(name)) { this.filteredSources.delete(name); }
else { this.filteredSources.add(name); }
@ -1222,15 +1291,7 @@ class FolkCalendarView extends HTMLElement {
${dayReminders.slice(0, 3).map(r => `<span class="dot dot--reminder" style="background:${r.sourceColor || "#f59e0b"}" title="${this.esc(r.title)}"></span>`).join("")}
${cellEvents.length > 5 ? `<span style="font-size:8px;color:var(--rs-text-muted)">+${cellEvents.length - 5}</span>` : ""}
</div>
${cellEvents.slice(0, 2).map(e => {
const evColor = e.source_color || "#6366f1";
const es = this.getEventStyles(e);
const city = this.getSpatialLabel(e.location_breadcrumb, 5);
const cityHtml = city ? `<span class="ev-loc">${this.esc(city)}</span>` : "";
const virtualHtml = e.is_virtual ? `<span class="ev-virtual" title="Virtual">\u{1F4BB}</span>` : "";
const likelihoodHtml = es.isTentative ? `<span class="ev-likelihood">${es.likelihoodLabel}</span>` : "";
return `<div class="ev-label" style="border-left:2px ${es.borderStyle} ${evColor};background:${es.bgColor};opacity:${es.opacity}" data-event-id="${e.id}">${e.rToolSource === "rSchedule" ? '<span class="ev-bell">&#128276;</span>' : ""}${virtualHtml}<span class="ev-time">${this.formatTime(e.start_time)}</span>${this.esc(e.title)}${likelihoodHtml}${cityHtml}</div>`;
}).join("")}
${this.renderDayCellContent(cellEvents)}
` : ""}
</div>`;
@ -2960,6 +3021,11 @@ class FolkCalendarView extends HTMLElement {
.ev-time { color: var(--rs-text-muted); font-size: 8px; margin-right: 2px; }
.ev-bell { margin-right: 2px; font-size: 8px; }
.ev-loc { color: var(--rs-text-muted); font-size: 7px; margin-left: 3px; }
.ev-spatial-chip { display: flex; align-items: center; gap: 2px; font-size: 8px; color: var(--rs-text-secondary); background: var(--rs-bg-hover); border: 1px solid var(--rs-border); border-radius: 6px; padding: 1px 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.4; }
.ev-spatial-name { overflow: hidden; text-overflow: ellipsis; }
.ev-spatial-count { font-weight: 700; color: var(--rs-text-muted); font-size: 7px; flex-shrink: 0; }
.ev-spatial-more { color: var(--rs-text-muted); font-size: 7px; justify-content: center; }
.ev-spatial-virtual { color: var(--rs-text-muted); font-size: 7px; }
.ev-virtual { font-size: 8px; margin-right: 2px; vertical-align: middle; }
.ev-likelihood { font-size: 8px; color: var(--rs-warning); margin-left: 3px; }
.dd-likelihood { font-size: 9px; color: var(--rs-warning); margin-left: 6px; }
@ -3250,6 +3316,7 @@ class FolkCalendarView extends HTMLElement {
.day { min-height: 0; padding: 4px; }
.day-num { font-size: 11px; }
.ev-label { display: none; }
.ev-spatial-chip { display: none; }
/* Dots → thin colored bars on mobile */
.dots { flex-direction: column; gap: 1px; margin-top: 2px; }
.dot { width: 100%; height: 2px; border-radius: 1px; margin: 0; }