perf/fix: Kalender-Toggle ohne Ladescreen + Mehrfach-Tint als Verlauf
Ausblenden: Events werden sofort client-seitig aus dem Cache gefiltert (calendar_id-Match), kein Netzwerkaufruf für die Ansicht nötig. Einblenden: fetchAndRender(force, silent=true) überspringt showLoading(), die aktuelle Ansicht bleibt sichtbar und wird nach dem Fetch aktualisiert. Mehrere mehrtägige Events am selben Tag erzeugen jetzt einen vertikalen Farbverlauf (linear-gradient) statt gestapelter Ebenen, bei denen nur die letzte Farbe sichtbar war.
This commit is contained in:
@@ -141,7 +141,7 @@ function prefetchIfNeeded(viewStart, viewEnd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Data fetching ─────────────────────────────────────────
|
// ── Data fetching ─────────────────────────────────────────
|
||||||
async function fetchAndRender(force = false) {
|
async function fetchAndRender(force = false, silent = false) {
|
||||||
const { start, end } = getViewRange();
|
const { start, end } = getViewRange();
|
||||||
|
|
||||||
// Cache hit: requested range is fully within what we already have
|
// Cache hit: requested range is fully within what we already have
|
||||||
@@ -159,7 +159,7 @@ async function fetchAndRender(force = false) {
|
|||||||
const fetchStart = new Date(start.getTime() - CACHE_BUF);
|
const fetchStart = new Date(start.getTime() - CACHE_BUF);
|
||||||
const fetchEnd = new Date(end.getTime() + CACHE_BUF);
|
const fetchEnd = new Date(end.getTime() + CACHE_BUF);
|
||||||
|
|
||||||
showLoading();
|
if (!silent) showLoading();
|
||||||
try {
|
try {
|
||||||
const resp = await api.get(`/caldav/events?start=${fetchStart.toISOString()}&end=${fetchEnd.toISOString()}`);
|
const resp = await api.get(`/caldav/events?start=${fetchStart.toISOString()}&end=${fetchEnd.toISOString()}`);
|
||||||
const events = resp.events || resp;
|
const events = resp.events || resp;
|
||||||
@@ -460,6 +460,8 @@ function renderCalendarList() {
|
|||||||
container.querySelectorAll('input[type=checkbox]').forEach(cb => {
|
container.querySelectorAll('input[type=checkbox]').forEach(cb => {
|
||||||
cb.addEventListener('change', async () => {
|
cb.addEventListener('change', async () => {
|
||||||
const source = cb.dataset.source;
|
const source = cb.dataset.source;
|
||||||
|
let cacheCalId = null; // calendar_id value used in cached events
|
||||||
|
|
||||||
if (source === 'caldav') {
|
if (source === 'caldav') {
|
||||||
const calId = parseInt(cb.dataset.calId);
|
const calId = parseInt(cb.dataset.calId);
|
||||||
await api.put(`/caldav/calendars/${calId}`, { enabled: cb.checked });
|
await api.put(`/caldav/calendars/${calId}`, { enabled: cb.checked });
|
||||||
@@ -468,16 +470,19 @@ function renderCalendarList() {
|
|||||||
if (cal.id === calId) cal.enabled = cb.checked;
|
if (cal.id === calId) cal.enabled = cb.checked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cacheCalId = calId; // numeric integer in cached events
|
||||||
} else if (source === 'local') {
|
} else if (source === 'local') {
|
||||||
const calId = parseInt(cb.dataset.calId);
|
const calId = parseInt(cb.dataset.calId);
|
||||||
await api.put(`/local/calendars/${calId}`, { enabled: cb.checked });
|
await api.put(`/local/calendars/${calId}`, { enabled: cb.checked });
|
||||||
const cal = state.localCalendars.find(c => c.id === calId);
|
const cal = state.localCalendars.find(c => c.id === calId);
|
||||||
if (cal) cal.enabled = cb.checked;
|
if (cal) cal.enabled = cb.checked;
|
||||||
|
cacheCalId = `local-${calId}`;
|
||||||
} else if (source === 'ical') {
|
} else if (source === 'ical') {
|
||||||
const subId = parseInt(cb.dataset.subId);
|
const subId = parseInt(cb.dataset.subId);
|
||||||
await api.put(`/ical/subscriptions/${subId}`, { enabled: cb.checked });
|
await api.put(`/ical/subscriptions/${subId}`, { enabled: cb.checked });
|
||||||
const sub = state.icalSubscriptions.find(s => s.id === subId);
|
const sub = state.icalSubscriptions.find(s => s.id === subId);
|
||||||
if (sub) sub.enabled = cb.checked;
|
if (sub) sub.enabled = cb.checked;
|
||||||
|
cacheCalId = `ical-${subId}`;
|
||||||
} else if (source === 'google') {
|
} else if (source === 'google') {
|
||||||
const calId = parseInt(cb.dataset.calId);
|
const calId = parseInt(cb.dataset.calId);
|
||||||
await api.put(`/google/calendars/${calId}`, { enabled: cb.checked });
|
await api.put(`/google/calendars/${calId}`, { enabled: cb.checked });
|
||||||
@@ -485,8 +490,20 @@ function renderCalendarList() {
|
|||||||
const cal = acc.calendars.find(c => c.id === calId);
|
const cal = acc.calendars.find(c => c.id === calId);
|
||||||
if (cal) cal.enabled = cb.checked;
|
if (cal) cal.enabled = cb.checked;
|
||||||
}
|
}
|
||||||
|
cacheCalId = `google-${calId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cb.checked && cacheCalId !== null) {
|
||||||
|
// Hiding: filter from cache instantly, no network call needed
|
||||||
|
eventCache.events = eventCache.events.filter(ev => ev.calendar_id !== cacheCalId);
|
||||||
|
state.events = eventCache.events;
|
||||||
|
renderView();
|
||||||
|
updateTitle();
|
||||||
|
renderMiniCal();
|
||||||
|
} else {
|
||||||
|
// Showing: refetch silently — view stays visible, updates when done
|
||||||
|
fetchAndRender(true, true);
|
||||||
}
|
}
|
||||||
fetchAndRender(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -131,10 +131,24 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC
|
|||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
// Background tint for days covered by multi-day events (timed or all-day)
|
// Background tint for days covered by multi-day events (timed or all-day)
|
||||||
const tintHtml = tintEvs.filter(ev => spansDay(ev, day)).map(ev => {
|
const dayTintEvs = tintEvs.filter(ev => spansDay(ev, day));
|
||||||
const color = ev.color || ev.calendarColor || '#4285f4';
|
const tintHtml = (() => {
|
||||||
return `<div class="col-span-tint" style="background:${color}26"></div>`;
|
if (!dayTintEvs.length) return '';
|
||||||
}).join('');
|
const colors = dayTintEvs.map(ev => ev.color || ev.calendarColor || '#4285f4');
|
||||||
|
let bg;
|
||||||
|
if (colors.length === 1) {
|
||||||
|
bg = colors[0] + '26';
|
||||||
|
} else {
|
||||||
|
// Vertical gradient bands for multiple overlapping multi-day events
|
||||||
|
const stops = colors.flatMap((c, i) => {
|
||||||
|
const p1 = ((i / colors.length) * 100).toFixed(1);
|
||||||
|
const p2 = (((i + 1) / colors.length) * 100).toFixed(1);
|
||||||
|
return [`${c}26 ${p1}%`, `${c}26 ${p2}%`];
|
||||||
|
}).join(',');
|
||||||
|
bg = `linear-gradient(to bottom,${stops})`;
|
||||||
|
}
|
||||||
|
return `<div class="col-span-tint" style="background:${bg}"></div>`;
|
||||||
|
})();
|
||||||
|
|
||||||
return `<div class="week-day-col" data-date="${key}" style="height:${hourH * 24}px">
|
return `<div class="week-day-col" data-date="${key}" style="height:${hourH * 24}px">
|
||||||
${tintHtml}
|
${tintHtml}
|
||||||
|
|||||||
Reference in New Issue
Block a user