diff --git a/frontend/src/lib/components/IconGridSelect.svelte b/frontend/src/lib/components/IconGridSelect.svelte index 3709c48..e72a509 100644 --- a/frontend/src/lib/components/IconGridSelect.svelte +++ b/frontend/src/lib/components/IconGridSelect.svelte @@ -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 @@ {#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); diff --git a/frontend/src/lib/grid-items.ts b/frontend/src/lib/grid-items.ts new file mode 100644 index 0000000..2d58fdd --- /dev/null +++ b/frontend/src/lib/grid-items.ts @@ -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' }, +]; diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 1b2f9b0..88cacd5 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -373,7 +373,9 @@ "added": "added", "removed": "removed", "renamed": "renamed", - "deleted": "deleted" + "deleted": "deleted", + "providerType": "Provider Type", + "sortRandom": "Random" }, "templateConfig": { "title": "Template Configs", diff --git a/frontend/src/lib/i18n/ru.json b/frontend/src/lib/i18n/ru.json index 92f9ee7..ebe5e78 100644 --- a/frontend/src/lib/i18n/ru.json +++ b/frontend/src/lib/i18n/ru.json @@ -373,7 +373,9 @@ "added": "добавление", "removed": "удаление", "renamed": "переименование", - "deleted": "удалён" + "deleted": "удалён", + "providerType": "Тип провайдера", + "sortRandom": "Случайный" }, "templateConfig": { "title": "Конфигурации шаблонов", diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index ef77d31..c36d9f0 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -8,9 +8,16 @@ import Loading from '$lib/components/Loading.svelte'; import MdiIcon from '$lib/components/MdiIcon.svelte'; import EventChart from '$lib/components/EventChart.svelte'; + import IconGridSelect from '$lib/components/IconGridSelect.svelte'; + import EntitySelect from '$lib/components/EntitySelect.svelte'; + import { eventTypeFilterItems, sortFilterItems } from '$lib/grid-items'; let status = $state(null); let providers = $derived(providersCache.items); + const providerFilterItems = $derived([ + { value: '', label: t('dashboard.allProviders'), icon: 'mdiFilterOff' }, + ...providers.map(p => ({ value: p.id, label: p.name, icon: p.icon || 'mdiServer', desc: p.type })), + ]); let chartDays = $state([]); let loaded = $state(false); let error = $state(''); @@ -88,6 +95,16 @@ } catch {} } + // Auto-apply when filter values change (via IconGridSelect bind:value) + let _prevFilterKey = ''; + $effect(() => { + const key = `${filterEventType}|${filterProviderId}|${filterSort}`; + if (loaded && key !== _prevFilterKey && _prevFilterKey !== '') { + applyFilters(); + } + _prevFilterKey = key; + }); + function applyFilters() { eventsOffset = 0; loadEvents(); @@ -190,14 +207,6 @@ collection_renamed: '#6366f1', collection_deleted: '#dc2626', sharing_changed: '#f59e0b', }; - const eventTypeOptions = $derived([ - { value: '', label: t('dashboard.allEvents') }, - { value: 'assets_added', label: t('dashboard.filterAssetsAdded') }, - { value: 'assets_removed', label: t('dashboard.filterAssetsRemoved') }, - { value: 'collection_renamed', label: t('dashboard.filterRenamed') }, - { value: 'collection_deleted', label: t('dashboard.filterDeleted') }, - { value: 'sharing_changed', label: t('dashboard.filterSharingChanged') }, - ]); @@ -248,24 +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)]" /> - - - + + + diff --git a/frontend/src/routes/command-configs/+page.svelte b/frontend/src/routes/command-configs/+page.svelte index 230c717..8be296c 100644 --- a/frontend/src/routes/command-configs/+page.svelte +++ b/frontend/src/routes/command-configs/+page.svelte @@ -13,10 +13,7 @@ import IconButton from '$lib/components/IconButton.svelte'; import CrossLink from '$lib/components/CrossLink.svelte'; import IconGridSelect from '$lib/components/IconGridSelect.svelte'; - - const providerTypeItems = [ - { value: 'immich', icon: 'mdiCamera', label: 'Immich' }, - ]; + import { providerTypeItems, localeItems, 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'; @@ -169,7 +166,7 @@
{#if !editing} - + {:else}

{form.provider_type}

{/if} @@ -198,19 +195,11 @@
- +
- +
diff --git a/frontend/src/routes/command-template-configs/+page.svelte b/frontend/src/routes/command-template-configs/+page.svelte index c16d8f1..6bde53d 100644 --- a/frontend/src/routes/command-template-configs/+page.svelte +++ b/frontend/src/routes/command-template-configs/+page.svelte @@ -13,6 +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 Modal from '$lib/components/Modal.svelte'; import JinjaEditor from '$lib/components/JinjaEditor.svelte'; import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte'; @@ -51,9 +52,6 @@ // Provider capabilities let allCapabilities = $state>({}); let providerTypes = $derived(Object.keys(allCapabilities)); - const providerTypeItems = $derived(providerTypes.map(pt => ({ - value: pt, icon: 'mdiCamera', label: allCapabilities[pt]?.display_name || pt, - }))); let commandSlots = $derived( allCapabilities[form.provider_type]?.command_slots || [] ); @@ -236,7 +234,7 @@ {#if !editing}
- +
{:else}
diff --git a/frontend/src/routes/providers/+page.svelte b/frontend/src/routes/providers/+page.svelte index 761e37c..b58e4f4 100644 --- a/frontend/src/routes/providers/+page.svelte +++ b/frontend/src/routes/providers/+page.svelte @@ -12,6 +12,8 @@ import EmptyState from '$lib/components/EmptyState.svelte'; import ConfirmModal from '$lib/components/ConfirmModal.svelte'; import IconButton from '$lib/components/IconButton.svelte'; + import IconGridSelect from '$lib/components/IconGridSelect.svelte'; + import { providerTypeItems } from '$lib/grid-items'; import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte'; import { highlightFromUrl } from '$lib/highlight'; import type { ServiceProvider } from '$lib/types'; @@ -111,11 +113,12 @@ {/if}
- - + + {#if !editing} + + {:else} +

{form.type}

+ {/if}
diff --git a/frontend/src/routes/targets/+page.svelte b/frontend/src/routes/targets/+page.svelte index 977180f..96e579a 100644 --- a/frontend/src/routes/targets/+page.svelte +++ b/frontend/src/routes/targets/+page.svelte @@ -17,6 +17,7 @@ import CrossLink from '$lib/components/CrossLink.svelte'; import IconGridSelect from '$lib/components/IconGridSelect.svelte'; import EntitySelect from '$lib/components/EntitySelect.svelte'; + import { chatActionItems } from '$lib/grid-items'; import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte'; import { highlightFromUrl } from '$lib/highlight'; import type { NotificationTarget, TelegramBot, TelegramChat, EmailBot, MatrixBot } from '$lib/types'; @@ -308,17 +309,8 @@
- - + +
diff --git a/frontend/src/routes/template-configs/+page.svelte b/frontend/src/routes/template-configs/+page.svelte index 495ed0c..ccd5a23 100644 --- a/frontend/src/routes/template-configs/+page.svelte +++ b/frontend/src/routes/template-configs/+page.svelte @@ -14,6 +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 Modal from '$lib/components/Modal.svelte'; import JinjaEditor from '$lib/components/JinjaEditor.svelte'; import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte'; @@ -111,9 +112,6 @@ // Provider capabilities: loaded dynamically let allCapabilities = $state>({}); let providerTypes = $derived(Object.keys(allCapabilities)); - const providerTypeItems = $derived(providerTypes.map(pt => ({ - value: pt, icon: 'mdiCamera', label: allCapabilities[pt]?.display_name || pt, - }))); // Dynamic slot definitions based on selected provider_type let notificationSlots = $derived<{name: string, description: string}[]>( @@ -249,7 +247,7 @@ {#if !editing}
- +
{:else}
@@ -259,12 +257,8 @@ {/if}
- - + +
{#each templateSlots as group} diff --git a/frontend/src/routes/tracking-configs/+page.svelte b/frontend/src/routes/tracking-configs/+page.svelte index cf39108..449b884 100644 --- a/frontend/src/routes/tracking-configs/+page.svelte +++ b/frontend/src/routes/tracking-configs/+page.svelte @@ -14,10 +14,7 @@ import Hint from '$lib/components/Hint.svelte'; import IconButton from '$lib/components/IconButton.svelte'; import IconGridSelect from '$lib/components/IconGridSelect.svelte'; - - const providerTypeItems = [ - { value: 'immich', icon: 'mdiCamera', label: 'Immich' }, - ]; + import { providerTypeItems, 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'; @@ -106,7 +103,7 @@
{#if !editing} - + {:else}

{form.provider_type}

{/if} @@ -133,16 +130,12 @@
- - + +
- - + +
@@ -168,14 +161,10 @@
-
+
-
+
@@ -189,19 +178,13 @@ {#if form.memory_enabled}
-
+
-
+
-
+
@@ -229,6 +212,7 @@
{#if config.icon}{/if}

{config.name}

+ {config.provider_type}

{[config.track_assets_added && t('trackingConfig.added'), config.track_assets_removed && t('trackingConfig.removed'), config.track_collection_renamed && t('trackingConfig.renamed'), config.track_collection_deleted && t('trackingConfig.deleted')].filter(Boolean).join(', ')}