From faada7359e16ba5f79483c0a1e358afa9eada971 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Tue, 7 Apr 2026 22:05:03 +0200 Subject: [PATCH] =?UTF-8?q?perf:=20Event=20cache=20mit=20=C2=B18-Wochen-Pu?= =?UTF-8?q?ffer=20f=C3=BCr=20schnelle=20Navigation=20Beim=20ersten=20Laden?= =?UTF-8?q?=20wird=20ein=20Fenster=20von=20=C2=B18=20Wochen=20um=20die=20a?= =?UTF-8?q?ktuelle=20Ansicht=20geholt.=20Wochenweise=20Navigation=20trifft?= =?UTF-8?q?=20danach=20den=20Cache=20sofort=20(kein=20Spinner,=20kein=20Ne?= =?UTF-8?q?tzwerk).=20Nach=20echten=20Daten=C3=A4nderungen=20(Event=20spei?= =?UTF-8?q?chern/l=C3=B6schen,=20Sync,=20Konto-=C3=84nderungen)=20wird=20d?= =?UTF-8?q?er=20Cache=20invalidiert=20und=20neu=20geladen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/js/calendar.js | 52 +++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/frontend/js/calendar.js b/frontend/js/calendar.js index 6658980..af88aaa 100644 --- a/frontend/js/calendar.js +++ b/frontend/js/calendar.js @@ -72,12 +72,40 @@ export async function initCalendar() { bindProfileModal(); } +// ── Event cache ─────────────────────────────────────────── +const eventCache = { start: null, end: null, events: [] }; + +function invalidateCache() { + eventCache.start = null; + eventCache.end = null; + eventCache.events = []; +} + // ── Data fetching ───────────────────────────────────────── -async function fetchAndRender() { +async function fetchAndRender(force = false) { const { start, end } = getViewRange(); + + // Cache hit: requested range is fully within what we already have + if (!force && eventCache.start && eventCache.end && + start >= eventCache.start && end <= eventCache.end) { + state.events = eventCache.events; + renderView(); + updateTitle(); + renderMiniCal(); + return; + } + + // Cache miss: fetch a wider window (±8 weeks) so subsequent navigation is instant + const BUF = 56 * 86400000; // 8 weeks in ms + const fetchStart = new Date(start.getTime() - BUF); + const fetchEnd = new Date(end.getTime() + BUF); + showLoading(); try { - const events = await api.get(`/caldav/events?start=${start.toISOString()}&end=${end.toISOString()}`); + const events = await api.get(`/caldav/events?start=${fetchStart.toISOString()}&end=${fetchEnd.toISOString()}`); + eventCache.start = fetchStart; + eventCache.end = fetchEnd; + eventCache.events = events; state.events = events; } catch (e) { showToast(t('error_load_events') + ': ' + e.message, true); @@ -676,7 +704,7 @@ function showEventPopup(ev, anchor) { await api.delete(`/caldav/events/${encodeURIComponent(ev.id)}?event_url=${encodeURIComponent(ev.url)}`); } showToast(t('event_deleted')); - fetchAndRender(); + fetchAndRender(true); } catch (e) { showToast(e.message, true); } }; document.getElementById('popup-close').onclick = () => popup.classList.add('hidden'); @@ -916,7 +944,7 @@ function bindEventModal() { showToast(t('event_created')); } closeModal('modal-event'); - fetchAndRender(); + fetchAndRender(true); } catch (e) { showToast(e.message, true); } @@ -940,7 +968,7 @@ function bindEventModal() { } showToast(t('event_deleted')); closeModal('modal-event'); - fetchAndRender(); + fetchAndRender(true); } catch (e) { showToast(e.message, true); } }; } @@ -993,7 +1021,7 @@ function bindAccountModal() { renderCalendarList(); closeModal('modal-account'); showToast(t("account_added", {name})); - fetchAndRender(); + fetchAndRender(true); } catch (e) { errEl.textContent = e.message; errEl.classList.remove('hidden'); @@ -1081,7 +1109,7 @@ function bindICalSubModal() { renderCalendarList(); closeModal('modal-ical-sub'); showToast(t("ical_subscribed", {name})); - fetchAndRender(); + fetchAndRender(true); } catch (e) { errEl.textContent = e.message; errEl.classList.remove('hidden'); @@ -1170,7 +1198,7 @@ function renderGoogleAccounts() { if (idx !== -1) state.googleAccounts[idx] = updated; renderGoogleAccounts(); renderCalendarList(); - fetchAndRender(); + fetchAndRender(true); showToast(t('google_synced')); } catch (e) { showToast(e.message, true); } }); @@ -1183,7 +1211,7 @@ function renderGoogleAccounts() { state.googleAccounts = state.googleAccounts.filter(a => a.id !== parseInt(btn.dataset.disconnectAcc)); renderGoogleAccounts(); renderCalendarList(); - fetchAndRender(); + fetchAndRender(true); showToast(t('google_disconnected')); } catch (e) { showToast(e.message, true); } }); @@ -1214,7 +1242,7 @@ function renderAllAccounts() { btn.disabled = true; btn.textContent = '…'; try { await api.post(`/caldav/accounts/${btn.dataset.caldavSync}/sync`); - renderCalendarList(); fetchAndRender(); + renderCalendarList(); fetchAndRender(true); showToast(t('google_synced')); } catch (e) { showToast(e.message, true); } finally { btn.disabled = false; btn.textContent = t('sync'); } @@ -1226,7 +1254,7 @@ function renderAllAccounts() { try { await api.delete(`/caldav/accounts/${btn.dataset.caldavDisconnect}`); state.accounts = state.accounts.filter(a => a.id !== parseInt(btn.dataset.caldavDisconnect)); - renderAllAccounts(); renderCalendarList(); fetchAndRender(); + renderAllAccounts(); renderCalendarList(); fetchAndRender(true); showToast(t('caldav_disconnected')); } catch (e) { showToast(e.message, true); } }); @@ -1274,7 +1302,7 @@ function renderAllAccounts() { try { await api.delete(`/ical/subscriptions/${btn.dataset.icalDelete}`); state.icalSubscriptions = state.icalSubscriptions.filter(s => s.id !== parseInt(btn.dataset.icalDelete)); - renderAllAccounts(); renderCalendarList(); fetchAndRender(); + renderAllAccounts(); renderCalendarList(); fetchAndRender(true); } catch (e) { showToast(e.message, true); } }); });