feat: IconGridSelect, CrossLink, SearchPalette components + entity crosslinks
New components: - IconGridSelect: Visual grid selector replacing <select> dropdowns, with icon + label cells, fixed-position popup, smart placement - CrossLink: Inline clickable badge for cross-entity navigation, hover highlights primary, used on entity cards - SearchPalette: Ctrl+K global command palette, searches all entity types via cached data, grouped results, keyboard navigation Integration: - Targets: type selector uses IconGridSelect (4-column grid with icons) - Targets: bot crosslink on telegram/email/matrix target cards - Command Trackers: provider and config badges → CrossLinks - Command Trackers: listener bot name → CrossLink - Command Configs: template config shown as CrossLink on card - Notification Trackers: provider CrossLink added to card - Layout: SearchPalette mounted globally Infrastructure: - Added CommandConfig, CommandTemplateConfig, CommandTracker types - Added notificationTrackersCache, commandTrackersCache to caches - Added allCaches map and fetchAllCaches() for search palette - Added searchPalette i18n keys (EN/RU)
This commit is contained in:
@@ -14,9 +14,34 @@
|
||||
import ConfirmModal from '$lib/components/ConfirmModal.svelte';
|
||||
import Hint from '$lib/components/Hint.svelte';
|
||||
import IconButton from '$lib/components/IconButton.svelte';
|
||||
import CrossLink from '$lib/components/CrossLink.svelte';
|
||||
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
import type { NotificationTarget, TelegramBot, TelegramChat, EmailBot, MatrixBot } from '$lib/types';
|
||||
|
||||
function getBotName(target: any): string | null {
|
||||
if (target.type === 'telegram' && target.config?.bot_id) {
|
||||
const bot = telegramBots.find(b => b.id === target.config.bot_id);
|
||||
return bot?.name || null;
|
||||
}
|
||||
if (target.type === 'email' && target.config?.email_bot_id) {
|
||||
const bot = emailBots.find(b => b.id === target.config.email_bot_id);
|
||||
return bot?.name || null;
|
||||
}
|
||||
if (target.type === 'matrix' && target.config?.matrix_bot_id) {
|
||||
const bot = matrixBots.find(b => b.id === target.config.matrix_bot_id);
|
||||
return bot?.name || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBotHref(target: any): string {
|
||||
if (target.type === 'telegram') return '/telegram-bots';
|
||||
if (target.type === 'email') return '/telegram-bots?tab=email';
|
||||
if (target.type === 'matrix') return '/telegram-bots?tab=matrix';
|
||||
return '/telegram-bots';
|
||||
}
|
||||
|
||||
const ALL_TYPES = ['telegram', 'webhook', 'email', 'discord', 'slack', 'ntfy', 'matrix'] as const;
|
||||
type TargetType = typeof ALL_TYPES[number];
|
||||
const TYPE_ICONS: Record<string, string> = {
|
||||
@@ -28,6 +53,12 @@
|
||||
discord: 'targets.descDiscord', slack: 'targets.descSlack', ntfy: 'targets.descNtfy', matrix: 'targets.descMatrix',
|
||||
};
|
||||
|
||||
const typeGridItems = $derived(ALL_TYPES.map(tt => ({
|
||||
value: tt,
|
||||
icon: TYPE_ICONS[tt] || 'mdiTarget',
|
||||
label: tt.charAt(0).toUpperCase() + tt.slice(1),
|
||||
})));
|
||||
|
||||
let allTargets = $derived(targetsCache.items);
|
||||
let activeType = $derived(page.url.searchParams.get('type') as TargetType | null);
|
||||
let targets = $derived(activeType ? allTargets.filter(t => t.type === activeType) : allTargets);
|
||||
@@ -189,13 +220,8 @@
|
||||
<form onsubmit={save} class="space-y-4">
|
||||
{#if !activeType}
|
||||
<div>
|
||||
<label for="tgt-type" class="block text-sm font-medium mb-1">{t('targets.type')}</label>
|
||||
<select id="tgt-type" bind:value={formType}
|
||||
class="w-full px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]">
|
||||
{#each ALL_TYPES as tt}
|
||||
<option value={tt}>{tt.charAt(0).toUpperCase() + tt.slice(1)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<label class="block text-sm font-medium mb-1">{t('targets.type')}</label>
|
||||
<IconGridSelect items={typeGridItems} bind:value={formType} columns={4} />
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
@@ -380,6 +406,7 @@
|
||||
<p class="font-medium">{target.name}</p>
|
||||
{#if !activeType}<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)]">{target.type}</span>{/if}
|
||||
{#if target.receiver_count}<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)]">{target.receiver_count} receiver(s)</span>{/if}
|
||||
{#if getBotName(target)}<CrossLink href={getBotHref(target)} icon="mdiRobot" label={getBotName(target)} />{/if}
|
||||
</div>
|
||||
<p class="text-sm text-[var(--color-muted-foreground)]">
|
||||
{#if target.type === 'telegram'}
|
||||
|
||||
Reference in New Issue
Block a user