feat: replace all select dropdowns with IconGridSelect, fix EN template seed

Shared grid-items.ts with reusable item definitions for: sort by/order,
album mode, asset type, memory source, locale, response mode, event type
filter, chat action, preview target type, provider type.

Replaced selects on:
- Dashboard: event type, provider, sort (compact mode — auto-width)
- Tracking configs: sort by/order, album modes, asset types, memory source
- Command configs: locale, response mode, provider type
- Targets: chat action
- Template configs: preview target type, provider type
- Command template configs: provider type
- Providers: type selector (read-only during edit)

IconGridSelect: added compact prop for inline filter bars (auto-width,
smaller padding, shows icon + label text).

Backend: template seed now re-creates deleted system templates on startup
using raw SQL to handle legacy NOT NULL columns.

Added i18n: trackingConfig.providerType, trackingConfig.sortRandom
Added provider_type badge to tracking config cards.
This commit is contained in:
2026-03-22 01:17:06 +03:00
parent db7aac5fe8
commit a9bb912c30
11 changed files with 175 additions and 104 deletions
@@ -14,12 +14,14 @@
placeholder = 'Select...',
columns = 2,
disabled = false,
compact = false,
}: {
items: GridItem[];
value: string | number | null;
placeholder?: string;
columns?: number;
disabled?: boolean;
compact?: boolean;
} = $props();
let open = $state(false);
@@ -58,16 +60,16 @@
<svelte:window onkeydown={open ? handleKeydown : undefined} />
<button type="button" bind:this={triggerEl} onclick={toggle}
class="icon-grid-trigger"
class="icon-grid-trigger {compact ? 'icon-grid-compact' : ''}"
class:disabled
style="opacity: {disabled ? 0.5 : 1}; cursor: {disabled ? 'default' : 'pointer'};">
{#if selected}
<span class="icon-grid-trigger-icon"><MdiIcon name={selected.icon} size={18} /></span>
<span class="icon-grid-trigger-icon"><MdiIcon name={selected.icon} size={compact ? 14 : 18} /></span>
<span class="icon-grid-trigger-label">{selected.label}</span>
{:else}
<span class="icon-grid-trigger-label" style="color: var(--color-muted-foreground);">{placeholder}</span>
{/if}
<span class="icon-grid-trigger-arrow"><MdiIcon name="mdiChevronDown" size={14} /></span>
<span class="icon-grid-trigger-arrow"><MdiIcon name="mdiChevronDown" size={compact ? 10 : 14} /></span>
</button>
{#if open}
@@ -113,6 +115,15 @@
.icon-grid-trigger:hover:not(.disabled) {
border-color: var(--color-primary);
}
.icon-grid-compact {
width: auto;
padding: 0.3rem 0.5rem;
gap: 0.3rem;
font-size: 0.8rem;
}
.icon-grid-compact .icon-grid-trigger-label {
flex: none;
}
.icon-grid-trigger-icon {
flex-shrink: 0;
color: var(--color-primary);
+102
View File
@@ -0,0 +1,102 @@
/**
* Shared IconGridSelect item definitions used across multiple pages.
* Keeps grid item arrays DRY and consistent.
*/
import { t } from '$lib/i18n';
import type { GridItem } from '$lib/components/IconGridSelect.svelte';
// --- Sort ---
export const sortByItems = (): GridItem[] => [
{ value: 'none', icon: 'mdiMinus', label: t('trackingConfig.sortNone') },
{ value: 'date', icon: 'mdiCalendar', label: t('trackingConfig.sortDate') },
{ value: 'rating', icon: 'mdiStar', label: t('trackingConfig.sortRating') },
{ value: 'name', icon: 'mdiSortAlphabeticalAscending', label: t('trackingConfig.sortName') },
{ value: 'random', icon: 'mdiDice3', label: t('trackingConfig.sortRandom') },
];
export const sortOrderItems = (): GridItem[] => [
{ value: 'descending', icon: 'mdiSortDescending', label: t('trackingConfig.orderDesc') },
{ value: 'ascending', icon: 'mdiSortAscending', label: t('trackingConfig.orderAsc') },
];
// --- Album mode ---
export const albumModeItems = (): GridItem[] => [
{ value: 'per_collection', icon: 'mdiViewGrid', label: t('trackingConfig.albumModePerAlbum') },
{ value: 'combined', icon: 'mdiSetMerge', label: t('trackingConfig.albumModeCombined') },
{ value: 'random', icon: 'mdiDice3', label: t('trackingConfig.albumModeRandom') },
];
// --- Asset type ---
export const assetTypeItems = (): GridItem[] => [
{ value: 'all', icon: 'mdiSelectAll', label: t('trackingConfig.assetTypeAll') },
{ value: 'photo', icon: 'mdiImage', label: t('trackingConfig.assetTypePhoto') },
{ value: 'video', icon: 'mdiVideo', label: t('trackingConfig.assetTypeVideo') },
];
// --- Memory source ---
export const memorySourceItems = (): GridItem[] => [
{ value: 'albums', icon: 'mdiImageMultiple', label: t('trackingConfig.memorySourceAlbums') },
{ value: 'native', icon: 'mdiMemory', label: t('trackingConfig.memorySourceNative') },
];
// --- Locale ---
export const localeItems = (): GridItem[] => [
{ value: 'en', icon: 'mdiAlphabeticalVariant', label: 'English' },
{ value: 'ru', icon: 'mdiAlphabeticalVariant', label: 'Русский' },
];
// --- Response mode ---
export const responseModeItems = (tFn: typeof t): GridItem[] => [
{ value: 'media', icon: 'mdiImage', label: tFn('commandConfig.modeMedia') },
{ value: 'text', icon: 'mdiText', label: tFn('commandConfig.modeText') },
];
// --- Event type filter (dashboard) ---
export const eventTypeFilterItems = (): GridItem[] => [
{ value: '', icon: 'mdiFilterOff', label: t('dashboard.allEvents') },
{ value: 'assets_added', icon: 'mdiImagePlus', label: t('dashboard.filterAssetsAdded') },
{ value: 'assets_removed', icon: 'mdiImageMinus', label: t('dashboard.filterAssetsRemoved') },
{ value: 'collection_renamed', icon: 'mdiRename', label: t('dashboard.filterRenamed') },
{ value: 'collection_deleted', icon: 'mdiDeleteAlert', label: t('dashboard.filterDeleted') },
{ value: 'sharing_changed', icon: 'mdiShareVariant', label: t('dashboard.filterSharingChanged') },
];
// --- Sort filter (dashboard) ---
export const sortFilterItems = (): GridItem[] => [
{ value: 'newest', icon: 'mdiSortClockDescending', label: t('dashboard.newestFirst') },
{ value: 'oldest', icon: 'mdiSortClockAscending', label: t('dashboard.oldestFirst') },
];
// --- Chat action (Telegram targets) ---
export const chatActionItems = (): GridItem[] => [
{ value: '', icon: 'mdiMinus', label: 'None' },
{ value: 'typing', icon: 'mdiKeyboard', label: 'Typing' },
{ value: 'upload_photo', icon: 'mdiImagePlus', label: 'Upload Photo' },
{ value: 'upload_video', icon: 'mdiVideoPlus', label: 'Upload Video' },
{ value: 'upload_document', icon: 'mdiFileUpload', label: 'Upload Doc' },
{ value: 'record_video', icon: 'mdiVideo', label: 'Record Video' },
{ value: 'record_voice', icon: 'mdiMicrophone', label: 'Record Voice' },
];
// --- Preview target type ---
export const previewTargetTypeItems = (): GridItem[] => [
{ value: 'telegram', icon: 'mdiSend', label: 'Telegram' },
{ value: 'webhook', icon: 'mdiWebhook', label: 'Webhook' },
];
// --- Provider type ---
export const providerTypeItems = (): GridItem[] => [
{ value: 'immich', icon: 'mdiCamera', label: 'Immich' },
];
+3 -1
View File
@@ -373,7 +373,9 @@
"added": "added",
"removed": "removed",
"renamed": "renamed",
"deleted": "deleted"
"deleted": "deleted",
"providerType": "Provider Type",
"sortRandom": "Random"
},
"templateConfig": {
"title": "Template Configs",
+3 -1
View File
@@ -373,7 +373,9 @@
"added": "добавление",
"removed": "удаление",
"renamed": "переименование",
"deleted": "удалён"
"deleted": "удалён",
"providerType": "Тип провайдера",
"sortRandom": "Случайный"
},
"templateConfig": {
"title": "Конфигурации шаблонов",