diff --git a/ui/app/components/History.vue b/ui/app/components/History.vue deleted file mode 100644 index 9c885c92..00000000 --- a/ui/app/components/History.vue +++ /dev/null @@ -1,1462 +0,0 @@ - - - diff --git a/ui/app/components/LateLoader.vue b/ui/app/components/LateLoader.vue index dc982a34..d7df1388 100644 --- a/ui/app/components/LateLoader.vue +++ b/ui/app/components/LateLoader.vue @@ -14,6 +14,9 @@ const props = defineProps<{ unrenderDelay?: number; }>(); +const ROOT_MARGIN = 600; +const nuxtApp = useNuxtApp(); + const shouldRender = ref(false); const targetEl = ref(null); const fixedMinHeight = ref(0); @@ -21,12 +24,35 @@ const fixedMinHeight = ref(0); let unrenderTimer: ReturnType | null = null; let renderTimer: ReturnType | null = null; -function onIdle(cb: () => void): void { - if ('requestIdleCallback' in window) { - (window as any).requestIdleCallback(cb); - } else { - setTimeout(() => nextTick(cb), 300); +function ensureRenderedIfNearViewport(): void { + if (!targetEl.value) { + return; } + + const rect = targetEl.value.getBoundingClientRect(); + const viewportHeight = window.innerHeight || document.documentElement.clientHeight; + const viewportWidth = window.innerWidth || document.documentElement.clientWidth; + + if ( + rect.bottom < -ROOT_MARGIN || + rect.top > viewportHeight + ROOT_MARGIN || + rect.right < 0 || + rect.left > viewportWidth + ) { + return; + } + + if (unrenderTimer) { + clearTimeout(unrenderTimer); + unrenderTimer = null; + } + + if (renderTimer) { + clearTimeout(renderTimer); + renderTimer = null; + } + + shouldRender.value = true; } const { stop } = useIntersectionObserver( @@ -43,7 +69,7 @@ const { stop } = useIntersectionObserver( props.unrender ? 200 : 0, ); - shouldRender.value = true; + ensureRenderedIfNearViewport(); if (!props.unrender) { stop(); @@ -61,19 +87,55 @@ const { stop } = useIntersectionObserver( }, props.unrenderDelay ?? 6000); } }, - { rootMargin: '600px' }, + { rootMargin: `${ROOT_MARGIN}px` }, ); -if (props.renderOnIdle) { - onIdle(() => { - shouldRender.value = true; - if (!props.unrender) { - stop(); - } +const removePageHooks = [ + nuxtApp.hook('page:finish', () => { + requestAnimationFrame(() => { + ensureRenderedIfNearViewport(); + }); + }), + nuxtApp.hook('page:transition:finish', () => { + requestAnimationFrame(() => { + ensureRenderedIfNearViewport(); + }); + }), +]; + +onMounted(() => { + window.addEventListener('resize', ensureRenderedIfNearViewport, { passive: true }); + requestAnimationFrame(() => { + ensureRenderedIfNearViewport(); }); +}); + +if (props.renderOnIdle) { + if ('requestIdleCallback' in window) { + (window as any).requestIdleCallback(() => { + shouldRender.value = true; + if (!props.unrender) { + stop(); + } + }); + } else { + setTimeout( + () => + nextTick(() => { + shouldRender.value = true; + if (!props.unrender) { + stop(); + } + }), + 300, + ); + } } onBeforeUnmount(() => { + window.removeEventListener('resize', ensureRenderedIfNearViewport); + removePageHooks.forEach((removeHook) => removeHook()); + if (renderTimer) { clearTimeout(renderTimer); } diff --git a/ui/app/components/Pager.vue b/ui/app/components/Pager.vue deleted file mode 100644 index c269cdee..00000000 --- a/ui/app/components/Pager.vue +++ /dev/null @@ -1,115 +0,0 @@ - - - diff --git a/ui/app/components/Queue.vue b/ui/app/components/Queue.vue deleted file mode 100644 index c2449125..00000000 --- a/ui/app/components/Queue.vue +++ /dev/null @@ -1,1167 +0,0 @@ - - - - - diff --git a/ui/app/components/Simple.vue b/ui/app/components/Simple.vue index cf727aef..40b7b521 100644 --- a/ui/app/components/Simple.vue +++ b/ui/app/components/Simple.vue @@ -22,7 +22,7 @@ -
+
+ > + Reload Queue + - - + + :icon="colorModeButtonIcon" + :square="isMobile" + :aria-label="colorModeButtonTitle" + :title="colorModeButtonTitle" + @click="colorMode.preference = nextColorModePreference" + > + {{ colorModeButtonTitle }} + @@ -51,23 +59,16 @@ variant="ghost" size="sm" icon="i-lucide-settings-2" - square + :square="isMobile" @click="$emit('show_settings')" - /> + > + WebUI Settings +
- - -
+
@@ -89,7 +90,7 @@ size="lg" required :disabled="isFormDisabled" - class="w-full" + class="min-w-0 flex-1" :ui="urlInputUi" /> @@ -100,7 +101,7 @@ icon="i-lucide-plus" :loading="addInProgress" :disabled="isFormDisabled || !formUrl.trim()" - class="justify-center sm:min-w-28" + class="shrink-0 justify-center min-w-20 sm:min-w-28" > Add @@ -166,233 +167,447 @@
-
-
-
-
- - Queue and history -
-
- -
- Queue {{ queueItems.length }} - History {{ historyEntries.length }} +
+
+ + + {{ queueItems.length }} items
-
- - - - -
- - -
-
- - +
- {{ entry.item.title || 'Untitled' }} - - +
+
+ + {{ updateProgress(item) }} +
+
+ -
- - {{ getSourceLabel(entry) }} - +
+
+ + + + - - {{ getStatusLabel(entry.item) }} - + - + + {{ getDurationLabel(item) }} + +
+ +
+
+ + + {{ item.title || 'Untitled' }} + + + +
+ + {{ downloadingStatuses.has(item.status) ? 'Active' : 'Queued' }} + + + + {{ getStatusLabel(item) }} + + + +
+
+ +

+ + +

+ +
+ + Start + + + + Pause + + + + {{ item.is_live ? 'Stop' : 'Cancel' }} + +
+
-
+ + -

- - -

+ +
+ +
-
- + + {{ getDurationLabel(item) }} + + - -
+ + {{ getStatusLabel(item) }} + + + +
+ + +

+ + +

+ +
+ + Download + + + + Requeue + + + + Delete + +
+ + + + + + + + + +
+
- - - -
-
- - Loading more items... -
-
+ + diff --git a/ui/app/spa-loading-template.html b/ui/app/spa-loading-template.html index 572e25d4..c74f7b2d 100644 --- a/ui/app/spa-loading-template.html +++ b/ui/app/spa-loading-template.html @@ -1,138 +1,206 @@ - + - YTPTube Loading... + + Loading YTPTube - +
+
+ + +
+

YTPTube

+

Loading your dashboard

+
+
+ +

Getting the interface ready. Please wait a moment...

+ + +

Please wait...

+
diff --git a/ui/app/utils/topLevelNavigation.ts b/ui/app/utils/topLevelNavigation.ts index 11e692d4..03c1b212 100644 --- a/ui/app/utils/topLevelNavigation.ts +++ b/ui/app/utils/topLevelNavigation.ts @@ -20,7 +20,6 @@ type NavDefinition = { matchPath?: string; sidebarVisible?: boolean; searchable?: boolean; - activeMode?: 'path' | 'downloads' | 'history'; navbarTitle?: string; requires?: 'file_logging' | 'console_enabled'; }; @@ -70,7 +69,6 @@ const NavItems: Array = [ icon: 'i-lucide-download', to: '/', matchPath: '/', - activeMode: 'downloads', }, { id: 'history', @@ -81,9 +79,8 @@ const NavItems: Array = [ breadcrumbSectionLabel: 'Workspace', description: 'Completed, skipped, and failed downloads.', icon: 'i-lucide-history', - to: '/#history', - matchPath: '/', - activeMode: 'history', + to: '/history', + matchPath: '/history', navbarTitle: 'Downloads', }, { @@ -269,24 +266,14 @@ export const getNavItemById = (id: string): NavItem | undefined => { }; export const isNavItemActive = (entry: NavItem, route: LocationPath): boolean => { - switch (entry.activeMode) { - case 'downloads': - return route.path === '/' && route.hash !== '#history'; + const current = normalizePath(route.path); + const target = normalizePath(entry.matchPath); - case 'history': - return route.path === '/' && route.hash === '#history'; - - default: { - const current = normalizePath(route.path); - const target = normalizePath(entry.matchPath); - - if (target === '/') { - return current === '/'; - } - - return current === target || current.startsWith(`${target}/`); - } + if (target === '/') { + return current === '/'; } + + return current === target || current.startsWith(`${target}/`); }; export const getActiveNavItem = (