minor design update

This commit is contained in:
arabcoders 2025-06-17 17:24:52 +03:00
parent 249bb43c15
commit 8694e82965
12 changed files with 122 additions and 88 deletions

View file

@ -76,6 +76,7 @@
"socketio",
"timespec",
"tmpfilename",
"ungroup",
"upgrader",
"urandom",
"urlsafe",

View file

@ -360,7 +360,7 @@ class DownloadQueue(metaclass=Singleton):
starts_in.replace(tzinfo=UTC) if starts_in.tzinfo is None else starts_in.astimezone(UTC)
)
starts_in = starts_in + timedelta(minutes=5, seconds=dl.extras.get("duration", 0))
dlInfo.info.error += f" Download will start at {starts_in.isoformat()}."
dlInfo.info.error += f" Download will start at {starts_in.astimezone().isoformat()}."
_requeue = False
except Exception as e:
LOG.error(f"Failed to parse live_in date '{release_in}'. {e!s}")
@ -917,7 +917,7 @@ class DownloadQueue(metaclass=Singleton):
premiere_ends: datetime = starts_in + timedelta(minutes=5, seconds=duration)
if time_now < premiere_ends:
LOG.debug(
f"Item '{item_ref}' is premiering, download will start in '{(starts_in.astimezone() + timedelta(minutes=5, seconds=duration)).isoformat()}'"
f"Item '{item_ref}' is premiering, download will start in '{(starts_in + timedelta(minutes=5, seconds=duration)).astimezone().isoformat()}'"
)
continue

View file

@ -44,7 +44,6 @@ class HttpAPI:
"config": self.config,
"notify": self._notify,
"cache": self.cache,
"app": self.app,
"http_api": self,
"root_path": self.rootPath,
}.items()

View file

@ -1,9 +1,11 @@
import inspect
import logging
from typing import Any, TypeVar
from app.library.Singleton import Singleton
T = TypeVar("T")
LOG: logging.Logger = logging.getLogger(__name__)
class Services(metaclass=Singleton):
@ -50,6 +52,10 @@ class Services(metaclass=Singleton):
sig = inspect.signature(handler)
expected_args = sig.parameters.keys()
filtered = {k: v for k, v in context.items() if k in expected_args}
if missing_args := expected_args - filtered.keys():
LOG.error(f"Missing arguments for handler '{handler.__name__}': {missing_args}")
return await handler(**filtered)
def handle_sync(self, handler: callable, **kwargs) -> Any:
@ -58,4 +64,8 @@ class Services(metaclass=Singleton):
sig = inspect.signature(handler)
expected_args = sig.parameters.keys()
filtered = {k: v for k, v in context.items() if k in expected_args}
if missing_args := expected_args - filtered.keys():
LOG.error(f"Missing arguments for handler '{handler.__name__}': {missing_args}")
return handler(**filtered)

View file

@ -152,7 +152,7 @@ class Tasks(metaclass=Singleton):
self._tasks.append(task)
if not task.timer:
if not task.timer or "[only_handler]" in task.name:
continue
try:

View file

@ -25,6 +25,7 @@ from app.library.HttpSocket import HttpSocket
from app.library.Notifications import Notification
from app.library.Presets import Presets
from app.library.Scheduler import Scheduler
from app.library.Services import Services
from app.library.Tasks import Tasks
LOG = logging.getLogger("app")
@ -38,6 +39,8 @@ class Main:
self._config = Config.get_instance(is_native=is_native)
self._app = web.Application()
Services.get_instance().add("app", self._app)
if self._config.debug:
loop = asyncio.get_event_loop()
loop.set_debug(True)

View file

@ -332,3 +332,7 @@ hr {
.is-bold {
font-weight: bold;
}
.fa-spin-10 {
--fa-animation-iteration-count: 10;
}

View file

@ -22,17 +22,16 @@
</span>
</button>
</div>
<div class="column is-half-mobile" v-if="hasDownloaded">
<button type="button" class="button is-fullwidth is-link" :disabled="!hasSelected" @click="downloadSelected">
<div class="column is-half-mobile" v-if="hasDownloaded && hasSelected">
<button type="button" class="button is-fullwidth is-link" @click="downloadSelected">
<span class="icon-text is-block">
<span class="icon"><i class="fa-solid fa-download" /></span>
<span>Download</span>
</span>
</button>
</div>
<div class="column is-half-mobile">
<button type="button" class="button is-fullwidth is-danger" :disabled="!hasSelected"
@click="deleteSelectedItems">
<div class="column is-half-mobile" v-if="hasSelected">
<button type="button" class="button is-fullwidth is-danger" @click="deleteSelectedItems">
<span class="icon-text is-block">
<span class="icon"><i class="fa-solid fa-trash-can" /></span>
<span>{{ config.app.remove_files ? 'Remove' : 'Clear' }}</span>
@ -154,16 +153,6 @@
</td>
<td class="is-vcentered is-items-center">
<div class="field is-grouped is-grouped-centered">
<div class="control" v-if="('finished' === item.status && item.filename) || isEmbedable(item.url)">
<button v-if="'finished' === item.status && item.filename" @click="playVideo(item)"
v-tooltip="'Play video'" class="button is-danger is-light is-small">
<span class="icon"><i class="fa-solid fa-play" /></span>
</button>
<button v-else @click="embed_url = getEmbedable(item.url)" v-tooltip="'Play video'"
class="button is-danger is-small">
<span class="icon"><i class="fa-solid fa-play" /></span>
</button>
</div>
<div class="control" v-if="item.status != 'finished' || !item.filename">
<button class="button is-warning is-fullwidth is-small" v-tooltip="'Re-queue video'"
@click="(event) => reQueueItem(item, event)">
@ -182,9 +171,23 @@
<span class="icon"><i class="fa-solid fa-trash-can" /></span>
</button>
</div>
<div class="control" v-if="item.url && !config.app.basic_mode">
<Dropdown icons="fa-solid fa-cogs" label="" @open_state="s => table_container = !s"
:button_classes="'is-small'">
<div class="control is-expanded" v-if="item.url && !config.app.basic_mode">
<Dropdown icons="fa-solid fa-cogs" @open_state="s => table_container = !s"
:button_classes="'is-small'" label="Actions">
<template v-if="'finished' === item.status && item.filename">
<NuxtLink @click="playVideo(item)" class="dropdown-item">
<span class="icon"><i class="fa-solid fa-play" /></span>
<span>Play video</span>
</NuxtLink>
<hr class="dropdown-divider" />
</template>
<template v-else-if="isEmbedable(item.url)">
<NuxtLink class="dropdown-item has-text-danger" @click="embed_url = getEmbedable(item.url)">
<span class="icon"><i class="fa-solid fa-play" /></span>
<span>Play video</span>
</NuxtLink>
<hr class="dropdown-divider" />
</template>
<NuxtLink class="dropdown-item" @click="emitter('getInfo', item.url)">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Information</span>
@ -235,27 +238,26 @@
</div>
<div class="card-header-icon">
<span class="tag is-info" v-if="item.extras?.duration">
{{ formatTime(item.extras.duration) }}
</span>
<div class="field is-grouped">
<div class="control">
<span class="tag is-info" v-if="item.extras?.duration">
{{ formatTime(item.extras.duration) }}
</span>
</div>
<div class="control">
<button @click="hideThumbnail = !hideThumbnail" v-if="thumbnails">
<span class="icon"><i class="fa-solid"
:class="{ 'fa-arrow-down': hideThumbnail, 'fa-arrow-up': !hideThumbnail, }" /></span>
</button>
</div>
<div class="control">
<label class="checkbox is-block">
<input class="completed-checkbox" type="checkbox" v-model="selectedElms"
:id="'checkbox-' + item._id" :value="item._id">
</label>
</div>
<span v-if="!showThumbnails">
<a v-if="'finished' === item.status && item.filename" href="#" @click.prevent="playVideo(item)"
v-tooltip="'Play video.'">
<span class="icon"><i class="fa-solid fa-play" /></span>
</a>
<a v-else-if="isEmbedable(item.url)" href="#" @click.prevent="embed_url = getEmbedable(item.url)"
v-tooltip="'Play video.'" class="has-text-danger">
<span class="icon"><i class="fa-solid fa-play" /></span>
</a>
</span>
<a :href="item.url" class="has-text-primary" v-tooltip="'Copy url.'" @click.prevent="copyText(item.url)">
<span class="icon"><i class="fa-solid fa-copy" /></span>
</a>
<button @click="hideThumbnail = !hideThumbnail" v-if="thumbnails">
<span class="icon"><i class="fa-solid"
:class="{ 'fa-arrow-down': hideThumbnail, 'fa-arrow-up': !hideThumbnail, }" /></span>
</button>
</div>
</div>
</header>
<div v-if="showThumbnails" class="card-image">
@ -298,10 +300,10 @@
</span>
</div>
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable"
v-if="item.live_in && 'not_live' === item.status">
<span :date-datetime="item.live_in" class="user-hint"
v-tooltip="'Will automatically be requeued at: ' + moment(item.live_in).format('YYYY-M-DD H:mm Z')"
v-rtime="item.live_in" />
v-if="'not_live' === item.status && (item.live_in || item.extras?.release_in)">
<span :date-datetime="item.live_in || item.extras?.release_in" class="user-hint"
v-tooltip="'Will be downloaded at: ' + moment(item.live_in || item.extras?.release_in).format('YYYY-M-DD H:mm Z')"
v-rtime="item.live_in || item.extras?.release_in" />
</div>
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable">
<span class="user-hint" :date-datetime="item.datetime"
@ -311,13 +313,6 @@
v-if="item.file_size">
{{ formatBytes(item.file_size) }}
</div>
<div class="column is-half-mobile has-text-centered is-unselectable">
<label class="checkbox is-block">
<input class="completed-checkbox" type="checkbox" v-model="selectedElms" :id="'checkbox-' + item._id"
:value="item._id">
Select
</label>
</div>
</div>
<div class="columns is-mobile is-multiline">
<div class="column is-half-mobile" v-if="item.status != 'finished' || !item.filename">
@ -346,8 +341,24 @@
</span>
</a>
</div>
<div class="column is-half-mobile" v-if="item.url && !config.app.basic_mode">
<div class="column" v-if="item.url && !config.app.basic_mode">
<Dropdown icons="fa-solid fa-cogs" label="Actions">
<template v-if="'finished' === item.status && item.filename">
<NuxtLink @click="playVideo(item)" class="dropdown-item">
<span class="icon"><i class="fa-solid fa-play" /></span>
<span>Play video</span>
</NuxtLink>
<hr class="dropdown-divider" />
</template>
<template v-else-if="isEmbedable(item.url)">
<NuxtLink class="dropdown-item has-text-danger" @click="embed_url = getEmbedable(item.url)">
<span class="icon"><i class="fa-solid fa-play" /></span>
<span>Play video</span>
</NuxtLink>
<hr class="dropdown-divider" />
</template>
<NuxtLink class="dropdown-item" @click="emitter('getInfo', item.url)">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Information</span>
@ -417,12 +428,12 @@
</template>
<script setup>
import moment from 'moment'
import { useStorage } from '@vueuse/core'
import { makeDownload, formatBytes, uri } from '~/utils/index'
import { isEmbedable, getEmbedable } from '~/utils/embedable'
import Dropdown from './Dropdown.vue'
import { NuxtLink } from '#components'
import { useStorage } from '@vueuse/core'
import moment from 'moment'
import { getEmbedable, isEmbedable } from '~/utils/embedable'
import { formatBytes, makeDownload, uri } from '~/utils/index'
import Dropdown from './Dropdown.vue'
const emitter = defineEmits(['getInfo', 'add_new'])
@ -849,6 +860,6 @@ const is_queued = item => {
return ''
}
return item.live_in || item.extras?.live_in || item.extras?.release_in ? 'fa-spin' : ''
return item.live_in || item.extras?.live_in || item.extras?.release_in ? 'fa-spin fa-spin-10' : ''
}
</script>

View file

@ -89,7 +89,7 @@
<div class="column is-4-tablet is-12-mobile" v-if="!config.app.basic_mode">
<div class="field">
<label class="label is-inline is-unselectable">
<span class="icon"><i class="fa-solid fa-comment" /></span>
<span class="icon"><i class="fa-solid fa-object-ungroup" /></span>
<span>URLs Separator</span>
</label>
<div class="control">
@ -103,7 +103,7 @@
</div>
<span class="help is-bold">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Use this separate multiple URLs in the input field.</span>
<span>Use this to separate multiple URLs in the input field.</span>
</span>
</div>
</div>

View file

@ -142,17 +142,31 @@
<NuxtLink target="_blank" :href="item.url">{{ item.title }}</NuxtLink>
</div>
<div class="card-header-icon">
<span class="tag is-info" v-if="item.extras?.duration">
{{ formatTime(item.extras.duration) }}
</span>
<div class="field is-grouped">
<a :href="item.url" class="has-text-primary" v-tooltip="'Copy url.'" @click.prevent="copyText(item.url)">
<span class="icon"><i class="fa-solid fa-copy" /></span>
</a>
<button @click="hideThumbnail = !hideThumbnail" v-if="thumbnails">
<span class="icon"><i class="fa-solid"
:class="{ 'fa-arrow-down': hideThumbnail, 'fa-arrow-up': !hideThumbnail, }" /></span>
</button>
<div class="control">
<span class="tag is-info" v-if="item.extras?.duration">
{{ formatTime(item.extras.duration) }}
</span>
</div>
<div class="control" v-if="!showThumbnails && isEmbedable(item.url)">
<NuxtLink @click="embed_url = getEmbedable(item.url)" v-tooltip="'Play video'">
<span class="icon has-text-danger"><i class="fa-solid fa-play" /></span>
</NuxtLink>
</div>
<div class="control">
<button @click="hideThumbnail = !hideThumbnail" v-if="thumbnails">
<span class="icon"><i class="fa-solid"
:class="{ 'fa-arrow-down': hideThumbnail, 'fa-arrow-up': !hideThumbnail }" /></span>
</button>
</div>
<div class="control">
<label class="checkbox is-block">
<input class="completed-checkbox" type="checkbox" v-model="selectedElms"
:id="'checkbox-' + item._id" :value="item._id">
</label>
</div>
</div>
</div>
</header>
<div v-if="showThumbnails" class="card-image">
@ -194,29 +208,19 @@
v-if="item.downloaded_bytes">
{{ formatBytes(item.downloaded_bytes) }}
</div>
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable">
<label class="checkbox is-block">
<input class="completed-checkbox" type="checkbox" v-model="selectedElms" :id="'checkbox-' + item._id"
:value="item._id">
Select
</label>
</div>
</div>
<div class="columns is-multiline is-mobile">
<div class="column is-half-mobile">
<button class="button is-warning is-fullwidth" @click="confirmCancel(item);">
<span class="icon-text is-block">
<span class="icon"><i class="fa-solid fa-eject" /></span>
<span>Cancel</span>
</span>
<span class="icon"><i class="fa-solid fa-eject" /></span>
<span>Cancel</span>
</button>
</div>
<div class="column is-half-mobile" v-if="item.url && !config.app.basic_mode">
<button class="button is-info is-fullwidth" @click="emitter('getInfo', item.url)">
<span class="icon-text is-block">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Information</span>
</span>
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Information</span>
</button>
</div>
</div>

View file

@ -250,6 +250,8 @@
and automatically queues any you havent downloaded yet.</li>
<li>To opt out of RSS monitoring for a specific task, append <code>[no_handler]</code> to that tasks name.
</li>
<li>To have the task only monitor RSS, set a timer and add <code>[only_handler]</code> to that tasks name.
</li>
<li>RSS Feed monitoring will only work if you have <code>--download-archive</code> set in command options
for ytdlp.cli, preset or task.</li>
<li>If you don't have <code>--download-archive</code> set but <code>YTP_KEEP_ARCHIVE</code> environment

View file

@ -23,7 +23,7 @@
</p>
<p class="control" v-if="!config.app.basic_mode && false === config.app.basic_mode">
<button v-tooltip.bottom="'Toggle new download form'" class="button is-primary has-tooltip-bottom"
<button v-tooltip.bottom="'Toggle download form'" class="button is-primary has-tooltip-bottom"
@click="config.showForm = !config.showForm">
<span class="icon"><i class="fa-solid fa-plus" /></span>
</button>