refactor: minor UX fixes.
This commit is contained in:
parent
651aa14882
commit
ed6cdf1f92
7 changed files with 64 additions and 111 deletions
18
FAQ.md
18
FAQ.md
|
|
@ -317,22 +317,13 @@ Then simply create a new preset, and in the `Command options for yt-dlp` field s
|
|||
|
||||
```bash
|
||||
--extractor-args "youtubepot-bgutilhttp:base_url=http://bgutil_provider:4416"
|
||||
--extractor-args "youtube:player-client=default,tv,mweb;formats=incomplete"
|
||||
```
|
||||
|
||||
you and also enable the fallback by using the follow extractor args
|
||||
|
||||
```bash
|
||||
--extractor-args "youtubepot-bgutilhttp:base_url=http://bgutil_provider:4416;disable_innertube=1"
|
||||
--extractor-args "youtube:player-client=default,tv,mweb;formats=incomplete"
|
||||
```
|
||||
|
||||
Use this alternative extractor args in case the extractor fails to get the pot tokens from the bgutil provider server.
|
||||
For more information please visit [bgutil-ytdlp-pot-provider](https://github.com/Brainicism/bgutil-ytdlp-pot-provider) project.
|
||||
|
||||
# Troubleshooting and submitting issues
|
||||
|
||||
Before asking a question or submitting an issue for YTPTube, please remember that YTPTube is only a thin wrapper for
|
||||
Before asking a question or submitting an issue for YTPTube, please remember that YTPTube is only a wrapper for
|
||||
[yt-dlp](https://github.com/yt-dlp/yt-dlp). Any issues you might be experiencing with authentication to video websites,
|
||||
postprocessing, permissions, other `yt-dlp options` configurations which seem not to work, or anything else that
|
||||
concerns the workings of the underlying yt-dlp library, need not be opened on the YTPTube project.
|
||||
|
|
@ -343,7 +334,8 @@ and once that is working, importing the options that worked for you into a new `
|
|||
## Via HTTP
|
||||
|
||||
If you have enabled the web terminal via `YTP_CONSOLE_ENABLED` environment variable, simply go to `Other > Terminal` use
|
||||
the yt-dlp command, the interface is jailed to the `yt-dlp` binary you can't access anything else.
|
||||
the yt-dlp command, the interface is jailed to the `yt-dlp` binary you can't access anything else. Or from download form
|
||||
by clicking `advanced options` button than the yellow terminal icon `Run directly in console`.
|
||||
|
||||
## Via CLI
|
||||
|
||||
|
|
@ -503,7 +495,7 @@ as we only test for GPU encoding once on first video stream.
|
|||
|
||||
# Allowing internal URLs requests
|
||||
|
||||
By default, YTPTube prevents requests to internal resources, for security reasons. However, if you want to allow requests to internal URLs, you can set the `YTP_ALLOW_INTERNAL_URLS` environment variable to `true`. This will allow requests to internal URLs.
|
||||
By default, YTPTube prevents requests to internal resources. However, if you want to allow requests to internal URLs, you can set the `YTP_ALLOW_INTERNAL_URLS` environment variable to `true`. This will allow requests to internal URLs.
|
||||
|
||||
We do not recommend enabling this option unless you know what you are doing, as it can expose your internal network to
|
||||
potential security risks. This should only be used if it's truly needed.
|
||||
|
|
@ -557,7 +549,6 @@ services:
|
|||
After making the changes, restart your container. This should resolve the "No space left on device"
|
||||
error during download.
|
||||
|
||||
|
||||
# How to prevent loading screen during YouTube premieres?
|
||||
|
||||
Depending on how you look at it, YTPTube live download implementation is rather great and fast. However, during YouTube
|
||||
|
|
@ -566,7 +557,6 @@ playing. By default we wait for 5min + the duration of the video before starting
|
|||
the loading screen. However, you can override the behavior by setting the following environment variable:
|
||||
|
||||
```env
|
||||
YTP_PREVENT_LIVE_PREMIERE=true
|
||||
YTP_LIVE_PREMIERE_BUFFER=10
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ class Config(metaclass=Singleton):
|
|||
is_native: bool = False
|
||||
"Is the application running in natively."
|
||||
|
||||
prevent_live_premiere: bool = False
|
||||
prevent_live_premiere: bool = True
|
||||
"""Prevent downloading of the initial premiere live broadcast."""
|
||||
|
||||
live_premiere_buffer: int = 5
|
||||
|
|
|
|||
|
|
@ -499,10 +499,11 @@ import { useStorage, useIntersectionObserver } from '@vueuse/core'
|
|||
import type { StoreItem } from '~/types/store'
|
||||
import { useConfirm } from '~/composables/useConfirm'
|
||||
import { deepIncludes, getPath, getImage } from '~/utils'
|
||||
import type { item_request } from '~/types/item'
|
||||
|
||||
const emitter = defineEmits<{
|
||||
(e: 'getInfo', url: string, preset: string, cli: string): void
|
||||
(e: 'add_new', item: Partial<StoreItem>): void
|
||||
(e: 'add_new', item: item_request): void
|
||||
(e: 'getItemInfo', id: string): void
|
||||
(e: 'clear_search'): void
|
||||
}>()
|
||||
|
|
@ -850,7 +851,7 @@ const removeItem = async (item: StoreItem) => {
|
|||
}
|
||||
|
||||
const retryItem = async (item: StoreItem, re_add: boolean = false, remove_file: boolean = false) => {
|
||||
const item_req: Partial<StoreItem> = {
|
||||
const item_req: item_request = {
|
||||
url: item.url,
|
||||
preset: item.preset,
|
||||
folder: item.folder,
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@
|
|||
<span>Pause</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="column is-half-mobile">
|
||||
<div class="column">
|
||||
<Dropdown icons="fa-solid fa-cogs" label="Actions">
|
||||
<template v-if="isEmbedable(item.url)">
|
||||
<NuxtLink class="dropdown-item has-text-danger" @click="embed_url = getEmbedable(item.url) as string">
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@
|
|||
|
||||
<div v-show="'history' === activeTab">
|
||||
<History @getInfo="(url: string, preset: string = '', cli: string = '') => view_info(url, false, preset, cli)"
|
||||
@add_new="(item: Partial<StoreItem>) => toNewDownload(item)" :query="query" :thumbnails="show_thumbnail"
|
||||
@add_new="(item: item_request) => toNewDownload(item)" :query="query" :thumbnails="show_thumbnail"
|
||||
@getItemInfo="(id: string) => view_info(`/api/history/${id}`, true)" @clear_search="query = ''" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -90,13 +90,13 @@
|
|||
|
||||
<div class="column is-2-tablet is-5-mobile">
|
||||
<Dropdown label="Actions" icons="fa-solid fa-list">
|
||||
<a class="dropdown-item has-text-purple" @click="(selectedElms.length > 0 && !massRun) ? runConfirm() : null"
|
||||
<a class="dropdown-item has-text-purple" @click="(selectedElms.length > 0 && !massRun) ? runSelected() : null"
|
||||
:style="{ opacity: (selectedElms.length < 1 || massRun) ? 0.5 : 1, cursor: (selectedElms.length < 1 || massRun) ? 'not-allowed' : 'pointer' }">
|
||||
<span class="icon"><i class="fa-solid fa-up-right-from-square" /></span>
|
||||
<span>Run Selected</span>
|
||||
</a>
|
||||
<a class="dropdown-item has-text-danger"
|
||||
@click="(selectedElms.length > 0 && !massDelete) ? deleteConfirm() : null"
|
||||
@click="(selectedElms.length > 0 && !massDelete) ? deleteSelected() : null"
|
||||
:style="{ opacity: (selectedElms.length < 1 || massDelete) ? 0.5 : 1, cursor: (selectedElms.length < 1 || massDelete) ? 'not-allowed' : 'pointer' }">
|
||||
<span class="icon"><i class="fa-solid fa-trash-can" /></span>
|
||||
<span>Remove Selected</span>
|
||||
|
|
@ -448,10 +448,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog v-if="dialog_confirm.visible" :visible="dialog_confirm.visible" :title="dialog_confirm.title"
|
||||
:message="dialog_confirm.message" :options="dialog_confirm.options" @confirm="dialog_confirm.confirm"
|
||||
:html_message="dialog_confirm.html_message" @cancel="dialog_confirm = reset_dialog()" />
|
||||
|
||||
<Modal v-if="inspectTask" @close="() => inspectTask = null" :contentClass="`modal-content-max`">
|
||||
<TaskInspect :url="inspectTask.url" :preset="inspectTask.preset" />
|
||||
</Modal>
|
||||
|
|
@ -470,6 +466,7 @@ import type { Task, ExportedTask } from '~/types/tasks'
|
|||
import type { WSEP } from '~/types/sockets'
|
||||
import { sleep } from '~/utils'
|
||||
import { useSessionCache } from '~/utils/cache'
|
||||
import type { item_request } from '~/types/item'
|
||||
|
||||
type TaskWithUI = Task & { in_progress?: boolean }
|
||||
|
||||
|
|
@ -478,16 +475,15 @@ const toast = useNotification()
|
|||
const config = useConfigStore()
|
||||
const socket = useSocketStore()
|
||||
const stateStore = useStateStore()
|
||||
const { confirmDialog: cDialog } = useDialog()
|
||||
const { confirmDialog } = useDialog()
|
||||
const sessionCache = useSessionCache()
|
||||
const display_style = useStorage<string>("tasks_display_style", "cards")
|
||||
const isMobile = useMediaQuery({ maxWidth: 1024 })
|
||||
|
||||
// Use the tasks composable
|
||||
const tasksComposable = useTasks()
|
||||
const { tasks, isLoading, addInProgress } = tasksComposable
|
||||
|
||||
const task = ref<TaskWithUI | Record<string, unknown>>({})
|
||||
const task = ref<Partial<TaskWithUI>>({})
|
||||
const taskRef = ref<number | null>(null)
|
||||
const toggleForm = ref<boolean>(false)
|
||||
const selectedElms = ref<Array<number>>([])
|
||||
|
|
@ -496,20 +492,10 @@ const massRun = ref<boolean>(false)
|
|||
const massDelete = ref<boolean>(false)
|
||||
const table_container = ref(false)
|
||||
const inspectTask = ref<TaskWithUI | null>(null)
|
||||
|
||||
const reset_dialog = () => ({
|
||||
visible: false,
|
||||
title: 'Confirm Action',
|
||||
confirm: (_opts: any) => { },
|
||||
message: '',
|
||||
html_message: '',
|
||||
options: [],
|
||||
});
|
||||
|
||||
const dialog_confirm = ref(reset_dialog())
|
||||
|
||||
const query = ref()
|
||||
const toggleFilter = ref(false)
|
||||
const CACHE_KEY = 'tasks:handler_support'
|
||||
const taskHandlerSupport = ref<Record<string, boolean>>(sessionCache.get(CACHE_KEY) || {})
|
||||
|
||||
watch(toggleFilter, () => {
|
||||
if (!toggleFilter.value) {
|
||||
|
|
@ -522,18 +508,19 @@ watch(query, () => {
|
|||
selectedElms.value = []
|
||||
})
|
||||
|
||||
|
||||
watch(masterSelectAll, value => {
|
||||
if (!value) {
|
||||
selectedElms.value = []
|
||||
return
|
||||
}
|
||||
|
||||
for (const key in filteredTasks.value) {
|
||||
const element = filteredTasks.value[key] as Task
|
||||
if (element.id) {
|
||||
selectedElms.value.push(element.id)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
watch(() => socket.isConnected, async () => {
|
||||
|
|
@ -543,10 +530,7 @@ watch(() => socket.isConnected, async () => {
|
|||
socket.on('item_status', statusHandler)
|
||||
})
|
||||
|
||||
|
||||
const CACHE_KEY = 'tasks:handler_support'
|
||||
const taskHandlerSupport = ref<Record<string, boolean>>(sessionCache.get(CACHE_KEY) || {})
|
||||
watch(taskHandlerSupport, (newValue) => sessionCache.set(CACHE_KEY, newValue), { deep: true })
|
||||
watch(taskHandlerSupport, newValue => sessionCache.set(CACHE_KEY, newValue), { deep: true })
|
||||
|
||||
const getCacheKey = (task: Task): string => `${task.id}:${task.url}`
|
||||
|
||||
|
|
@ -581,7 +565,7 @@ const checkHandlerSupport = async (task: Task): Promise<boolean> => {
|
|||
url: task.url,
|
||||
static_only: true
|
||||
})
|
||||
const supported = result?.matched === true
|
||||
const supported = true === result?.matched
|
||||
taskHandlerSupport.value[cacheKey] = supported
|
||||
return supported
|
||||
} catch {
|
||||
|
|
@ -624,7 +608,6 @@ const reloadContent = async (fromMounted: boolean = false) => {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const resetForm = (closeForm: boolean = false) => {
|
||||
task.value = {
|
||||
name: '',
|
||||
|
|
@ -644,38 +627,39 @@ const resetForm = (closeForm: boolean = false) => {
|
|||
}
|
||||
}
|
||||
|
||||
const deleteConfirm = () => {
|
||||
if (selectedElms.value.length < 1) {
|
||||
toast.error('No tasks selected.')
|
||||
return
|
||||
}
|
||||
dialog_confirm.value.visible = true
|
||||
dialog_confirm.value.title = 'Delete Selected Tasks'
|
||||
dialog_confirm.value.message = `Delete ${selectedElms.value.length} task/s?`
|
||||
dialog_confirm.value.confirm = async () => await deleteSelected()
|
||||
}
|
||||
|
||||
const deleteSelected = async () => {
|
||||
if (selectedElms.value.length < 1) {
|
||||
toast.error('No tasks selected.')
|
||||
return
|
||||
}
|
||||
|
||||
massDelete.value = true
|
||||
dialog_confirm.value = reset_dialog()
|
||||
const { status } = await confirmDialog({
|
||||
title: 'Delete Selected Tasks',
|
||||
rawHTML: `Delete <strong class="has-text-danger">${selectedElms.value.length}</strong> task/s?<ul>` + selectedElms.value.map(id => {
|
||||
const item = tasks.value.find(t => t.id === id)
|
||||
return item ? `<li>${item.id}: ${item.name}</li>` : ''
|
||||
}).join('') + `</ul>`,
|
||||
confirmText: 'Delete',
|
||||
confirmColor: 'is-danger'
|
||||
})
|
||||
|
||||
if (true !== status) {
|
||||
return
|
||||
}
|
||||
|
||||
const itemsToDelete = tasks.value.filter(t => t.id && selectedElms.value.includes(t.id))
|
||||
if (itemsToDelete.length < 1) {
|
||||
toast.error('No tasks found to delete.')
|
||||
massDelete.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// Delete tasks sequentially
|
||||
massDelete.value = true
|
||||
|
||||
for (const item of itemsToDelete) {
|
||||
if (item.id) {
|
||||
await tasksComposable.deleteTask(item.id)
|
||||
if (!item.id) {
|
||||
continue
|
||||
}
|
||||
await tasksComposable.deleteTask(item.id)
|
||||
}
|
||||
|
||||
selectedElms.value = []
|
||||
|
|
@ -687,15 +671,9 @@ const deleteSelected = async () => {
|
|||
}
|
||||
|
||||
const deleteItem = async (item: Task) => {
|
||||
if (true !== (await box.confirm(`Delete '${item.name}' task?`))) {
|
||||
if (!item.id || true !== (await box.confirm(`Delete '${item.name}' task?`))) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!item.id) {
|
||||
toast.error('Task ID is missing')
|
||||
return
|
||||
}
|
||||
|
||||
await tasksComposable.deleteTask(item.id)
|
||||
}
|
||||
|
||||
|
|
@ -705,15 +683,9 @@ const toggleEnabled = async (item: Task) => {
|
|||
return
|
||||
}
|
||||
|
||||
const newStatus = !item.enabled
|
||||
|
||||
const updated = await tasksComposable.patchTask(item.id, { enabled: newStatus })
|
||||
|
||||
const updated = await tasksComposable.patchTask(item.id, { enabled: !item.enabled })
|
||||
if (updated) {
|
||||
// Update local reference
|
||||
item.enabled = updated.enabled
|
||||
|
||||
// Update handler support cache if needed
|
||||
if (updated.enabled) {
|
||||
await checkHandlerSupport(updated)
|
||||
}
|
||||
|
|
@ -726,15 +698,9 @@ const toggleHandlerEnabled = async (item: Task) => {
|
|||
return
|
||||
}
|
||||
|
||||
const newStatus = !item.handler_enabled
|
||||
|
||||
const updated = await tasksComposable.patchTask(item.id, { handler_enabled: newStatus })
|
||||
|
||||
const updated = await tasksComposable.patchTask(item.id, { handler_enabled: !item.handler_enabled })
|
||||
if (updated) {
|
||||
// Update local reference
|
||||
item.handler_enabled = updated.handler_enabled
|
||||
|
||||
// Update handler support cache if needed
|
||||
if (updated.handler_enabled) {
|
||||
await checkHandlerSupport(updated)
|
||||
}
|
||||
|
|
@ -745,10 +711,8 @@ const updateItem = async ({ reference, task, archive_all }: { reference?: number
|
|||
let createdOrUpdated: Task | null = null
|
||||
|
||||
if (reference) {
|
||||
// Update existing task
|
||||
createdOrUpdated = await tasksComposable.updateTask(reference, task)
|
||||
} else {
|
||||
// Create new task
|
||||
createdOrUpdated = await tasksComposable.createTask(task)
|
||||
}
|
||||
|
||||
|
|
@ -756,7 +720,6 @@ const updateItem = async ({ reference, task, archive_all }: { reference?: number
|
|||
return
|
||||
}
|
||||
|
||||
// Check handler support for the new/updated task
|
||||
await checkHandlerSupport(createdOrUpdated)
|
||||
|
||||
if (!reference && true === archive_all && createdOrUpdated.id) {
|
||||
|
|
@ -799,29 +762,22 @@ const tryParse = (expression: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
const runConfirm = () => {
|
||||
if (selectedElms.value.length < 1) {
|
||||
toast.error('No tasks selected.')
|
||||
return
|
||||
}
|
||||
dialog_confirm.value.visible = true
|
||||
dialog_confirm.value.title = 'Run Selected Tasks'
|
||||
|
||||
dialog_confirm.value.html_message = `Run the following tasks?<ul>` + selectedElms.value.map(id => {
|
||||
const item = tasks.value.find(t => t.id === id)
|
||||
return item ? `<li>${item.name}</li>` : ''
|
||||
}).join('') + `</ul>`
|
||||
|
||||
dialog_confirm.value.confirm = async () => await runSelected()
|
||||
}
|
||||
|
||||
const runSelected = async () => {
|
||||
if (selectedElms.value.length < 1) {
|
||||
toast.error('No tasks selected.')
|
||||
return
|
||||
}
|
||||
|
||||
dialog_confirm.value = reset_dialog()
|
||||
const { status } = await confirmDialog({
|
||||
rawHTML: `Run the following tasks?<ul>` + selectedElms.value.map(id => {
|
||||
const item = tasks.value.find(t => t.id === id)
|
||||
return item ? `<li>${item.name}</li>` : ''
|
||||
}).join('') + `</ul>`
|
||||
})
|
||||
|
||||
if (true !== status) {
|
||||
return
|
||||
}
|
||||
|
||||
massRun.value = true
|
||||
|
||||
|
|
@ -851,9 +807,14 @@ const runNow = async (item: TaskWithUI, mass: boolean = false) => {
|
|||
item.in_progress = true
|
||||
}
|
||||
|
||||
const data: Record<string, unknown> = {
|
||||
const data: item_request = {
|
||||
url: item.url,
|
||||
preset: item.preset,
|
||||
extras: {
|
||||
source_name: item.name,
|
||||
source_id: item.id,
|
||||
source_handler: "Web",
|
||||
}
|
||||
}
|
||||
|
||||
if (item.folder) {
|
||||
|
|
@ -868,7 +829,7 @@ const runNow = async (item: TaskWithUI, mass: boolean = false) => {
|
|||
data.cli = item.cli
|
||||
}
|
||||
|
||||
if (item?.auto_start !== undefined) {
|
||||
if (undefined !== item?.auto_start) {
|
||||
data.auto_start = item.auto_start
|
||||
}
|
||||
|
||||
|
|
@ -939,7 +900,7 @@ const archiveAll = async (item: TaskWithUI, by_pass: boolean = false) => {
|
|||
|
||||
try {
|
||||
if (true !== by_pass) {
|
||||
const { status } = await cDialog({
|
||||
const { status } = await confirmDialog({
|
||||
message: `Mark all '${item.name}' items as downloaded in download archive?`
|
||||
})
|
||||
|
||||
|
|
@ -965,7 +926,7 @@ const unarchiveAll = async (item: TaskWithUI) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const { status } = await cDialog({
|
||||
const { status } = await confirmDialog({
|
||||
message: `Remove all '${item.name}' items from download archive?`
|
||||
})
|
||||
|
||||
|
|
@ -990,7 +951,7 @@ const generateMeta = async (item: TaskWithUI) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const { status } = await cDialog({
|
||||
const { status } = await confirmDialog({
|
||||
rawHTML: `
|
||||
<p>
|
||||
Generate '${item.name}' metadata? you will be notified when it is done.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import type { item_request } from '~/types/item'
|
||||
import type { StoreItem } from '~/types/store'
|
||||
import { request } from '~/utils'
|
||||
|
||||
|
|
@ -211,7 +212,7 @@ export const useStateStore = defineStore('state', () => {
|
|||
* @param data - Download data (url, preset, folder, etc.)
|
||||
* @returns Promise that resolves when download is added
|
||||
*/
|
||||
const addDownload = async (data: Record<string, unknown>): Promise<void> => {
|
||||
const addDownload = async (data: item_request): Promise<void> => {
|
||||
const socket = useSocketStore()
|
||||
const toast = useNotification()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue