refactor(ui): more consistency changes.
This commit is contained in:
parent
e644ea6357
commit
fc5cf84dd2
17 changed files with 704 additions and 928 deletions
|
|
@ -475,7 +475,9 @@
|
|||
: 'i-lucide-circle-help'
|
||||
"
|
||||
title="Filter Status"
|
||||
:description="logicStatusText"
|
||||
:description="
|
||||
testData.data.status === null ? 'Not tested' : logicTest ? 'Matched' : 'Not matched'
|
||||
"
|
||||
/>
|
||||
|
||||
<UFormField :ui="fieldUi">
|
||||
|
|
@ -673,14 +675,6 @@ const logicTest = computed(() => {
|
|||
}
|
||||
});
|
||||
|
||||
const logicStatusText = computed(() => {
|
||||
if (testData.value.data.status === null) {
|
||||
return 'Not tested';
|
||||
}
|
||||
|
||||
return logicTest.value ? 'Matched' : 'Not matched';
|
||||
});
|
||||
|
||||
const checkInfo = async (): Promise<void> => {
|
||||
for (const key of ['name', 'filter'] as const) {
|
||||
if (!form[key]) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="inline-flex min-w-0 items-center gap-2 text-sm font-semibold text-default">
|
||||
<UIcon v-if="resolvedIcon" :name="resolvedIcon" class="size-4 shrink-0 text-toned" />
|
||||
<UIcon v-if="props.icon" :name="props.icon" class="size-4 shrink-0 text-toned" />
|
||||
<UTooltip :text="field ? `yt-dlp option: ${field}` : undefined">
|
||||
<span class="truncate" :class="{ 'has-tooltip': field }">
|
||||
{{ label }}
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<span class="inline-flex items-center gap-2 font-semibold">
|
||||
<UIcon v-if="resolvedIcon" :name="resolvedIcon" class="size-4 text-toned" />
|
||||
<UIcon v-if="props.icon" :name="props.icon" class="size-4 text-toned" />
|
||||
<UTooltip :text="field ? `yt-dlp option: ${field}` : undefined">
|
||||
<span :class="{ 'has-tooltip': field }">
|
||||
{{ label }}
|
||||
|
|
@ -142,14 +142,6 @@ const boolModel = computed({
|
|||
},
|
||||
});
|
||||
|
||||
const resolvedIcon = computed(() => {
|
||||
if (!props.icon) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return props.icon;
|
||||
});
|
||||
|
||||
const fieldUi = computed(() => ({
|
||||
container: props.compact ? 'space-y-1.5' : 'space-y-2',
|
||||
description: props.compact ? 'text-xs text-toned' : 'text-sm text-toned',
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
<template v-if="'alert' === state.current?.type">
|
||||
<UButton
|
||||
id="primaryButton"
|
||||
:color="resolveConfirmColor(state.current?.opts.confirmColor)"
|
||||
:color="state.current?.opts.confirmColor ?? 'primary'"
|
||||
@click="onEnter"
|
||||
>
|
||||
{{ state.current?.opts.confirmText ?? 'OK' }}
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
<template v-else-if="'confirm' === state.current?.type || 'prompt' === state.current?.type">
|
||||
<UButton
|
||||
id="primaryButton"
|
||||
:color="resolveConfirmColor(state.current?.opts.confirmColor)"
|
||||
:color="state.current?.opts.confirmColor ?? 'primary'"
|
||||
:disabled="
|
||||
'prompt' === state.current?.type &&
|
||||
localInput === (state.current?.opts as PromptOptions)?.initial
|
||||
|
|
@ -121,8 +121,6 @@ const focusInput = async () => {
|
|||
requestAnimationFrame(focusPrimary);
|
||||
};
|
||||
|
||||
const resolveConfirmColor = (color?: ConfirmOptions['confirmColor']) => color ?? 'primary';
|
||||
|
||||
const onCancel = () => cancel();
|
||||
const onEnter = () =>
|
||||
confirm('confirm' === state.current?.type ? selected.value : localInput.value);
|
||||
|
|
|
|||
|
|
@ -32,15 +32,24 @@
|
|||
</UDropdownMenu>
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="direction === 'desc' ? 'i-lucide-arrow-down-a-z' : 'i-lucide-arrow-up-a-z'"
|
||||
@click="toggleDirection"
|
||||
>
|
||||
<span>Sort</span>
|
||||
</UButton>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<UBadge color="neutral" variant="soft" size="sm">
|
||||
<span class="inline-flex items-center gap-1.5">
|
||||
<UIcon name="i-lucide-history" class="size-3.5" />
|
||||
<span>Total: {{ stateStore.count('history') }}</span>
|
||||
</span>
|
||||
</UBadge>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="direction === 'desc' ? 'i-lucide-arrow-down-a-z' : 'i-lucide-arrow-up-a-z'"
|
||||
@click="toggleDirection"
|
||||
>
|
||||
<span>Sort</span>
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
|
|
@ -599,15 +608,6 @@
|
|||
<li><code>source_name:task_name</code> - items added by the specified task.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@click="() => emitter('clear_search')"
|
||||
>
|
||||
Clear filter
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UAlert>
|
||||
|
|
|
|||
|
|
@ -202,6 +202,24 @@
|
|||
border-radius: 0.9rem;
|
||||
}
|
||||
|
||||
.docs-markdown img.docs-markdown__image--inline {
|
||||
display: inline-block;
|
||||
max-width: none;
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.docs-markdown img.docs-markdown__image--badge {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.docs-markdown img.docs-markdown__image--large {
|
||||
width: auto;
|
||||
min-width: min(100%, 24rem);
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.markdown-alert {
|
||||
--markdown-alert-accent: color-mix(in oklab, var(--ui-border-accented) 70%, transparent);
|
||||
margin-top: 1rem;
|
||||
|
|
@ -294,6 +312,18 @@ const content = ref('');
|
|||
const error = ref('');
|
||||
const isLoading = ref(true);
|
||||
|
||||
const isInlineMarkdownImage = (href: string): boolean => {
|
||||
return /(?:badge\.svg|shields\.io|ghcr-badge|\.svg(?:[?#].*)?$)/i.test(href);
|
||||
};
|
||||
|
||||
const getMarkdownImageClasses = (href: string): string[] => {
|
||||
if (isInlineMarkdownImage(href)) {
|
||||
return ['docs-markdown__image--inline', 'docs-markdown__image--badge'];
|
||||
}
|
||||
|
||||
return ['docs-markdown__image--large'];
|
||||
};
|
||||
|
||||
const createMarkdownParser = () => {
|
||||
const parser = new Marked();
|
||||
|
||||
|
|
@ -370,7 +400,9 @@ const createMarkdownParser = () => {
|
|||
const refPolicy = ' referrerpolicy="no-referrer"';
|
||||
const crossorigin = token._isExternalImage ? ' crossorigin="anonymous"' : '';
|
||||
const loading = ' loading="lazy"';
|
||||
return `<img src="${token.href || ''}"${alt}${title}${refPolicy}${crossorigin}${loading} />`;
|
||||
const src = token.href || '';
|
||||
const classes = getMarkdownImageClasses(src).join(' ');
|
||||
return `<img src="${src}" class="${classes}"${alt}${title}${refPolicy}${crossorigin}${loading} />`;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,6 +44,15 @@
|
|||
</UButton>
|
||||
</UDropdownMenu>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<UBadge color="neutral" variant="soft" size="sm">
|
||||
<span class="inline-flex items-center gap-1.5">
|
||||
<UIcon name="i-lucide-list-ordered" class="size-3.5" />
|
||||
<span>Total: {{ stateStore.count('queue') }}</span>
|
||||
</span>
|
||||
</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
@ -548,15 +557,6 @@
|
|||
<li><code>source_name:task_name</code> - items added by the specified task.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@click="() => emitter('clear_search')"
|
||||
>
|
||||
Clear filter
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UAlert>
|
||||
|
|
|
|||
|
|
@ -44,80 +44,74 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 xl:items-end">
|
||||
<div class="flex flex-wrap gap-2 xl:justify-end">
|
||||
<UButton
|
||||
color="neutral"
|
||||
:variant="show_filter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilter"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 xl:justify-end">
|
||||
<UButton
|
||||
color="neutral"
|
||||
:variant="show_filter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilter"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="controlEnabled"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-folder-plus"
|
||||
@click="() => void handleCreateDirectory()"
|
||||
>
|
||||
<span>New Folder</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="controlEnabled"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-folder-plus"
|
||||
@click="() => void handleCreateDirectory()"
|
||||
>
|
||||
<span>New Folder</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="display_style === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ display_style === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
|
||||
<UDropdownMenu v-if="hasItems" :items="sortGroups" :modal="false">
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="display_style === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
icon="i-lucide-arrow-up-down"
|
||||
trailing-icon="i-lucide-chevron-down"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ display_style === 'list' ? 'List' : 'Grid' }}</span>
|
||||
<span>Sort</span>
|
||||
</UButton>
|
||||
</UDropdownMenu>
|
||||
|
||||
<UDropdownMenu v-if="hasItems" :items="sortGroups" :modal="false">
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-arrow-up-down"
|
||||
trailing-icon="i-lucide-chevron-down"
|
||||
>
|
||||
<span>Sort</span>
|
||||
</UButton>
|
||||
</UDropdownMenu>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent(browserPath)"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent(browserPath)"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div v-if="show_filter" class="relative w-full xl:w-80">
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-toned"
|
||||
>
|
||||
<UIcon name="i-lucide-filter" class="size-4" />
|
||||
</span>
|
||||
<input
|
||||
id="search"
|
||||
ref="searchInput"
|
||||
v-model.lazy="localSearch"
|
||||
type="search"
|
||||
placeholder="Filter"
|
||||
class="w-full rounded-md border border-default bg-elevated py-2 pr-3 pl-9 text-sm text-default outline-none transition focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<UInput
|
||||
v-if="show_filter"
|
||||
id="search"
|
||||
ref="searchInput"
|
||||
v-model.lazy="localSearch"
|
||||
type="search"
|
||||
placeholder="Filter"
|
||||
icon="i-lucide-filter"
|
||||
size="sm"
|
||||
class="order-last w-full sm:order-first sm:w-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -458,10 +452,6 @@
|
|||
title="No results"
|
||||
:description="`No results found for '${localSearch}'.`"
|
||||
/>
|
||||
|
||||
<UButton color="neutral" variant="outline" size="sm" @click="clearFilter"
|
||||
>Clear filter</UButton
|
||||
>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
|
|
@ -554,7 +544,7 @@ const display_style = useStorage<string>('browser_display_style', 'list');
|
|||
const isMobile = useMediaQuery({ maxWidth: 639 });
|
||||
const show_filter = ref(false);
|
||||
const localSearch = ref('');
|
||||
const searchInput = ref<HTMLInputElement | null>(null);
|
||||
const searchInput = ref<{ inputRef?: { value?: HTMLInputElement | null } } | null>(null);
|
||||
|
||||
const items = browser.items;
|
||||
const browserPath = browser.path;
|
||||
|
|
@ -943,7 +933,7 @@ const toggleFilter = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
await nextTick();
|
||||
searchInput.value?.focus();
|
||||
searchInput.value?.inputRef?.value?.focus?.({ preventScroll: true });
|
||||
};
|
||||
|
||||
const toggleDisplayStyle = (): void => {
|
||||
|
|
|
|||
|
|
@ -21,43 +21,36 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 xl:items-end">
|
||||
<div class="flex flex-wrap gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="logs.length > 0"
|
||||
color="neutral"
|
||||
:variant="toggleFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilter = !toggleFilter"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="logs.length > 0"
|
||||
color="neutral"
|
||||
:variant="toggleFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilter = !toggleFilter"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
|
||||
<USwitch
|
||||
v-model="latestOnly"
|
||||
color="primary"
|
||||
size="sm"
|
||||
:label="latestOnly ? 'Latest Only' : 'All Loaded'"
|
||||
:ui="{ root: 'items-center gap-2', wrapper: 'ms-0 text-xs text-toned' }"
|
||||
/>
|
||||
</div>
|
||||
<USwitch
|
||||
v-model="latestOnly"
|
||||
color="primary"
|
||||
size="sm"
|
||||
:label="latestOnly ? 'Latest Only' : 'All Loaded'"
|
||||
:ui="{ root: 'items-center gap-2', wrapper: 'ms-0 text-xs text-toned' }"
|
||||
/>
|
||||
|
||||
<div v-if="toggleFilter && logs.length > 0" class="relative w-full xl:w-80">
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-toned"
|
||||
>
|
||||
<UIcon name="i-lucide-filter" class="size-4" />
|
||||
</span>
|
||||
|
||||
<input
|
||||
id="filter"
|
||||
v-model.lazy="query"
|
||||
type="search"
|
||||
placeholder="Filter changelog entries"
|
||||
class="w-full rounded-md border border-default bg-elevated py-2 pr-3 pl-9 text-sm text-default outline-none transition focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<UInput
|
||||
v-if="toggleFilter && logs.length > 0"
|
||||
id="filter"
|
||||
v-model.lazy="query"
|
||||
type="search"
|
||||
placeholder="Filter changelog entries"
|
||||
icon="i-lucide-filter"
|
||||
size="sm"
|
||||
class="order-last w-full sm:order-first sm:w-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -176,10 +169,6 @@
|
|||
:description="`No changelog entries found for the query: ${query}.`"
|
||||
/>
|
||||
|
||||
<UButton v-if="query" color="neutral" variant="outline" size="sm" @click="query = ''">
|
||||
Clear filter
|
||||
</UButton>
|
||||
|
||||
<UAlert
|
||||
v-else
|
||||
color="warning"
|
||||
|
|
|
|||
|
|
@ -21,70 +21,64 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 xl:items-end">
|
||||
<div class="flex flex-wrap gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="openCreate"
|
||||
>
|
||||
<span>New Condition</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="openCreate"
|
||||
>
|
||||
<span>New Condition</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="displayStyle === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ displayStyle === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="displayStyle === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ displayStyle === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
</div>
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
|
||||
<div v-if="showFilter && items.length > 0" class="relative w-full xl:w-80">
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-toned"
|
||||
>
|
||||
<UIcon name="i-lucide-filter" class="size-4" />
|
||||
</span>
|
||||
<input
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
class="w-full rounded-md border border-default bg-elevated py-2 pr-3 pl-9 text-sm text-default outline-none transition focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<UInput
|
||||
v-if="showFilter && items.length > 0"
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
icon="i-lucide-filter"
|
||||
size="sm"
|
||||
class="order-last w-full sm:order-first sm:w-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -432,10 +426,6 @@
|
|||
title="No Results"
|
||||
:description="`No results found for the query: ${query}. Please try a different search term.`"
|
||||
/>
|
||||
|
||||
<UButton color="neutral" variant="outline" size="sm" @click="query = ''">
|
||||
Clear filter
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
|
|
@ -479,8 +469,8 @@
|
|||
<UModal
|
||||
v-if="editorOpen"
|
||||
:open="editorOpen"
|
||||
:title="modalTitle"
|
||||
:description="modalDescription"
|
||||
:title="itemRef ? `Edit - ${item.name || 'Condition'}` : 'Add new condition'"
|
||||
description="Run yt-dlp custom match filter on returned info. and apply options."
|
||||
:dismissible="!conditions.addInProgress.value"
|
||||
:ui="{ content: 'w-full sm:max-w-6xl', body: 'max-h-[85vh] overflow-y-auto p-4 sm:p-6' }"
|
||||
@update:open="handleEditorOpenChange"
|
||||
|
|
@ -534,7 +524,7 @@ const editorOpen = ref(false);
|
|||
const editorDirty = ref(false);
|
||||
const query = ref('');
|
||||
const showFilter = ref(false);
|
||||
const filterInput = ref<HTMLInputElement | null>(null);
|
||||
const filterInput = ref<{ inputRef?: { value?: HTMLInputElement | null } } | null>(null);
|
||||
const selectedIds = ref<number[]>([]);
|
||||
const massDelete = ref(false);
|
||||
|
||||
|
|
@ -576,12 +566,6 @@ const bulkActionGroups = computed<DropdownMenuItem[][]>(() => [
|
|||
],
|
||||
]);
|
||||
|
||||
const modalTitle = computed(() =>
|
||||
itemRef.value ? `Edit - ${item.value.name}` : 'Add new condition',
|
||||
);
|
||||
const modalDescription = computed(
|
||||
() => 'Run yt-dlp custom match filter on returned info. and apply options.',
|
||||
);
|
||||
const modalKey = computed(
|
||||
() => `${itemRef.value ?? 'new'}-${editorOpen.value ? 'open' : 'closed'}`,
|
||||
);
|
||||
|
|
@ -639,7 +623,7 @@ const toggleFilterPanel = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
await nextTick();
|
||||
filterInput.value?.focus();
|
||||
filterInput.value?.inputRef?.value?.focus?.({ preventScroll: true });
|
||||
};
|
||||
|
||||
const loadContent = async (pageNumber = 1): Promise<void> => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<main class="w-full min-w-0 max-w-full space-y-6">
|
||||
<main class="flex min-h-0 w-full min-w-0 max-w-full flex-1 flex-col gap-6">
|
||||
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
|
||||
<div class="flex min-w-0 items-start gap-3">
|
||||
<span
|
||||
|
|
@ -17,37 +17,7 @@
|
|||
<span>{{ pageShell.pageLabel }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<UBadge :color="sessionStatusColor" variant="soft" size="sm">
|
||||
<span class="inline-flex items-center gap-1.5">
|
||||
<UIcon
|
||||
:name="sessionStatusIcon"
|
||||
class="size-3.5"
|
||||
:class="sessionStatusSpinning ? 'animate-spin' : ''"
|
||||
/>
|
||||
<span>{{ sessionStatusLabel }}</span>
|
||||
</span>
|
||||
</UBadge>
|
||||
|
||||
<UBadge v-if="hasActiveSession" color="neutral" variant="outline" size="sm">
|
||||
Session {{ shortSessionId }}
|
||||
</UBadge>
|
||||
|
||||
<UBadge
|
||||
v-if="sessionExitCode !== null && !isLoading"
|
||||
:color="exitCodeBadgeColor"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
Exit {{ sessionExitCode }}
|
||||
</UBadge>
|
||||
|
||||
<UBadge v-if="commandHistory.length > 0" color="neutral" variant="outline" size="sm">
|
||||
{{ commandHistory.length }} saved
|
||||
</UBadge>
|
||||
</div>
|
||||
|
||||
<p class="max-w-3xl text-sm text-toned">{{ sessionStatusDescription }}</p>
|
||||
<p class="max-w-3xl text-sm text-toned">{{ pageShell.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -75,13 +45,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<UPageCard variant="naked" :ui="pageCardUi">
|
||||
<UPageCard variant="naked" :ui="pageCardUi" class="flex min-h-0 flex-1">
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<div class="overflow-hidden rounded-xl border border-default bg-neutral-950/95 shadow-sm">
|
||||
<div class="flex min-h-0 flex-1 flex-col gap-4">
|
||||
<div
|
||||
class="flex min-h-72 min-w-0 flex-1 overflow-hidden rounded-sm border border-default bg-neutral-950/95 shadow-sm"
|
||||
>
|
||||
<div
|
||||
ref="terminal_window"
|
||||
class="terminal-host min-h-[55vh] max-h-[55vh] overflow-hidden"
|
||||
class="terminal-host h-full min-h-0 w-full overflow-hidden"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -92,14 +64,25 @@
|
|||
<div class="min-w-0 flex-1 space-y-1">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div
|
||||
class="flex min-w-0 items-center gap-2 text-sm font-semibold text-highlighted"
|
||||
class="flex min-w-0 flex-wrap items-center gap-2 text-sm font-semibold text-highlighted"
|
||||
>
|
||||
<UIcon name="i-lucide-send" class="size-4 shrink-0 text-toned" />
|
||||
<span>Command</span>
|
||||
|
||||
<UBadge :color="sessionStatusColor" variant="soft" size="sm">
|
||||
<span class="inline-flex items-center gap-1.5">
|
||||
<UIcon
|
||||
:name="sessionStatusIcon"
|
||||
class="size-3.5"
|
||||
:class="sessionStatusSpinning ? 'animate-spin' : ''"
|
||||
/>
|
||||
<span>{{ sessionStatusLabel }}</span>
|
||||
</span>
|
||||
</UBadge>
|
||||
</div>
|
||||
|
||||
<UBadge :color="inputModeColor" variant="soft" size="sm">
|
||||
{{ inputModeLabel }}
|
||||
<UBadge :color="isMultiLineInput ? 'info' : 'neutral'" variant="soft" size="sm">
|
||||
{{ isMultiLineInput ? 'Multi-line' : 'Single-line' }}
|
||||
</UBadge>
|
||||
</div>
|
||||
|
||||
|
|
@ -120,37 +103,13 @@
|
|||
:description="sessionError"
|
||||
/>
|
||||
|
||||
<UAlert
|
||||
v-if="hasYtDlpPrefix"
|
||||
color="warning"
|
||||
variant="soft"
|
||||
icon="i-lucide-triangle-alert"
|
||||
title="Remove the yt-dlp prefix"
|
||||
>
|
||||
<template #description>
|
||||
<p class="text-sm text-default">
|
||||
Enter only the URLs and flags. This page already runs commands as
|
||||
<code>yt-dlp <your command></code>.
|
||||
</p>
|
||||
</template>
|
||||
</UAlert>
|
||||
|
||||
<UAlert
|
||||
v-if="sessionStatus === 'reconnecting'"
|
||||
color="warning"
|
||||
variant="soft"
|
||||
icon="i-lucide-rotate-cw"
|
||||
title="Reconnecting to the live stream"
|
||||
description="The command is still tracked in the background and the page is trying to reattach automatically."
|
||||
/>
|
||||
|
||||
<UAlert
|
||||
v-if="showExpiredAlert"
|
||||
color="warning"
|
||||
variant="soft"
|
||||
icon="i-lucide-clock-3"
|
||||
title="Session expired"
|
||||
description="The saved transcript has aged out, but the last command was restored so you can run it again."
|
||||
title="Reconnecting to the command stream"
|
||||
description="The connection was lost. Attempting to reconnect and restore the stream."
|
||||
/>
|
||||
|
||||
<UAlert
|
||||
|
|
@ -159,16 +118,7 @@
|
|||
variant="soft"
|
||||
icon="i-lucide-circle-off"
|
||||
title="Session interrupted"
|
||||
description="The last command did not finish cleanly. Inspect the transcript above or rerun the command below."
|
||||
/>
|
||||
|
||||
<UAlert
|
||||
v-if="showNonZeroExitAlert"
|
||||
color="warning"
|
||||
variant="soft"
|
||||
icon="i-lucide-badge-alert"
|
||||
:title="`Command exited with code ${sessionExitCode}`"
|
||||
description="The transcript is preserved so you can review the output or run the command again."
|
||||
description="The command execution was interrupted."
|
||||
/>
|
||||
|
||||
<div class="grid gap-3 xl:grid-cols-[minmax(0,1fr)_auto] xl:items-end">
|
||||
|
|
@ -180,8 +130,8 @@
|
|||
class="console-input"
|
||||
:options="ytDlpOptions"
|
||||
:disabled="isStartBlocked"
|
||||
:icon="commandInputIcon"
|
||||
:icon-class="commandInputIconClass"
|
||||
:icon="isLoading ? 'i-lucide-loader-circle' : 'i-lucide-terminal'"
|
||||
:icon-class="isLoading ? 'animate-spin' : ''"
|
||||
placeholder="--help"
|
||||
:rows="5"
|
||||
@keydown="handleKeyDown"
|
||||
|
|
@ -194,8 +144,8 @@
|
|||
class="console-input"
|
||||
:options="ytDlpOptions"
|
||||
:disabled="isStartBlocked"
|
||||
:icon="commandInputIcon"
|
||||
:icon-class="commandInputIconClass"
|
||||
:icon="isLoading ? 'i-lucide-loader-circle' : 'i-lucide-terminal'"
|
||||
:icon-class="isLoading ? 'animate-spin' : ''"
|
||||
placeholder="--help"
|
||||
:multiple="true"
|
||||
:allowShortFlags="true"
|
||||
|
|
@ -348,11 +298,19 @@
|
|||
}
|
||||
|
||||
.terminal-host :deep(.xterm) {
|
||||
height: 100%;
|
||||
padding: 0.75rem !important;
|
||||
}
|
||||
|
||||
.terminal-host :deep(.xterm-viewport) {
|
||||
-ms-overflow-style: none;
|
||||
background-color: transparent !important;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.terminal-host :deep(.xterm-viewport::-webkit-scrollbar) {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -386,8 +344,8 @@ const dialog = useDialog();
|
|||
const consoleSession = useConsoleSession();
|
||||
const pageShell = requirePageShell('console');
|
||||
|
||||
const terminal = ref<Terminal | null>(null);
|
||||
const terminalFit = ref<FitAddon | null>(null);
|
||||
const terminal = shallowRef<Terminal | null>(null);
|
||||
const terminalFit = shallowRef<FitAddon | null>(null);
|
||||
const command = ref('');
|
||||
const manualReconnectPending = ref(false);
|
||||
const cancelPending = ref(false);
|
||||
|
|
@ -398,10 +356,10 @@ const storedCommand = useStorage<string>('console_command', '');
|
|||
const commandHistory = useStorage<string[]>('console_command_history', []);
|
||||
|
||||
const pageCardUi = {
|
||||
root: 'w-full bg-transparent',
|
||||
container: 'w-full p-0 sm:p-0',
|
||||
wrapper: 'w-full items-stretch',
|
||||
body: 'w-full',
|
||||
root: 'flex min-h-0 flex-1 w-full bg-transparent',
|
||||
container: 'flex min-h-0 flex-1 w-full p-0 sm:p-0',
|
||||
wrapper: 'flex min-h-0 flex-1 w-full items-stretch',
|
||||
body: 'flex min-h-0 flex-1 w-full flex-col',
|
||||
};
|
||||
|
||||
const historyCardUi = {
|
||||
|
|
@ -419,11 +377,9 @@ const sessionStatus = computed(() => consoleSession.state.value.status);
|
|||
const sessionError = computed(() => consoleSession.state.value.error);
|
||||
const sessionExitCode = computed(() => consoleSession.state.value.exitCode);
|
||||
const hasActiveSession = computed(() => Boolean(consoleSession.state.value.sessionId));
|
||||
const shortSessionId = computed(() => consoleSession.state.value.sessionId?.slice(0, 8) ?? '');
|
||||
const displayCommand = computed(() => command.value.trim().replace(/^yt-dlp\b\s*/i, ''));
|
||||
const runnableCommand = computed(() => displayCommand.value.replace(/\n/g, ' ').trim());
|
||||
const hasValidCommand = computed(() => Boolean(runnableCommand.value));
|
||||
const hasYtDlpPrefix = computed(() => /^yt-dlp\b/i.test(command.value.trim()));
|
||||
const isLoading = computed(() => consoleSession.isLoading.value);
|
||||
const isMultiLineInput = computed(() => Boolean(command.value && command.value.includes('\n')));
|
||||
const historyEntries = computed(() => commandHistory.value);
|
||||
|
|
@ -433,22 +389,6 @@ const showCancelButton = computed(() =>
|
|||
);
|
||||
const isStartBlocked = computed(() => isLoading.value);
|
||||
const canStartCommand = computed(() => !isStartBlocked.value && hasValidCommand.value);
|
||||
const showExpiredAlert = computed(
|
||||
() => sessionStatus.value === 'expired' && Boolean(consoleSession.state.value.command),
|
||||
);
|
||||
const showNonZeroExitAlert = computed(
|
||||
() =>
|
||||
!isLoading.value && typeof sessionExitCode.value === 'number' && sessionExitCode.value !== 0,
|
||||
);
|
||||
const inputModeLabel = computed(() => (isMultiLineInput.value ? 'Multi-line' : 'Single-line'));
|
||||
const inputModeColor = computed(() => (isMultiLineInput.value ? 'info' : 'neutral'));
|
||||
const commandInputIcon = computed(() =>
|
||||
isLoading.value ? 'i-lucide-loader-circle' : 'i-lucide-terminal',
|
||||
);
|
||||
const commandInputIconClass = computed(() => (isLoading.value ? 'animate-spin' : ''));
|
||||
const exitCodeBadgeColor = computed(() => {
|
||||
return sessionExitCode.value === 0 ? 'success' : 'error';
|
||||
});
|
||||
const runButtonLabel = computed(() => {
|
||||
if (runnableCommand.value === 'clear') {
|
||||
return 'Clear output';
|
||||
|
|
@ -483,7 +423,7 @@ const sessionStatusLabel = computed(() => {
|
|||
return 'Reconnecting';
|
||||
|
||||
case 'finished':
|
||||
return 'Finished';
|
||||
return sessionExitCode.value === 0 ? 'Finished' : 'Failed';
|
||||
|
||||
case 'interrupted':
|
||||
return 'Interrupted';
|
||||
|
|
@ -510,7 +450,7 @@ const sessionStatusColor = computed(() => {
|
|||
return 'warning';
|
||||
|
||||
case 'finished':
|
||||
return 'success';
|
||||
return sessionExitCode.value === 0 ? 'success' : 'error';
|
||||
|
||||
case 'error':
|
||||
return 'error';
|
||||
|
|
@ -527,7 +467,7 @@ const sessionStatusIcon = computed(() => {
|
|||
return 'i-lucide-loader-circle';
|
||||
|
||||
case 'finished':
|
||||
return 'i-lucide-circle-check';
|
||||
return sessionExitCode.value === 0 ? 'i-lucide-circle-check' : 'i-lucide-triangle-alert';
|
||||
|
||||
case 'interrupted':
|
||||
return 'i-lucide-circle-off';
|
||||
|
|
@ -543,38 +483,6 @@ const sessionStatusIcon = computed(() => {
|
|||
}
|
||||
});
|
||||
const sessionStatusSpinning = computed(() => ACTIVE_SESSION_STATUSES.includes(sessionStatus.value));
|
||||
const sessionStatusDescription = computed(() => {
|
||||
switch (sessionStatus.value) {
|
||||
case 'starting':
|
||||
case 'running':
|
||||
return 'The command keeps running even if you reload or navigate away.';
|
||||
|
||||
case 'reconnecting':
|
||||
return 'The command is still tracked in the background while the page reconnects to the stream.';
|
||||
|
||||
case 'finished':
|
||||
return typeof sessionExitCode.value === 'number' && sessionExitCode.value !== 0
|
||||
? `The last command finished with exit code ${sessionExitCode.value}.`
|
||||
: 'The last command completed and its transcript is still available.';
|
||||
|
||||
case 'interrupted':
|
||||
return 'The last command was interrupted before it completed.';
|
||||
|
||||
case 'expired':
|
||||
return 'The transcript expired, but you can rerun the restored command below.';
|
||||
|
||||
case 'error':
|
||||
return typeof sessionExitCode.value === 'number'
|
||||
? `The last command failed with exit code ${sessionExitCode.value}.`
|
||||
: hasActiveSession.value
|
||||
? 'The live stream ran into a problem while the page was attached to the session.'
|
||||
: 'The command request failed before a durable session could be restored.';
|
||||
|
||||
default:
|
||||
return 'Run yt-dlp commands directly in a non-interactive session.';
|
||||
}
|
||||
});
|
||||
|
||||
watch(command, (value) => {
|
||||
storedCommand.value = value;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,70 +21,64 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 xl:items-end">
|
||||
<div class="flex flex-wrap gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="openCreate"
|
||||
>
|
||||
<span>New Field</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="openCreate"
|
||||
>
|
||||
<span>New Field</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="displayStyle === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ displayStyle === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="displayStyle === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ displayStyle === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
</div>
|
||||
<UButton
|
||||
v-if="items.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
|
||||
<div v-if="showFilter && items.length > 0" class="relative w-full xl:w-80">
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-toned"
|
||||
>
|
||||
<UIcon name="i-lucide-filter" class="size-4" />
|
||||
</span>
|
||||
<input
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
class="w-full rounded-md border border-default bg-elevated py-2 pr-3 pl-9 text-sm text-default outline-none transition focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<UInput
|
||||
v-if="showFilter && items.length > 0"
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
icon="i-lucide-filter"
|
||||
size="sm"
|
||||
class="order-last w-full sm:order-first sm:w-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -384,10 +378,6 @@
|
|||
title="No Results"
|
||||
:description="`No results found for the query: ${query}. Please try a different search term.`"
|
||||
/>
|
||||
|
||||
<UButton color="neutral" variant="outline" size="sm" @click="query = ''">
|
||||
Clear filter
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
|
|
@ -402,8 +392,8 @@
|
|||
<UModal
|
||||
v-if="editorOpen"
|
||||
:open="editorOpen"
|
||||
:title="modalTitle"
|
||||
:description="modalDescription"
|
||||
:title="itemRef ? `Edit - ${item.name || 'Field'}` : 'Add new field'"
|
||||
description="Custom fields allow you to add new fields to the download form."
|
||||
:dismissible="!dlFields.addInProgress.value"
|
||||
:ui="{ content: 'w-full sm:max-w-5xl', body: 'max-h-[85vh] overflow-y-auto p-4 sm:p-6' }"
|
||||
@update:open="handleEditorOpenChange"
|
||||
|
|
@ -455,7 +445,7 @@ const editorOpen = ref(false);
|
|||
const editorDirty = ref(false);
|
||||
const query = ref('');
|
||||
const showFilter = ref(false);
|
||||
const filterInput = ref<HTMLInputElement | null>(null);
|
||||
const filterInput = ref<{ inputRef?: { value?: HTMLInputElement | null } } | null>(null);
|
||||
const selectedIds = ref<number[]>([]);
|
||||
const massDelete = ref(false);
|
||||
|
||||
|
|
@ -495,10 +485,6 @@ const bulkActionGroups = computed<DropdownMenuItem[][]>(() => [
|
|||
],
|
||||
]);
|
||||
|
||||
const modalTitle = computed(() => (itemRef.value ? `Edit - ${item.value.name}` : 'Add new field'));
|
||||
const modalDescription = computed(
|
||||
() => 'Custom fields allow you to add new fields to the download form.',
|
||||
);
|
||||
const modalKey = computed(
|
||||
() => `${itemRef.value ?? 'new'}-${editorOpen.value ? 'open' : 'closed'}`,
|
||||
);
|
||||
|
|
@ -556,7 +542,7 @@ const toggleFilterPanel = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
await nextTick();
|
||||
filterInput.value?.focus();
|
||||
filterInput.value?.inputRef?.value?.focus?.({ preventScroll: true });
|
||||
};
|
||||
|
||||
const loadContent = async (pageNumber = 1): Promise<void> => {
|
||||
|
|
|
|||
|
|
@ -21,61 +21,59 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 xl:items-end">
|
||||
<div class="flex flex-wrap gap-2 xl:justify-end">
|
||||
<UButton
|
||||
color="neutral"
|
||||
:variant="toggleFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilter = !toggleFilter"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 xl:justify-end">
|
||||
<UButton
|
||||
color="neutral"
|
||||
:variant="toggleFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilter = !toggleFilter"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="false === config.paused"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-pause"
|
||||
@click="() => void pauseDownload()"
|
||||
>
|
||||
<span>Pause</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="false === config.paused"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-pause"
|
||||
@click="() => void pauseDownload()"
|
||||
>
|
||||
<span>Pause</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-else
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-play"
|
||||
@click="() => void resumeDownload()"
|
||||
>
|
||||
<span>Resume</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-play"
|
||||
@click="() => void resumeDownload()"
|
||||
>
|
||||
<span>Resume</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="config.showForm = !config.showForm"
|
||||
>
|
||||
<span>Add</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="config.showForm = !config.showForm"
|
||||
>
|
||||
<span>Add</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="display_style === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="changeDisplay"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ display_style === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
</div>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="display_style === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="changeDisplay"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ display_style === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
|
||||
<UInput
|
||||
v-if="toggleFilter"
|
||||
|
|
@ -85,7 +83,7 @@
|
|||
placeholder="Filter displayed content"
|
||||
icon="i-lucide-filter"
|
||||
size="sm"
|
||||
class="w-full xl:w-80"
|
||||
class="order-last w-full sm:order-first sm:w-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -123,17 +121,26 @@
|
|||
</section>
|
||||
|
||||
<section id="history" class="scroll-mt-24 space-y-4">
|
||||
<div class="flex flex-wrap items-start justify-between gap-3 border-b border-default pb-3">
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex items-center gap-2 text-base font-semibold text-highlighted">
|
||||
<UIcon :name="historyShell.icon" class="size-4 text-toned" />
|
||||
<h2>{{ historyShell.pageLabel }}</h2>
|
||||
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<span
|
||||
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
|
||||
>
|
||||
<UIcon :name="historyShell.icon" class="size-5" />
|
||||
</span>
|
||||
|
||||
<div class="min-w-0 space-y-2">
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
|
||||
>
|
||||
<span>{{ historyShell.sectionLabel }}</span>
|
||||
<span>/</span>
|
||||
<span>{{ historyShell.pageLabel }}</span>
|
||||
</div>
|
||||
|
||||
<p class="max-w-3xl text-sm text-toned">{{ historyShell.description }}</p>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-toned">{{ historyShell.description }}</p>
|
||||
</div>
|
||||
|
||||
<UBadge color="neutral" variant="soft" size="sm">{{ historyCount }}</UBadge>
|
||||
</div>
|
||||
|
||||
<History
|
||||
|
|
@ -205,9 +212,8 @@ onMounted(async () => {
|
|||
});
|
||||
|
||||
const queueCount = computed(() => stateStore.count('queue'));
|
||||
const historyCount = computed(() => stateStore.count('history'));
|
||||
const hasDownloadsContent = computed(
|
||||
() => queueCount.value > 0 || historyCount.value > 0 || query.value.trim().length > 0,
|
||||
() => queueCount.value > 0 || stateStore.count('history') > 0 || query.value.trim().length > 0,
|
||||
);
|
||||
|
||||
watch(toggleFilter, () => {
|
||||
|
|
|
|||
|
|
@ -25,104 +25,61 @@
|
|||
<span>{{ pageShell.pageLabel }}</span>
|
||||
</div>
|
||||
|
||||
<UBadge v-if="loading" color="info" variant="soft" size="sm">Loading history</UBadge>
|
||||
|
||||
<UBadge color="neutral" variant="soft" size="sm">
|
||||
{{ filteredLogs.length }} shown
|
||||
</UBadge>
|
||||
|
||||
<UBadge
|
||||
v-if="logs.length !== filteredLogs.length"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
{{ logs.length }} loaded
|
||||
</UBadge>
|
||||
|
||||
<UBadge v-if="hasActiveFilter" color="warning" variant="soft" size="sm">
|
||||
{{ matchCount }} matches
|
||||
</UBadge>
|
||||
|
||||
<UBadge v-if="reachedEnd && !hasActiveFilter" color="neutral" variant="soft" size="sm">
|
||||
Start of file loaded
|
||||
</UBadge>
|
||||
|
||||
<p class="max-w-3xl text-sm text-toned">{{ pageShell.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 xl:items-end">
|
||||
<div class="flex flex-wrap gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="!autoScroll"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-arrow-down"
|
||||
@click="scrollToBottom(false)"
|
||||
>
|
||||
Jump to Live Tail
|
||||
</UButton>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="!autoScroll"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-arrow-down"
|
||||
@click="scrollToBottom(false)"
|
||||
>
|
||||
Jump to Live Tail
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
:variant="toggleFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilter = !toggleFilter"
|
||||
>
|
||||
Filter
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
:variant="toggleFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilter = !toggleFilter"
|
||||
>
|
||||
Filter
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
:variant="textWrap ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-wrap-text"
|
||||
:aria-pressed="textWrap"
|
||||
:title="textWrap ? 'Wrap lines enabled' : 'Wrap lines disabled'"
|
||||
:class="[
|
||||
'transition-all',
|
||||
textWrap ? '-translate-y-px ring ring-default shadow-xs' : '',
|
||||
]"
|
||||
@click="textWrap = !textWrap"
|
||||
>
|
||||
Wrap lines
|
||||
</UButton>
|
||||
</div>
|
||||
<UButton
|
||||
color="neutral"
|
||||
:variant="textWrap ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-wrap-text"
|
||||
:aria-pressed="textWrap"
|
||||
:title="textWrap ? 'Wrap lines enabled' : 'Wrap lines disabled'"
|
||||
:class="['transition-all', textWrap ? '-translate-y-px ring ring-default shadow-xs' : '']"
|
||||
@click="textWrap = !textWrap"
|
||||
>
|
||||
Wrap lines
|
||||
</UButton>
|
||||
|
||||
<div v-if="toggleFilter || query" class="relative w-full xl:w-80">
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-toned"
|
||||
>
|
||||
<UIcon name="i-lucide-filter" class="size-4" />
|
||||
</span>
|
||||
|
||||
<input
|
||||
id="filter"
|
||||
v-model.lazy="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
class="w-full rounded-md border border-default bg-elevated py-2 pr-3 pl-9 text-sm text-default outline-none transition focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<UInput
|
||||
v-if="toggleFilter || query"
|
||||
id="filter"
|
||||
v-model.lazy="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
icon="i-lucide-filter"
|
||||
size="sm"
|
||||
class="order-last w-full sm:order-first sm:w-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UPageCard variant="naked" :ui="pageCardUi">
|
||||
<template #body>
|
||||
<div class="w-full min-w-0 max-w-full space-y-3">
|
||||
<div class="flex flex-wrap items-center justify-end gap-2 text-xs text-toned">
|
||||
<span v-if="searchTerm">
|
||||
Query: <code>{{ searchTerm }}</code>
|
||||
</span>
|
||||
|
||||
<span v-if="filterContext > 0">
|
||||
Context: <code>{{ filterContext }}</code>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="w-full min-w-0 max-w-full overflow-hidden">
|
||||
<div
|
||||
ref="logContainer"
|
||||
|
|
@ -164,24 +121,24 @@
|
|||
</article>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="rounded-xl border border-default bg-muted/20 px-4 py-6 text-center"
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm font-semibold text-highlighted">
|
||||
{{
|
||||
hasActiveFilter
|
||||
? 'No log lines match the current filter.'
|
||||
: 'No log lines are available yet.'
|
||||
}}
|
||||
</p>
|
||||
<div v-else class="space-y-3 font-sans text-sm leading-normal">
|
||||
<UAlert
|
||||
v-if="query"
|
||||
color="warning"
|
||||
variant="soft"
|
||||
icon="i-lucide-search"
|
||||
title="No Results"
|
||||
:description="`No log lines found for the query: ${query}. Please try a different search term.`"
|
||||
/>
|
||||
|
||||
<p v-if="hasActiveFilter" class="text-xs text-toned">
|
||||
Try a different term or clear <code>{{ query }}</code
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
<UAlert
|
||||
v-else
|
||||
color="warning"
|
||||
variant="soft"
|
||||
icon="i-lucide-circle-alert"
|
||||
title="No log lines"
|
||||
description="No log lines are available yet."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div ref="bottomMarker" />
|
||||
|
|
@ -320,8 +277,6 @@ const filteredLogs = computed<FilteredLogEntry[]>(() => {
|
|||
return result;
|
||||
});
|
||||
|
||||
const matchCount = computed(() => filteredLogs.value.filter((entry) => entry.isMatch).length);
|
||||
|
||||
const fetchLogs = async (): Promise<void> => {
|
||||
loading.value = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,83 +21,77 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 xl:items-end">
|
||||
<div class="flex flex-wrap gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="notifications.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="notifications.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="openCreate"
|
||||
>
|
||||
<span>New Notification</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="openCreate"
|
||||
>
|
||||
<span>New Notification</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="notifications.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-send"
|
||||
:loading="sendingTest"
|
||||
:disabled="sendingTest"
|
||||
@click="() => void sendTest()"
|
||||
>
|
||||
<span>Send Test</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="notifications.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-send"
|
||||
:loading="sendingTest"
|
||||
:disabled="sendingTest"
|
||||
@click="() => void sendTest()"
|
||||
>
|
||||
<span>Send Test</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="notifications.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="displayStyle === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ displayStyle === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="notifications.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="displayStyle === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ displayStyle === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="notifications.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
</div>
|
||||
<UButton
|
||||
v-if="notifications.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
|
||||
<div v-if="showFilter && notifications.length > 0" class="relative w-full xl:w-80">
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-toned"
|
||||
>
|
||||
<UIcon name="i-lucide-filter" class="size-4" />
|
||||
</span>
|
||||
<input
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
class="w-full rounded-md border border-default bg-elevated py-2 pr-3 pl-9 text-sm text-default outline-none transition focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<UInput
|
||||
v-if="showFilter && notifications.length > 0"
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
icon="i-lucide-filter"
|
||||
size="sm"
|
||||
class="order-last w-full sm:order-first sm:w-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -482,10 +476,6 @@
|
|||
title="No Results"
|
||||
:description="`No results found for the query: ${query}. Please try a different search term.`"
|
||||
/>
|
||||
|
||||
<UButton color="neutral" variant="outline" size="sm" @click="query = ''">
|
||||
Clear filter
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
|
|
@ -532,8 +522,8 @@
|
|||
<UModal
|
||||
v-if="editorOpen"
|
||||
:open="editorOpen"
|
||||
:title="modalTitle"
|
||||
:description="modalDescription"
|
||||
:title="targetRef ? `Edit - ${target.name}` : 'Add new notification target'"
|
||||
description="Send notifications to your webhooks based on specified events or presets."
|
||||
:dismissible="!addInProgress"
|
||||
:ui="{ content: 'w-full sm:max-w-5xl', body: 'max-h-[85vh] overflow-y-auto p-4 sm:p-6' }"
|
||||
@update:open="handleEditorOpenChange"
|
||||
|
|
@ -593,7 +583,7 @@ const editorDirty = ref(false);
|
|||
const sendingTest = ref(false);
|
||||
const query = ref('');
|
||||
const showFilter = ref(false);
|
||||
const filterInput = ref<HTMLInputElement | null>(null);
|
||||
const filterInput = ref<{ inputRef?: { value?: HTMLInputElement | null } } | null>(null);
|
||||
const selectedIds = ref<number[]>([]);
|
||||
const massDelete = ref(false);
|
||||
|
||||
|
|
@ -604,12 +594,6 @@ const contentStyle = computed<'list' | 'grid'>(() =>
|
|||
isMobile.value ? 'grid' : displayStyle.value,
|
||||
);
|
||||
|
||||
const modalTitle = computed(() =>
|
||||
targetRef.value ? `Edit - ${target.value.name}` : 'Add new notification target',
|
||||
);
|
||||
const modalDescription = computed(
|
||||
() => 'Send notifications to your webhooks based on specified events or presets.',
|
||||
);
|
||||
const modalKey = computed(
|
||||
() => `${targetRef.value ?? 'new'}-${editorOpen.value ? 'open' : 'closed'}`,
|
||||
);
|
||||
|
|
@ -699,7 +683,7 @@ const toggleFilterPanel = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
await nextTick();
|
||||
filterInput.value?.focus();
|
||||
filterInput.value?.inputRef?.value?.focus?.({ preventScroll: true });
|
||||
};
|
||||
|
||||
const loadContent = async (pageNumber = page.value): Promise<void> => {
|
||||
|
|
|
|||
|
|
@ -21,69 +21,63 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 xl:items-end">
|
||||
<div class="flex flex-wrap gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="presetsNoDefault.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="presetsNoDefault.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="editor.openCreate()"
|
||||
>
|
||||
<span>New Preset</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="editor.openCreate()"
|
||||
>
|
||||
<span>New Preset</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="display_style === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ display_style === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="display_style === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ display_style === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="presets.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
</div>
|
||||
<UButton
|
||||
v-if="presets.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
|
||||
<div v-if="showFilter && presetsNoDefault.length > 0" class="relative w-full xl:w-80">
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-toned"
|
||||
>
|
||||
<UIcon name="i-lucide-filter" class="size-4" />
|
||||
</span>
|
||||
<input
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
class="w-full rounded-md border border-default bg-elevated py-2 pr-3 pl-9 text-sm text-default outline-none transition focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<UInput
|
||||
v-if="showFilter && presetsNoDefault.length > 0"
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
icon="i-lucide-filter"
|
||||
size="sm"
|
||||
class="order-last w-full sm:order-first sm:w-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -413,10 +407,6 @@
|
|||
title="No Results"
|
||||
:description="`No results found for the query: ${query}. Please try a different search term.`"
|
||||
/>
|
||||
|
||||
<UButton color="neutral" variant="outline" size="sm" @click="query = ''"
|
||||
>Clear filter</UButton
|
||||
>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
|
|
@ -490,7 +480,7 @@ const isMobile = useMediaQuery({ maxWidth: 639 });
|
|||
|
||||
const query = ref('');
|
||||
const showFilter = ref(false);
|
||||
const filterInput = ref<HTMLInputElement | null>(null);
|
||||
const filterInput = ref<{ inputRef?: { value?: HTMLInputElement | null } } | null>(null);
|
||||
const selectedIds = ref<number[]>([]);
|
||||
const massDelete = ref(false);
|
||||
|
||||
|
|
@ -562,7 +552,7 @@ const toggleFilterPanel = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
await nextTick();
|
||||
filterInput.value?.focus();
|
||||
filterInput.value?.inputRef?.value?.focus?.({ preventScroll: true });
|
||||
};
|
||||
|
||||
const reloadContent = async (): Promise<void> => {
|
||||
|
|
|
|||
|
|
@ -21,78 +21,72 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 xl:items-end">
|
||||
<div class="flex flex-wrap gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="definitions.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="definitions.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-search"
|
||||
@click="inspect = true"
|
||||
>
|
||||
<span>Inspect</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-search"
|
||||
@click="inspect = true"
|
||||
>
|
||||
<span>Inspect</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="openCreate"
|
||||
>
|
||||
<span>New Definition</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="openCreate"
|
||||
>
|
||||
<span>New Definition</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="display_style === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ display_style === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="display_style === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ display_style === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
</div>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
|
||||
<div v-if="showFilter && definitions.length > 0" class="relative w-full xl:w-80">
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-toned"
|
||||
>
|
||||
<UIcon name="i-lucide-filter" class="size-4" />
|
||||
</span>
|
||||
<input
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
class="w-full rounded-md border border-default bg-elevated py-2 pr-3 pl-9 text-sm text-default outline-none transition focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<UInput
|
||||
v-if="showFilter && definitions.length > 0"
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
icon="i-lucide-filter"
|
||||
size="sm"
|
||||
class="order-last w-full sm:order-first sm:w-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -404,10 +398,6 @@
|
|||
title="No Results"
|
||||
:description="`No results found for the query: ${query}. Please try a different search term.`"
|
||||
/>
|
||||
|
||||
<UButton color="neutral" variant="outline" size="sm" @click="query = ''"
|
||||
>Clear filter</UButton
|
||||
>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
|
|
@ -422,8 +412,16 @@
|
|||
<UModal
|
||||
v-if="isEditorOpen"
|
||||
:open="isEditorOpen"
|
||||
:title="editorTitle"
|
||||
:description="editorDescription"
|
||||
:title="
|
||||
editorMode === 'create'
|
||||
? 'Create Task Definition'
|
||||
: `Edit - ${currentSummary?.name || 'Task Definition'}`
|
||||
"
|
||||
:description="
|
||||
editorLoading
|
||||
? 'Loading full definition before editing.'
|
||||
: 'Use the GUI editor when it fits, or switch to advanced JSON for full control.'
|
||||
"
|
||||
:dismissible="!editorLoading && !editorSubmitting"
|
||||
:ui="{ content: 'w-full sm:max-w-7xl', body: 'max-h-[85vh] overflow-y-auto p-4 sm:p-6' }"
|
||||
@update:open="handleEditorOpenChange"
|
||||
|
|
@ -528,7 +526,7 @@ const isMobile = useMediaQuery({ maxWidth: 639 });
|
|||
|
||||
const query = ref('');
|
||||
const showFilter = ref(false);
|
||||
const filterInput = ref<HTMLInputElement | null>(null);
|
||||
const filterInput = ref<{ inputRef?: { value?: HTMLInputElement | null } } | null>(null);
|
||||
const hideImportByDefault = ref(false);
|
||||
const selectedIds = ref<number[]>([]);
|
||||
const massDelete = ref(false);
|
||||
|
|
@ -590,20 +588,6 @@ const currentSummary = computed<TaskDefinitionSummary | undefined>(() => {
|
|||
return definitions.value.find((item) => item.id === workingId.value);
|
||||
});
|
||||
|
||||
const editorTitle = computed(() => {
|
||||
return editorMode.value === 'create'
|
||||
? 'Create Task Definition'
|
||||
: `Edit - ${currentSummary.value?.name || 'Task Definition'}`;
|
||||
});
|
||||
|
||||
const editorDescription = computed(() => {
|
||||
if (editorLoading.value) {
|
||||
return 'Loading full definition before editing.';
|
||||
}
|
||||
|
||||
return 'Use the GUI editor when it fits, or switch to advanced JSON for full control.';
|
||||
});
|
||||
|
||||
const showImportByDefault = computed(
|
||||
() => editorMode.value === 'create' && !hideImportByDefault.value,
|
||||
);
|
||||
|
|
@ -655,7 +639,7 @@ const toggleFilterPanel = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
await nextTick();
|
||||
filterInput.value?.focus();
|
||||
filterInput.value?.inputRef?.value?.focus?.({ preventScroll: true });
|
||||
};
|
||||
|
||||
const reloadContent = async (): Promise<void> => {
|
||||
|
|
|
|||
|
|
@ -21,69 +21,63 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 xl:items-end">
|
||||
<div class="flex flex-wrap gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="tasks.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2 xl:justify-end">
|
||||
<UButton
|
||||
v-if="tasks.length > 0"
|
||||
color="neutral"
|
||||
:variant="showFilter ? 'soft' : 'outline'"
|
||||
size="sm"
|
||||
icon="i-lucide-filter"
|
||||
@click="toggleFilterPanel"
|
||||
>
|
||||
<span>Filter</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="openCreateForm"
|
||||
>
|
||||
<span>New Task</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-plus"
|
||||
@click="openCreateForm"
|
||||
>
|
||||
<span>New Task</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="displayStyle === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ displayStyle === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:icon="displayStyle === 'list' ? 'i-lucide-list' : 'i-lucide-grid-2x2'"
|
||||
class="hidden sm:inline-flex"
|
||||
@click="toggleDisplayStyle"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ displayStyle === 'list' ? 'List' : 'Grid' }}</span>
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="tasks.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
</div>
|
||||
<UButton
|
||||
v-if="tasks.length > 0"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-refresh-cw"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="() => void reloadContent()"
|
||||
>
|
||||
<span>Reload</span>
|
||||
</UButton>
|
||||
|
||||
<div v-if="showFilter && tasks.length > 0" class="relative w-full xl:w-80">
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-toned"
|
||||
>
|
||||
<UIcon name="i-lucide-filter" class="size-4" />
|
||||
</span>
|
||||
<input
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
class="w-full rounded-md border border-default bg-elevated py-2 pr-3 pl-9 text-sm text-default outline-none transition focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<UInput
|
||||
v-if="showFilter && tasks.length > 0"
|
||||
id="filter"
|
||||
ref="filterInput"
|
||||
v-model="query"
|
||||
type="search"
|
||||
placeholder="Filter displayed content"
|
||||
icon="i-lucide-filter"
|
||||
size="sm"
|
||||
class="order-last w-full sm:order-first sm:w-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -623,10 +617,6 @@
|
|||
title="No Results"
|
||||
:description="`No results found for the query: ${query}. Please try a different search term.`"
|
||||
/>
|
||||
|
||||
<UButton color="neutral" variant="outline" size="sm" @click="query = ''">
|
||||
Clear filter
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
|
|
@ -672,8 +662,12 @@
|
|||
<UModal
|
||||
v-if="toggleForm"
|
||||
:open="toggleForm"
|
||||
:title="editorTitle"
|
||||
:description="editorDescription"
|
||||
:title="taskRef ? `Edit - ${task.name || 'Task'}` : 'Add new task'"
|
||||
:description="
|
||||
taskRef
|
||||
? 'Update the settings of your automated download task'
|
||||
: 'Create an automated download task'
|
||||
"
|
||||
:dismissible="!addInProgress"
|
||||
:ui="{ content: 'w-full sm:max-w-7xl', body: 'max-h-[85vh] overflow-y-auto p-4 sm:p-6' }"
|
||||
@update:open="handleEditorOpenChange"
|
||||
|
|
@ -767,7 +761,7 @@ const massDelete = ref(false);
|
|||
const inspectTask = ref<Task | null>(null);
|
||||
const query = ref('');
|
||||
const showFilter = ref(false);
|
||||
const filterInput = ref<HTMLInputElement | null>(null);
|
||||
const filterInput = ref<{ inputRef?: { value?: HTMLInputElement | null } } | null>(null);
|
||||
const CACHE_KEY = 'tasks:handler_support';
|
||||
const taskHandlerSupport = ref<Record<string, boolean>>(sessionCache.get(CACHE_KEY) || {});
|
||||
|
||||
|
|
@ -780,16 +774,6 @@ const contentStyle = computed<'list' | 'grid'>(() =>
|
|||
|
||||
const editorSessionId = ref(0);
|
||||
|
||||
const editorTitle = computed(() => {
|
||||
return taskRef.value ? `Edit - ${task.value.name}` : 'Add new task';
|
||||
});
|
||||
|
||||
const editorDescription = computed(() => {
|
||||
return taskRef.value
|
||||
? 'Update the settings of your automated download task'
|
||||
: 'Create an automated download task';
|
||||
});
|
||||
|
||||
const formKey = computed(() => `${taskRef.value ?? 'new'}:${editorSessionId.value}`);
|
||||
|
||||
const discardEditor = (): void => {
|
||||
|
|
@ -885,7 +869,7 @@ const toggleFilterPanel = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
await nextTick();
|
||||
filterInput.value?.focus();
|
||||
filterInput.value?.inputRef?.value?.focus?.({ preventScroll: true });
|
||||
};
|
||||
|
||||
const toggleMasterSelection = (): void => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue