feat: consistent IconGridSelect sizing + descriptions + filter upgrades
- Added desc text to all 40+ grid items (EN + RU) - compact prop on all IconGridSelect in compact form sections - Fixed compact width to fill grid cells (removed width:auto) - Replaced <select> filter dropdowns with IconGridSelect on config pages - Replaced <select> provider filters with EntitySelect on tracker pages - Dashboard filters constrained to fixed widths (not full row) - Added filtering to command-template-configs and providers pages - providerTypeFilterItems() with "All" option for filter contexts
This commit is contained in:
@@ -257,9 +257,9 @@
|
||||
placeholder={t('dashboard.searchEvents')}
|
||||
class="w-full px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
</div>
|
||||
<IconGridSelect items={eventTypeFilterItems()} bind:value={filterEventType} columns={3} compact />
|
||||
<IconGridSelect items={providerFilterItems} bind:value={filterProviderId} columns={2} compact />
|
||||
<IconGridSelect items={sortFilterItems()} bind:value={filterSort} columns={2} compact />
|
||||
<div class="w-40"><IconGridSelect items={eventTypeFilterItems()} bind:value={filterEventType} columns={3} compact /></div>
|
||||
<div class="w-40"><IconGridSelect items={providerFilterItems} bind:value={filterProviderId} columns={2} compact /></div>
|
||||
<div class="w-36"><IconGridSelect items={sortFilterItems()} bind:value={filterSort} columns={2} compact /></div>
|
||||
</div>
|
||||
|
||||
<!-- Chart (now inside Events section, affected by filters) -->
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import IconButton from '$lib/components/IconButton.svelte';
|
||||
import CrossLink from '$lib/components/CrossLink.svelte';
|
||||
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
|
||||
import { providerTypeItems, responseModeItems } from '$lib/grid-items';
|
||||
import { providerTypeItems, providerTypeFilterItems, responseModeItems } from '$lib/grid-items';
|
||||
import EntitySelect from '$lib/components/EntitySelect.svelte';
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
import { highlightFromUrl } from '$lib/highlight';
|
||||
@@ -202,7 +202,7 @@
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs mb-1">{t('commandConfig.responseMode')}</label>
|
||||
<IconGridSelect items={responseModeItems(t)} bind:value={form.response_mode} columns={2} />
|
||||
<IconGridSelect items={responseModeItems(t)} bind:value={form.response_mode} columns={2} compact />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs mb-1">{t('commandConfig.defaultCount')}</label>
|
||||
@@ -239,12 +239,9 @@
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<input type="text" bind:value={filterText} placeholder={t('common.filterByName')}
|
||||
class="flex-1 px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
<select bind:value={filterType}
|
||||
class="px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]">
|
||||
<option value="">{t('common.allTypes')}</option>
|
||||
<option value="immich">Immich</option>
|
||||
<option value="gitea">Gitea</option>
|
||||
</select>
|
||||
<div class="w-48">
|
||||
<IconGridSelect items={providerTypeFilterItems()} bind:value={filterType} columns={2} compact />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import ConfirmModal from '$lib/components/ConfirmModal.svelte';
|
||||
import IconButton from '$lib/components/IconButton.svelte';
|
||||
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
|
||||
import { providerTypeItems as providerTypeItemsFn } from '$lib/grid-items';
|
||||
import { providerTypeItems as providerTypeItemsFn, providerTypeFilterItems } from '$lib/grid-items';
|
||||
import Modal from '$lib/components/Modal.svelte';
|
||||
import JinjaEditor from '$lib/components/JinjaEditor.svelte';
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
@@ -37,7 +37,13 @@
|
||||
|
||||
const LOCALES = ['en', 'ru'] as const;
|
||||
|
||||
let configs = $state<CmdTemplateConfig[]>([]);
|
||||
let allCmdTplConfigs = $state<CmdTemplateConfig[]>([]);
|
||||
let filterText = $state('');
|
||||
let filterType = $state('');
|
||||
let configs = $derived(allCmdTplConfigs.filter(c =>
|
||||
(!filterText || c.name.toLowerCase().includes(filterText.toLowerCase())) &&
|
||||
(!filterType || c.provider_type === filterType)
|
||||
));
|
||||
let loaded = $state(false);
|
||||
let showForm = $state(false);
|
||||
let editing = $state<number | null>(null);
|
||||
@@ -88,7 +94,7 @@
|
||||
api('/providers/capabilities'),
|
||||
api('/command-template-configs/variables'),
|
||||
]);
|
||||
configs = cfgs;
|
||||
allCmdTplConfigs = cfgs;
|
||||
allCapabilities = caps;
|
||||
varsRef = vars;
|
||||
} catch (err: any) {
|
||||
@@ -325,10 +331,24 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if configs.length === 0 && !showForm}
|
||||
{#if !showForm && allCmdTplConfigs.length > 0}
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<input type="text" bind:value={filterText} placeholder={t('common.filterByName')}
|
||||
class="flex-1 px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
<div class="w-48">
|
||||
<IconGridSelect items={providerTypeFilterItems()} bind:value={filterType} columns={2} compact />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if allCmdTplConfigs.length === 0 && !showForm}
|
||||
<Card>
|
||||
<EmptyState icon="mdiConsoleLine" message={t('cmdTemplateConfig.noConfigs')} />
|
||||
</Card>
|
||||
{:else if configs.length === 0 && !showForm}
|
||||
<Card>
|
||||
<EmptyState icon="mdiFilterOff" message={t('common.noFilterResults')} />
|
||||
</Card>
|
||||
{:else}
|
||||
<div class="space-y-3 stagger-children">
|
||||
{#each configs as config}
|
||||
|
||||
@@ -222,13 +222,9 @@
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<input type="text" bind:value={filterText} placeholder={t('common.filterByName')}
|
||||
class="flex-1 px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
<select bind:value={filterProviderId}
|
||||
class="px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]">
|
||||
<option value={0}>{t('common.allProviders')}</option>
|
||||
{#each providers as p}
|
||||
<option value={p.id}>{p.name} ({p.type})</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div class="w-48">
|
||||
<EntitySelect items={[{value: 0, label: t('common.allProviders'), icon: 'mdiFilterOff'}, ...providerItems]} bind:value={filterProviderId} placeholder={t('common.allProviders')} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -377,13 +377,9 @@
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<input type="text" bind:value={filterText} placeholder={t('common.filterByName')}
|
||||
class="flex-1 px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
<select bind:value={filterProviderId}
|
||||
class="px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]">
|
||||
<option value={0}>{t('common.allProviders')}</option>
|
||||
{#each providers as p}
|
||||
<option value={p.id}>{p.name} ({p.type})</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div class="w-48">
|
||||
<EntitySelect items={[{value: 0, label: t('common.allProviders'), icon: 'mdiFilterOff'}, ...providerItems]} bind:value={filterProviderId} placeholder={t('common.allProviders')} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
import { highlightFromUrl } from '$lib/highlight';
|
||||
import type { ServiceProvider } from '$lib/types';
|
||||
|
||||
let providers = $derived(providersCache.items);
|
||||
let allProviders = $derived(providersCache.items);
|
||||
let filterText = $state('');
|
||||
let providers = $derived(allProviders.filter(p =>
|
||||
!filterText || p.name.toLowerCase().includes(filterText.toLowerCase()) || p.type.toLowerCase().includes(filterText.toLowerCase())
|
||||
));
|
||||
let showForm = $state(false);
|
||||
let editing = $state<number | null>(null);
|
||||
let form = $state({ name: 'Immich', type: 'immich', url: '', api_key: '', api_token: '', webhook_secret: '', external_domain: '', icon: '' });
|
||||
@@ -183,10 +187,21 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if providers.length === 0 && !showForm}
|
||||
{#if !showForm && allProviders.length > 0}
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<input type="text" bind:value={filterText} placeholder={t('common.filterByName')}
|
||||
class="flex-1 px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if allProviders.length === 0 && !showForm}
|
||||
<Card>
|
||||
<EmptyState icon="mdiServer" message={t('providers.noProviders')} />
|
||||
</Card>
|
||||
{:else if providers.length === 0 && !showForm}
|
||||
<Card>
|
||||
<EmptyState icon="mdiFilterOff" message={t('common.noFilterResults')} />
|
||||
</Card>
|
||||
{:else}
|
||||
<div class="space-y-3 stagger-children">
|
||||
{#each providers as provider}
|
||||
|
||||
@@ -443,7 +443,7 @@
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-xs mb-1">{t('targets.chatAction')}</label>
|
||||
<IconGridSelect items={chatActionItems()} bind:value={form.chat_action} columns={4} />
|
||||
<IconGridSelect items={chatActionItems()} bind:value={form.chat_action} columns={4} compact />
|
||||
</div>
|
||||
<label class="flex items-center gap-2 text-sm col-span-2"><input type="checkbox" bind:checked={form.disable_url_preview} /> {t('targets.disableUrlPreview')}</label>
|
||||
<label class="flex items-center gap-2 text-sm col-span-2"><input type="checkbox" bind:checked={form.send_large_photos_as_documents} /> {t('targets.sendLargeAsDocuments')}</label>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import Hint from '$lib/components/Hint.svelte';
|
||||
import IconButton from '$lib/components/IconButton.svelte';
|
||||
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
|
||||
import { providerTypeItems, previewTargetTypeItems } from '$lib/grid-items';
|
||||
import { providerTypeItems, providerTypeFilterItems, previewTargetTypeItems } from '$lib/grid-items';
|
||||
import Modal from '$lib/components/Modal.svelte';
|
||||
import JinjaEditor from '$lib/components/JinjaEditor.svelte';
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
@@ -347,13 +347,9 @@
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<input type="text" bind:value={filterText} placeholder={t('common.filterByName')}
|
||||
class="flex-1 px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
<select bind:value={filterType}
|
||||
class="px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]">
|
||||
<option value="">{t('common.allTypes')}</option>
|
||||
<option value="immich">Immich</option>
|
||||
<option value="gitea">Gitea</option>
|
||||
<option value="scheduler">Scheduler</option>
|
||||
</select>
|
||||
<div class="w-48">
|
||||
<IconGridSelect items={providerTypeFilterItems()} bind:value={filterType} columns={2} compact />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import Hint from '$lib/components/Hint.svelte';
|
||||
import IconButton from '$lib/components/IconButton.svelte';
|
||||
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
|
||||
import { providerTypeItems, sortByItems, sortOrderItems, albumModeItems, assetTypeItems, memorySourceItems } from '$lib/grid-items';
|
||||
import { providerTypeItems, providerTypeFilterItems, sortByItems, sortOrderItems, albumModeItems, assetTypeItems, memorySourceItems } from '$lib/grid-items';
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
import { highlightFromUrl } from '$lib/highlight';
|
||||
import type { TrackingConfig } from '$lib/types';
|
||||
@@ -163,11 +163,11 @@
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs mb-1">{t('trackingConfig.sortBy')}</label>
|
||||
<IconGridSelect items={sortByItems()} bind:value={form.assets_order_by} columns={2} />
|
||||
<IconGridSelect items={sortByItems()} bind:value={form.assets_order_by} columns={2} compact />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs mb-1">{t('trackingConfig.sortOrder')}</label>
|
||||
<IconGridSelect items={sortOrderItems()} bind:value={form.assets_order} columns={2} />
|
||||
<IconGridSelect items={sortOrderItems()} bind:value={form.assets_order} columns={2} compact />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -195,10 +195,10 @@
|
||||
<div class="grid grid-cols-3 gap-3 mt-3">
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.times')}<Hint text={t('hints.times')} /></label><input bind:value={form.scheduled_times} placeholder="09:00" class="w-full px-2 py-1 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" /></div>
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.albumMode')}<Hint text={t('hints.albumMode')} /></label>
|
||||
<IconGridSelect items={albumModeItems()} bind:value={form.scheduled_collection_mode} columns={3} /></div>
|
||||
<IconGridSelect items={albumModeItems()} bind:value={form.scheduled_collection_mode} columns={3} compact /></div>
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.maxAssets')}<Hint text={t('hints.maxAssets')} /></label><input type="number" bind:value={form.scheduled_limit} min="1" max="100" class="w-full px-2 py-1 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" /></div>
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.assetType')}</label>
|
||||
<IconGridSelect items={assetTypeItems()} bind:value={form.scheduled_asset_type} columns={3} /></div>
|
||||
<IconGridSelect items={assetTypeItems()} bind:value={form.scheduled_asset_type} columns={3} compact /></div>
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.minRating')}<Hint text={t('hints.minRating')} /></label><input type="number" bind:value={form.scheduled_min_rating} min="0" max="5" class="w-full px-2 py-1 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" /></div>
|
||||
<label class="flex items-center gap-2 text-sm"><input type="checkbox" bind:checked={form.scheduled_favorite_only} /> {t('trackingConfig.favoritesOnly')}<Hint text={t('hints.favoritesOnly')} /></label>
|
||||
</div>
|
||||
@@ -212,13 +212,13 @@
|
||||
{#if form.memory_enabled}
|
||||
<div class="grid grid-cols-3 gap-3 mt-3">
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.memorySource')}<Hint text={t('hints.memorySource')} /></label>
|
||||
<IconGridSelect items={memorySourceItems()} bind:value={form.memory_source} columns={2} /></div>
|
||||
<IconGridSelect items={memorySourceItems()} bind:value={form.memory_source} columns={2} compact /></div>
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.times')}<Hint text={t('hints.times')} /></label><input bind:value={form.memory_times} placeholder="09:00" class="w-full px-2 py-1 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" /></div>
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.albumMode')}<Hint text={t('hints.albumMode')} /></label>
|
||||
<IconGridSelect items={albumModeItems()} bind:value={form.memory_collection_mode} columns={3} /></div>
|
||||
<IconGridSelect items={albumModeItems()} bind:value={form.memory_collection_mode} columns={3} compact /></div>
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.maxAssets')}<Hint text={t('hints.maxAssets')} /></label><input type="number" bind:value={form.memory_limit} min="1" max="100" class="w-full px-2 py-1 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" /></div>
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.assetType')}</label>
|
||||
<IconGridSelect items={assetTypeItems()} bind:value={form.memory_asset_type} columns={3} /></div>
|
||||
<IconGridSelect items={assetTypeItems()} bind:value={form.memory_asset_type} columns={3} compact /></div>
|
||||
<div><label class="block text-xs mb-1">{t('trackingConfig.minRating')}<Hint text={t('hints.minRating')} /></label><input type="number" bind:value={form.memory_min_rating} min="0" max="5" class="w-full px-2 py-1 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" /></div>
|
||||
<label class="flex items-center gap-2 text-sm"><input type="checkbox" bind:checked={form.memory_favorite_only} /> {t('trackingConfig.favoritesOnly')}<Hint text={t('hints.favoritesOnly')} /></label>
|
||||
</div>
|
||||
@@ -238,13 +238,9 @@
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<input type="text" bind:value={filterText} placeholder={t('common.filterByName')}
|
||||
class="flex-1 px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
<select bind:value={filterType}
|
||||
class="px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]">
|
||||
<option value="">{t('common.allTypes')}</option>
|
||||
<option value="immich">Immich</option>
|
||||
<option value="gitea">Gitea</option>
|
||||
<option value="scheduler">Scheduler</option>
|
||||
</select>
|
||||
<div class="w-48">
|
||||
<IconGridSelect items={providerTypeFilterItems()} bind:value={filterType} columns={2} compact />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user