perf: Event cache mit ±8-Wochen-Puffer für schnelle Navigation
Beim ersten Laden wird ein Fenster von ±8 Wochen um die aktuelle Ansicht geholt. Wochenweise Navigation trifft danach den Cache sofort (kein Spinner, kein Netzwerk). Nach echten Datenänderungen (Event speichern/löschen, Sync, Konto-Änderungen) wird der Cache invalidiert und neu geladen.
This commit is contained in:
@@ -72,12 +72,40 @@ export async function initCalendar() {
|
|||||||
bindProfileModal();
|
bindProfileModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Event cache ───────────────────────────────────────────
|
||||||
|
const eventCache = { start: null, end: null, events: [] };
|
||||||
|
|
||||||
|
function invalidateCache() {
|
||||||
|
eventCache.start = null;
|
||||||
|
eventCache.end = null;
|
||||||
|
eventCache.events = [];
|
||||||
|
}
|
||||||
|
|
||||||
// ── Data fetching ─────────────────────────────────────────
|
// ── Data fetching ─────────────────────────────────────────
|
||||||
async function fetchAndRender() {
|
async function fetchAndRender(force = false) {
|
||||||
const { start, end } = getViewRange();
|
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();
|
showLoading();
|
||||||
try {
|
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;
|
state.events = events;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(t('error_load_events') + ': ' + e.message, true);
|
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)}`);
|
await api.delete(`/caldav/events/${encodeURIComponent(ev.id)}?event_url=${encodeURIComponent(ev.url)}`);
|
||||||
}
|
}
|
||||||
showToast(t('event_deleted'));
|
showToast(t('event_deleted'));
|
||||||
fetchAndRender();
|
fetchAndRender(true);
|
||||||
} catch (e) { showToast(e.message, true); }
|
} catch (e) { showToast(e.message, true); }
|
||||||
};
|
};
|
||||||
document.getElementById('popup-close').onclick = () => popup.classList.add('hidden');
|
document.getElementById('popup-close').onclick = () => popup.classList.add('hidden');
|
||||||
@@ -916,7 +944,7 @@ function bindEventModal() {
|
|||||||
showToast(t('event_created'));
|
showToast(t('event_created'));
|
||||||
}
|
}
|
||||||
closeModal('modal-event');
|
closeModal('modal-event');
|
||||||
fetchAndRender();
|
fetchAndRender(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(e.message, true);
|
showToast(e.message, true);
|
||||||
}
|
}
|
||||||
@@ -940,7 +968,7 @@ function bindEventModal() {
|
|||||||
}
|
}
|
||||||
showToast(t('event_deleted'));
|
showToast(t('event_deleted'));
|
||||||
closeModal('modal-event');
|
closeModal('modal-event');
|
||||||
fetchAndRender();
|
fetchAndRender(true);
|
||||||
} catch (e) { showToast(e.message, true); }
|
} catch (e) { showToast(e.message, true); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -993,7 +1021,7 @@ function bindAccountModal() {
|
|||||||
renderCalendarList();
|
renderCalendarList();
|
||||||
closeModal('modal-account');
|
closeModal('modal-account');
|
||||||
showToast(t("account_added", {name}));
|
showToast(t("account_added", {name}));
|
||||||
fetchAndRender();
|
fetchAndRender(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errEl.textContent = e.message;
|
errEl.textContent = e.message;
|
||||||
errEl.classList.remove('hidden');
|
errEl.classList.remove('hidden');
|
||||||
@@ -1081,7 +1109,7 @@ function bindICalSubModal() {
|
|||||||
renderCalendarList();
|
renderCalendarList();
|
||||||
closeModal('modal-ical-sub');
|
closeModal('modal-ical-sub');
|
||||||
showToast(t("ical_subscribed", {name}));
|
showToast(t("ical_subscribed", {name}));
|
||||||
fetchAndRender();
|
fetchAndRender(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errEl.textContent = e.message;
|
errEl.textContent = e.message;
|
||||||
errEl.classList.remove('hidden');
|
errEl.classList.remove('hidden');
|
||||||
@@ -1170,7 +1198,7 @@ function renderGoogleAccounts() {
|
|||||||
if (idx !== -1) state.googleAccounts[idx] = updated;
|
if (idx !== -1) state.googleAccounts[idx] = updated;
|
||||||
renderGoogleAccounts();
|
renderGoogleAccounts();
|
||||||
renderCalendarList();
|
renderCalendarList();
|
||||||
fetchAndRender();
|
fetchAndRender(true);
|
||||||
showToast(t('google_synced'));
|
showToast(t('google_synced'));
|
||||||
} catch (e) { showToast(e.message, true); }
|
} catch (e) { showToast(e.message, true); }
|
||||||
});
|
});
|
||||||
@@ -1183,7 +1211,7 @@ function renderGoogleAccounts() {
|
|||||||
state.googleAccounts = state.googleAccounts.filter(a => a.id !== parseInt(btn.dataset.disconnectAcc));
|
state.googleAccounts = state.googleAccounts.filter(a => a.id !== parseInt(btn.dataset.disconnectAcc));
|
||||||
renderGoogleAccounts();
|
renderGoogleAccounts();
|
||||||
renderCalendarList();
|
renderCalendarList();
|
||||||
fetchAndRender();
|
fetchAndRender(true);
|
||||||
showToast(t('google_disconnected'));
|
showToast(t('google_disconnected'));
|
||||||
} catch (e) { showToast(e.message, true); }
|
} catch (e) { showToast(e.message, true); }
|
||||||
});
|
});
|
||||||
@@ -1214,7 +1242,7 @@ function renderAllAccounts() {
|
|||||||
btn.disabled = true; btn.textContent = '…';
|
btn.disabled = true; btn.textContent = '…';
|
||||||
try {
|
try {
|
||||||
await api.post(`/caldav/accounts/${btn.dataset.caldavSync}/sync`);
|
await api.post(`/caldav/accounts/${btn.dataset.caldavSync}/sync`);
|
||||||
renderCalendarList(); fetchAndRender();
|
renderCalendarList(); fetchAndRender(true);
|
||||||
showToast(t('google_synced'));
|
showToast(t('google_synced'));
|
||||||
} catch (e) { showToast(e.message, true); }
|
} catch (e) { showToast(e.message, true); }
|
||||||
finally { btn.disabled = false; btn.textContent = t('sync'); }
|
finally { btn.disabled = false; btn.textContent = t('sync'); }
|
||||||
@@ -1226,7 +1254,7 @@ function renderAllAccounts() {
|
|||||||
try {
|
try {
|
||||||
await api.delete(`/caldav/accounts/${btn.dataset.caldavDisconnect}`);
|
await api.delete(`/caldav/accounts/${btn.dataset.caldavDisconnect}`);
|
||||||
state.accounts = state.accounts.filter(a => a.id !== parseInt(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'));
|
showToast(t('caldav_disconnected'));
|
||||||
} catch (e) { showToast(e.message, true); }
|
} catch (e) { showToast(e.message, true); }
|
||||||
});
|
});
|
||||||
@@ -1274,7 +1302,7 @@ function renderAllAccounts() {
|
|||||||
try {
|
try {
|
||||||
await api.delete(`/ical/subscriptions/${btn.dataset.icalDelete}`);
|
await api.delete(`/ical/subscriptions/${btn.dataset.icalDelete}`);
|
||||||
state.icalSubscriptions = state.icalSubscriptions.filter(s => s.id !== parseInt(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); }
|
} catch (e) { showToast(e.message, true); }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user