feat: entity cache system, nav UX improvements, split CLAUDE.md
- Add $state-based entity cache layer with 30s TTL, request deduplication, and local mutation helpers (entity-cache.svelte.ts + caches.svelte.ts) - Wire all 10 page components to use shared caches for cross-page data - Add slide animation for nav tree expand/collapse with rotating chevron - Remove aggregate count badges from container nav nodes (keep on leaves) - Convert Targets from flat leaf to group with per-type children (Telegram, Webhook, Email, Discord, Slack, ntfy, Matrix) - Add URL-based type filtering on Targets page with per-type descriptions - Add Bots group children for Email and Matrix alongside Telegram - Tab-based routing for bots page (?tab=telegram/email/matrix) - Add per-type target counts and email/matrix bot counts to /status/counts - Split CLAUDE.md into focused context files under .claude/docs/ - Fix .gitignore: scope lib/ to root, allow .claude/docs/ tracking - Clear all caches on logout - Reset form state when switching target type tabs
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { slide } from 'svelte/transition';
|
||||
import { api } from '$lib/api';
|
||||
import { t } from '$lib/i18n';
|
||||
import { providersCache } from '$lib/stores/caches.svelte';
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import Card from '$lib/components/Card.svelte';
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
@@ -14,7 +15,7 @@
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
import type { ServiceProvider } from '$lib/types';
|
||||
|
||||
let providers = $state<ServiceProvider[]>([]);
|
||||
let providers = $derived(providersCache.items);
|
||||
let showForm = $state(false);
|
||||
let editing = $state<number | null>(null);
|
||||
let form = $state({ name: 'Immich', type: 'immich', url: '', api_key: '', external_domain: '', icon: '' });
|
||||
@@ -29,7 +30,7 @@
|
||||
onMount(load);
|
||||
async function load() {
|
||||
try {
|
||||
providers = await api('/providers');
|
||||
await providersCache.fetch(true);
|
||||
loadError = '';
|
||||
} catch (err: any) {
|
||||
loadError = err.message || t('providers.loadError');
|
||||
@@ -65,7 +66,7 @@
|
||||
config.api_key = form.api_key; // required on create
|
||||
await api('/providers', { method: 'POST', body: JSON.stringify({ type: form.type, name: form.name, icon: form.icon, config }) });
|
||||
}
|
||||
showForm = false; editing = null; await load();
|
||||
showForm = false; editing = null; providersCache.invalidate(); await load();
|
||||
snackSuccess(t('snack.providerSaved'));
|
||||
} catch (err: any) { error = err.message; snackError(err.message); }
|
||||
submitting = false;
|
||||
@@ -76,7 +77,7 @@
|
||||
if (!confirmDelete) return;
|
||||
const id = confirmDelete.id;
|
||||
confirmDelete = null;
|
||||
try { await api(`/providers/${id}`, { method: 'DELETE' }); await load(); snackSuccess(t('snack.providerDeleted')); }
|
||||
try { await api(`/providers/${id}`, { method: 'DELETE' }); providersCache.invalidate(); await load(); snackSuccess(t('snack.providerDeleted')); }
|
||||
catch (err: any) { error = err.message; snackError(err.message); }
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user