From 0156052ab94ae818d852b21b2db8ecd751fe7aae Mon Sep 17 00:00:00 2001 From: arabcoders Date: Tue, 3 Jun 2025 18:03:23 +0300 Subject: [PATCH] Reduce confirmation box usage with optional WebUI flag. --- .vscode/settings.json | 9 ++ ui/components/ConditionForm.vue | 7 +- ui/components/History.vue | 13 +-- ui/components/NewDownload.vue | 3 +- ui/components/NotificationForm.vue | 4 +- ui/components/PresetForm.vue | 5 +- ui/components/Queue.vue | 98 ++++++++++---------- ui/components/Settings.vue | 144 +++++++++++++++++------------ ui/components/TaskForm.vue | 41 ++++---- ui/composables/useConfirm.ts | 19 ++++ ui/pages/conditions.vue | 3 +- ui/pages/index.vue | 4 +- ui/pages/notifications.vue | 5 +- ui/pages/presets.vue | 3 +- ui/pages/tasks.vue | 5 +- 15 files changed, 213 insertions(+), 150 deletions(-) create mode 100644 ui/composables/useConfirm.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 7485ca2d..b5623dc6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -74,4 +74,13 @@ "spellright.documentTypes": [ "latex" ], + "search.exclude": { + "ui/.nuxt": true, + "ui/.output": true, + "ui/dist": true, + "ui/exported": true, + "ui/node_modules": true, + "ui/.out": true, + "**/.venv": true, + } } diff --git a/ui/components/ConditionForm.vue b/ui/components/ConditionForm.vue index 665b3d8c..ea09e06c 100644 --- a/ui/components/ConditionForm.vue +++ b/ui/components/ConditionForm.vue @@ -57,7 +57,7 @@
+ placeholder="For the problematic channel or video name.">
@@ -74,7 +74,7 @@
+ placeholder="availability = 'needs_auth' & channel_id = 'channel_id'">
@@ -150,6 +150,7 @@ const props = defineProps({ }) const toast = useNotification() +const box = useConfirm() const form = reactive(JSON.parse(JSON.stringify(props.item))) const import_string = ref('') const showImport = useStorage('showImport', false); @@ -219,7 +220,7 @@ const importItem = async () => { return } - if ((form.filter || form.cli) && false === confirm('This will overwrite the current data. Are you sure?')) { + if ((form.filter || form.cli) && false === box.confirm('This will overwrite the current data. Are you sure?', true)) { return } diff --git a/ui/components/History.vue b/ui/components/History.vue index d77b90da..368b9657 100644 --- a/ui/components/History.vue +++ b/ui/components/History.vue @@ -386,6 +386,7 @@ const config = useConfigStore() const stateStore = useStateStore() const socket = useSocketStore() const toast = useNotification() +const box = useConfirm() const selectedElms = ref([]) const masterSelectAll = ref(false) @@ -488,7 +489,7 @@ const deleteSelectedItems = () => { msg += '\nThis will delete the files from the server if they exist.' } - if (false === confirm(msg)) { + if (false === box.confirm(msg, config.app.remove_files)) { return } @@ -506,7 +507,7 @@ const deleteSelectedItems = () => { const clearCompleted = () => { let msg = 'Are you sure you want to clear all completed downloads?' - if (false === confirm(msg)) { + if (false === box.confirm(msg)) { return } @@ -518,7 +519,7 @@ const clearCompleted = () => { } const clearIncomplete = () => { - if (false === confirm('Are you sure you want to clear all in-complete downloads?')) { + if (false === box.confirm('Are you sure you want to clear all in-complete downloads?')) { return } @@ -598,7 +599,7 @@ const setStatus = item => { } const requeueIncomplete = () => { - if (false === confirm('Are you sure you want to re-queue all incomplete downloads?')) { + if (false === box.confirm('Are you sure you want to re-queue all incomplete downloads?')) { return false } @@ -612,7 +613,7 @@ const requeueIncomplete = () => { } const archiveItem = item => { - if (!confirm(`Archive '${item.title ?? item.id ?? item.url ?? '??'}'?`)) { + if (!box.confirm(`Archive '${item.title ?? item.id ?? item.url ?? '??'}'?`)) { return } socket.emit('archive_item', item) @@ -621,7 +622,7 @@ const archiveItem = item => { const removeItem = item => { const msg = `Remove '${item.title ?? item.id ?? item.url ?? '??'}'?\n this will delete the file from the server.` - if (config.app.remove_files && !confirm(msg)) { + if (false === box.confirm(msg, config.app.remove_files)) { return false } diff --git a/ui/components/NewDownload.vue b/ui/components/NewDownload.vue index 61a415a2..9507d583 100644 --- a/ui/components/NewDownload.vue +++ b/ui/components/NewDownload.vue @@ -190,6 +190,7 @@ const emitter = defineEmits(['getInfo', 'clear_form']) const config = useConfigStore() const socket = useSocketStore() const toast = useNotification() +const box = useConfirm() const showAdvanced = useStorage('show_advanced', false) const addInProgress = ref(false) @@ -261,7 +262,7 @@ const addDownload = async () => { } const resetConfig = () => { - if (true !== confirm('Reset your local configuration?')) { + if (true !== box.confirm('Reset your local configuration?')) { return } diff --git a/ui/components/NotificationForm.vue b/ui/components/NotificationForm.vue index 90a9dcff..ded92e10 100644 --- a/ui/components/NotificationForm.vue +++ b/ui/components/NotificationForm.vue @@ -295,6 +295,7 @@ const requestMethods = ['POST', 'PUT']; const requestType = ['json', 'form']; const showImport = useStorage('showImport', false); const import_string = ref(''); +const box = useConfirm() const checkInfo = async () => { const required = ['name', 'request.url', 'request.method', 'request.type', 'request.data_key']; @@ -331,7 +332,6 @@ const checkInfo = async () => { emitter('submit', { reference: toRaw(props.reference), item: toRaw(form) }); } - const importItem = async () => { let val = import_string.value.trim() if (!val) { @@ -359,7 +359,7 @@ const importItem = async () => { } if (form.target) { - if (false === confirm('This will overwrite the current form fields. Are you sure?')) { + if (false === box.confirm('This will overwrite the current form fields. Are you sure?', true)) { return } } diff --git a/ui/components/PresetForm.vue b/ui/components/PresetForm.vue index 7ff1a062..d7b90262 100644 --- a/ui/components/PresetForm.vue +++ b/ui/components/PresetForm.vue @@ -221,7 +221,8 @@ const config = useConfigStore() const toast = useNotification() const form = reactive(JSON.parse(JSON.stringify(props.preset))) const import_string = ref('') -const showImport = useStorage('showImport', false); +const showImport = useStorage('showImport', false) +const box = useConfirm() onMounted(() => { if (props.preset?.cli && '' !== props.preset?.cli) { @@ -328,7 +329,7 @@ const importItem = async () => { return } - if (form.cli && false === confirm('This will overwrite the current data. Are you sure?')) { + if (form.cli && false === box.confirm('This will overwrite the current data. Are you sure?', true)) { return } diff --git a/ui/components/Queue.vue b/ui/components/Queue.vue index f5261c05..d7bce21c 100644 --- a/ui/components/Queue.vue +++ b/ui/components/Queue.vue @@ -235,12 +235,13 @@ import { ucFirst } from '~/utils/index' import { isEmbedable, getEmbedable } from '~/utils/embedable' const emitter = defineEmits(['getInfo']) -const config = useConfigStore(); -const stateStore = useStateStore(); -const socket = useSocketStore(); +const config = useConfigStore() +const stateStore = useStateStore() +const socket = useSocketStore() +const box = useConfirm() -const selectedElms = ref([]); -const masterSelectAll = ref(false); +const selectedElms = ref([]) +const masterSelectAll = ref(false) const showQueue = useStorage('showQueue', true) const hideThumbnail = useStorage('hideThumbnailQueue', false) const display_style = useStorage('display_style', 'cards') @@ -252,11 +253,11 @@ const bg_opacity = useStorage('random_bg_opacity', 0.85) watch(masterSelectAll, (value) => { for (const key in stateStore.queue) { - const element = stateStore.queue[key]; + const element = stateStore.queue[key] if (value) { - selectedElms.value.push(element._id); + selectedElms.value.push(element._id) } else { - selectedElms.value = []; + selectedElms.value = [] } } }) @@ -266,35 +267,35 @@ const hasQueuedItems = computed(() => stateStore.count('queue') > 0) const setIcon = item => { if ('downloading' === item.status && item.is_live) { - return 'fa-globe fa-spin'; + return 'fa-globe fa-spin' } if ('downloading' === item.status) { - return 'fa-download'; + return 'fa-download' } if ('postprocessing' === item.status) { - return 'fa-cog fa-spin'; + return 'fa-cog fa-spin' } if (null === item.status && true === config.paused) { - return 'fa-pause-circle'; + return 'fa-pause-circle' } if (!item.status) { - return 'fa-question'; + return 'fa-question' } - return 'fa-spinner fa-spin'; + return 'fa-spinner fa-spin' } const setStatus = item => { if (null === item.status && true === config.paused) { - return 'Paused'; + return 'Paused' } if ('downloading' === item.status && item.is_live) { - return 'Live Streaming'; + return 'Live Streaming' } if ('preparing' === item.status) { @@ -302,7 +303,7 @@ const setStatus = item => { } if (!item.status) { - return 'Unknown..'; + return 'Unknown..' } return ucFirst(item.status) @@ -324,84 +325,82 @@ const setIconColor = item => { return '' } - - const ETAPipe = value => { if (value === null || 0 === value) { - return 'Live'; + return 'Live' } if (value < 60) { - return `${Math.round(value)}s`; + return `${Math.round(value)}s` } if (value < 3600) { - return `${Math.floor(value / 60)}m ${Math.round(value % 60)}s`; + return `${Math.floor(value / 60)}m ${Math.round(value % 60)}s` } const hours = Math.floor(value / 3600) const minutes = value % 3600 - return `${hours}h ${Math.floor(minutes / 60)}m ${Math.round(minutes % 60)}s`; + return `${hours}h ${Math.floor(minutes / 60)}m ${Math.round(minutes % 60)}s` } const speedPipe = value => { if (null === value || 0 === value) { - return '0KB/s'; + return '0KB/s' } - const k = 1024; - const dm = 2; - const sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s', 'EB/s', 'ZB/s', 'YB/s']; - const i = Math.floor(Math.log(value) / Math.log(k)); - return parseFloat((value / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + const k = 1024 + const dm = 2 + const sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s', 'EB/s', 'ZB/s', 'YB/s'] + const i = Math.floor(Math.log(value) / Math.log(k)) + return parseFloat((value / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] } const percentPipe = value => { if (value === null || 0 === value) { - return '00.00'; + return '00.00' } - return parseFloat(value).toFixed(2); + return parseFloat(value).toFixed(2) } const updateProgress = (item) => { - let string = ''; + let string = '' if (null === item.status && true === config.paused) { - return 'Paused'; + return 'Paused' } if ('postprocessing' === item.status) { - return 'Post-processors are running.'; + return 'Post-processors are running.' } if ('preparing' === item.status) { - return ag(item, 'extras.external_downloader') ? 'External downloader.' : 'Preparing'; + return ag(item, 'extras.external_downloader') ? 'External downloader.' : 'Preparing' } if (null != item.status) { - string += item.percent && !item.is_live ? percentPipe(item.percent) + '%' : 'Live'; + string += item.percent && !item.is_live ? percentPipe(item.percent) + '%' : 'Live' } - string += item.speed ? ' - ' + speedPipe(item.speed) : ' - Waiting..'; + string += item.speed ? ' - ' + speedPipe(item.speed) : ' - Waiting..' if (null != item.status && item.eta) { - string += ' - ' + ETAPipe(item.eta); + string += ' - ' + ETAPipe(item.eta) } return string; } const confirmCancel = item => { - if (true !== confirm(`Are you sure you want to cancel (${item.title})?`)) { - return false; + if (true !== box.confirm(`Are you sure you want to cancel (${item.title})?`)) { + return false } - cancelItems(item._id); - return true; + cancelItems(item._id) + return true } const cancelSelected = () => { - if (true !== confirm('Are you sure you want to cancel selected items?')) { + if (true !== box.confirm('Are you sure you want to cancel selected items?')) { return false; } - cancelItems(selectedElms.value); - selectedElms.value = []; + cancelItems(selectedElms.value) + selectedElms.value = [] return true; } @@ -410,20 +409,21 @@ const cancelItems = item => { if (typeof item === 'object') { for (const key in item) { - items.push(item[key]); + items.push(item[key]) } } else { - items.push(item); + items.push(item) } if (items.length < 0) { - return; + return } - items.forEach(id => socket.emit('item_cancel', id)); + items.forEach(id => socket.emit('item_cancel', id)) } const pImg = e => e.target.naturalHeight > e.target.naturalWidth ? e.target.classList.add('image-portrait') : null + watch(embed_url, v => { if (!bg_enable.value) { return diff --git a/ui/components/Settings.vue b/ui/components/Settings.vue index bb70948a..cf64c3da 100644 --- a/ui/components/Settings.vue +++ b/ui/components/Settings.vue @@ -9,71 +9,93 @@
-
- -
- - - -
-

- - Select the color scheme for the WebUI. -

-
+
+
+
+ +
+ + + +
+

+ + Select the color scheme for the WebUI. +

+
-
- -
- - -
-

- - Use random background image. -

-
+
+ +
+ + +
+

+ + + Use random background image. + + Reload + + + +

+
-
- -
- +
+ +
+ +
+

+ + How visible the background image should be. +

+
-

- - How visible the background image should be. -

-
+
+
+ +
+ + +
+

+ + Reduce the usage of confirm boxes in the WebUI. +

+
-
- -
- +
+ +
+ + +
+

+ + + Show notification toasts. If disabled, you will not see errors reported or anything else. + +

+
-

- - Change the displayed picture. -

@@ -96,5 +118,7 @@ defineProps({ const bg_enable = useStorage('random_bg', true) const bg_opacity = useStorage('random_bg_opacity', 0.85) const selectedTheme = useStorage('theme', 'auto') +const allow_toasts = useStorage('allow_toasts', true) +const reduce_confirm = useStorage('reduce_confirm', false) diff --git a/ui/components/TaskForm.vue b/ui/components/TaskForm.vue index d544a7fa..a6d1e183 100644 --- a/ui/components/TaskForm.vue +++ b/ui/components/TaskForm.vue @@ -232,11 +232,12 @@ import { useStorage } from '@vueuse/core' import { CronExpressionParser } from 'cron-parser' -const emitter = defineEmits(['cancel', 'submit']); -const toast = useNotification(); -const config = useConfigStore(); -const showImport = useStorage('showImport', false); -const import_string = ref(''); +const emitter = defineEmits(['cancel', 'submit']) +const toast = useNotification() +const config = useConfigStore() +const showImport = useStorage('showImport', false) +const import_string = ref('') +const box = useConfirm() const props = defineProps({ reference: { @@ -255,49 +256,49 @@ const props = defineProps({ }, }) -const form = reactive(props.task); +const form = reactive(props.task) onMounted(() => { if (!props.task?.preset || '' === props.task.preset) { - form.preset = toRaw(config.app.default_preset); + form.preset = toRaw(config.app.default_preset) } }) const checkInfo = async () => { - const required = ['name', 'url']; + const required = ['name', 'url'] for (const key of required) { if (!form[key]) { - toast.error(`The ${key} field is required.`); - return; + toast.error(`The ${key} field is required.`) + return } } if (form.timer) { try { - CronExpressionParser.parse(form.timer); + CronExpressionParser.parse(form.timer) } catch (e) { console.error(e) - toast.error(`Invalid CRON expression. ${e.message}`); - return; + toast.error(`Invalid CRON expression. ${e.message}`) + return } } try { - new URL(form.url); + new URL(form.url) } catch (e) { - toast.error('Invalid URL'); - return; + toast.error('Invalid URL') + return } if (form?.cli && '' !== form.cli) { - const options = await convertOptions(form.cli); + const options = await convertOptions(form.cli) if (null === options) { return } form.cli = form.cli.trim(" ") } - emitter('submit', { reference: toRaw(props.reference), task: toRaw(form) }); + emitter('submit', { reference: toRaw(props.reference), task: toRaw(form) }) } const importItem = async () => { @@ -327,7 +328,7 @@ const importItem = async () => { } if (form.url || form.timer) { - if (false === confirm('This will overwrite the current form fields. Are you sure?')) { + if (false === box.confirm('This will overwrite the current form fields. Are you sure?', true)) { return } } @@ -391,7 +392,7 @@ const convertOptions = async args => { toast.error(e.message) } - return null; + return null } const hasFormatInConfig = computed(() => { diff --git a/ui/composables/useConfirm.ts b/ui/composables/useConfirm.ts new file mode 100644 index 00000000..45976a4d --- /dev/null +++ b/ui/composables/useConfirm.ts @@ -0,0 +1,19 @@ +import { useStorage } from '@vueuse/core' + +const reduceConfirm = useStorage('reduce_confirm', false) + +function confirm(msg: string, force: boolean = false): boolean { + if (false === force && true === reduceConfirm.value) { + return true + } + + return window.confirm(msg) +} + +function alert(msg: string): boolean { + return window.confirm(msg) +} + +export default function useConfirm() { + return { confirm, alert, reduceConfirm } +} diff --git a/ui/pages/conditions.vue b/ui/pages/conditions.vue index 075efb03..96433fa9 100644 --- a/ui/pages/conditions.vue +++ b/ui/pages/conditions.vue @@ -116,6 +116,7 @@ import { request } from '~/utils/index' const toast = useNotification() const config = useConfigStore() const socket = useSocketStore() +const box = useConfirm() const items = ref([]) const item = ref({}) @@ -213,7 +214,7 @@ const updateItems = async items => { } const deleteItem = async (item) => { - if (true !== confirm(`Delete '${item.name}'?`)) { + if (true !== box.confirm(`Delete '${item.name}'?`, true)) { return } diff --git a/ui/pages/index.vue b/ui/pages/index.vue index 2486aaff..b0e1f7ae 100644 --- a/ui/pages/index.vue +++ b/ui/pages/index.vue @@ -62,6 +62,8 @@ const emitter = defineEmits(['getInfo']) const config = useConfigStore() const stateStore = useStateStore() const socket = useSocketStore() +const box = useConfirm() + const get_info = ref('') const bg_enable = useStorage('random_bg', true) const bg_opacity = useStorage('random_bg_opacity', 0.85) @@ -91,7 +93,7 @@ watch(() => stateStore.queue, () => { }, { deep: true }) const pauseDownload = () => { - if (false === confirm('Are you sure you want to pause all non-active downloads?')) { + if (false === box.confirm('Are you sure you want to pause all non-active downloads?')) { return false } diff --git a/ui/pages/notifications.vue b/ui/pages/notifications.vue index dc57249f..9a3457a4 100644 --- a/ui/pages/notifications.vue +++ b/ui/pages/notifications.vue @@ -143,6 +143,7 @@ import { request } from '~/utils/index' const toast = useNotification() const config = useConfigStore() const socket = useSocketStore() +const box = useConfirm() const allowedEvents = ref([]) const notifications = ref([]) @@ -234,7 +235,7 @@ const updateData = async notifications => { } const deleteItem = async item => { - if (true !== confirm(`Are you sure you want to delete notification target (${item.name})?`)) { + if (true !== box.confirm(`Are you sure you want to delete notification target (${item.name})?`)) { return } @@ -294,7 +295,7 @@ const editItem = item => { const join_events = events => (!events || events.length < 1) ? 'ALL' : events.map(e => ucFirst(e)).join(', ') const sendTest = async () => { - if (true !== confirm('Are you sure you want to send a test notification?')) { + if (true !== box.confirm('Are you sure you want to send a test notification?')) { return } diff --git a/ui/pages/presets.vue b/ui/pages/presets.vue index 90864b17..fc9a80b3 100644 --- a/ui/pages/presets.vue +++ b/ui/pages/presets.vue @@ -152,6 +152,7 @@ import { request } from '~/utils/index' const toast = useNotification() const config = useConfigStore() const socket = useSocketStore() +const box = useConfirm() const presets = ref([]) const preset = ref({}) @@ -249,7 +250,7 @@ const updatePresets = async (items) => { } const deleteItem = async (item) => { - if (true !== confirm(`Delete preset '${item.name}'?`)) { + if (true !== box.confirm(`Delete preset '${item.name}'?`, true)) { return } diff --git a/ui/pages/tasks.vue b/ui/pages/tasks.vue index c8e19e8b..1d3171b5 100644 --- a/ui/pages/tasks.vue +++ b/ui/pages/tasks.vue @@ -125,6 +125,7 @@ import { request } from '~/utils/index' const toast = useNotification() const config = useConfigStore() const socket = useSocketStore() +const box = useConfirm() const tasks = ref([]) const task = ref({}) @@ -216,7 +217,7 @@ const updateTasks = async items => { } const deleteItem = async item => { - if (true !== confirm(`Are you sure you want to delete (${item.name})?`)) { + if (true !== box.confirm(`Are you sure you want to delete (${item.name})?`, true)) { return } @@ -290,7 +291,7 @@ const tryParse = expression => { } const runNow = item => { - if (true !== confirm(`Run '${item.name}' now? it will also run at the scheduled time.`)) { + if (true !== box.confirm(`Run '${item.name}' now? it will also run at the scheduled time.`)) { return }