minor design update
This commit is contained in:
parent
249bb43c15
commit
8694e82965
12 changed files with 122 additions and 88 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -76,6 +76,7 @@
|
|||
"socketio",
|
||||
"timespec",
|
||||
"tmpfilename",
|
||||
"ungroup",
|
||||
"upgrader",
|
||||
"urandom",
|
||||
"urlsafe",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -332,3 +332,7 @@ hr {
|
|||
.is-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fa-spin-10 {
|
||||
--fa-animation-iteration-count: 10;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -250,6 +250,8 @@
|
|||
and automatically queues any you haven’t downloaded yet.</li>
|
||||
<li>To opt out of RSS monitoring for a specific task, append <code>[no_handler]</code> to that task’s name.
|
||||
</li>
|
||||
<li>To have the task only monitor RSS, set a timer and add <code>[only_handler]</code> to that task’s 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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue