Add MDI icon picker to all entity types
Some checks failed
Validate / Hassfest (push) Has been cancelled

- Install @mdi/js (~7000 Material Design Icons)
- IconPicker component: dropdown with search, popular icons grid,
  clear option. Stores icon name as string (e.g. "mdiCamera")
- MdiIcon component: renders SVG from icon name
- Backend: add `icon` field to ImmichServer, TelegramBot,
  TrackingConfig, TemplateConfig, NotificationTarget, AlbumTracker
- All 6 entity pages: icon picker next to name input in create/edit
  forms, icon displayed on entity cards

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 18:01:22 +03:00
parent af9bfb7b22
commit 5a0b0b78f6
11 changed files with 182 additions and 24 deletions

View File

@@ -5,11 +5,13 @@
import PageHeader from '$lib/components/PageHeader.svelte';
import Card from '$lib/components/Card.svelte';
import Loading from '$lib/components/Loading.svelte';
import IconPicker from '$lib/components/IconPicker.svelte';
import MdiIcon from '$lib/components/MdiIcon.svelte';
let servers = $state<any[]>([]);
let showForm = $state(false);
let editing = $state<number | null>(null);
let form = $state({ name: 'Immich', url: '', api_key: '' });
let form = $state({ name: 'Immich', url: '', api_key: '', icon: '' });
let error = $state('');
let submitting = $state(false);
let loaded = $state(false);
@@ -27,11 +29,11 @@
}
function openNew() {
form = { name: 'Immich', url: '', api_key: '' };
form = { name: 'Immich', url: '', api_key: '', icon: '' };
editing = null; showForm = true;
}
function edit(s: any) {
form = { name: s.name, url: s.url, api_key: '' };
form = { name: s.name, url: s.url, api_key: '', icon: s.icon || '' };
editing = s.id; showForm = true;
}
@@ -72,8 +74,13 @@
{#if error}<div class="bg-[var(--color-error-bg)] text-[var(--color-error-fg)] text-sm rounded-md p-3 mb-4">{error}</div>{/if}
<form onsubmit={save} class="space-y-3">
<div>
<label for="srv-name" class="block text-sm font-medium mb-1">{t('servers.name')}</label>
<input id="srv-name" bind:value={form.name} required class="w-full px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
<div class="flex items-end gap-2">
<label for="srv-name" class="block text-sm font-medium mb-1">{t('servers.name')}</label>
</div>
<div class="flex gap-2">
<IconPicker value={form.icon} onselect={(v) => form.icon = v} />
<input id="srv-name" bind:value={form.name} required class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
</div>
</div>
<div>
<label for="srv-url" class="block text-sm font-medium mb-1">{t('servers.url')}</label>
@@ -100,6 +107,7 @@
<div class="flex items-center gap-2">
<span class="inline-block w-2.5 h-2.5 rounded-full {health[server.id] === true ? 'bg-green-500' : health[server.id] === false ? 'bg-red-500' : 'bg-yellow-400 animate-pulse'}"
title={health[server.id] === true ? 'Online' : health[server.id] === false ? 'Offline' : 'Checking...'}></span>
{#if server.icon}<MdiIcon name={server.icon} />{/if}
<div>
<p class="font-medium">{server.name}</p>
<p class="text-sm text-[var(--color-muted-foreground)]">{server.url}</p>