minor design update
This commit is contained in:
parent
5e14c232d7
commit
dfe1acbec4
8 changed files with 265 additions and 274 deletions
|
|
@ -19,40 +19,32 @@
|
|||
</vTooltip>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const props = defineProps({
|
||||
image: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
loader: {
|
||||
Type: Function,
|
||||
required: false,
|
||||
},
|
||||
privacy: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
const props = defineProps < {
|
||||
image?: string
|
||||
title?: string
|
||||
loader?: () => Promise < void>
|
||||
privacy ?: boolean
|
||||
} > ()
|
||||
|
||||
const cache = useSessionCache()
|
||||
const toast = useNotification()
|
||||
const url = ref()
|
||||
const url = ref < string | null > (null)
|
||||
const error = ref(false)
|
||||
const isPreloading = ref(false)
|
||||
|
||||
let loadTimer = null;
|
||||
const cancelRequest = new AbortController();
|
||||
let loadTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const cancelRequest = new AbortController()
|
||||
|
||||
const defaultLoader = async () => {
|
||||
const defaultLoader = async (): Promise<void> => {
|
||||
try {
|
||||
if (!props.image) {
|
||||
return
|
||||
}
|
||||
|
||||
if (cache.has(props.image)) {
|
||||
url.value = cache.get(props.image)
|
||||
return
|
||||
|
|
@ -60,18 +52,16 @@ const defaultLoader = async () => {
|
|||
|
||||
const response = await request(props.image, { signal: cancelRequest.signal })
|
||||
|
||||
if (200 !== response.status) {
|
||||
if (!response.ok) {
|
||||
toast.error(`ImageView Request error. ${response.status}: ${response.statusText}`)
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const objUrl = URL.createObjectURL(await response.blob());
|
||||
|
||||
const objUrl = URL.createObjectURL(await response.blob())
|
||||
cache.set(props.image, objUrl)
|
||||
|
||||
url.value = objUrl;
|
||||
} catch (e) {
|
||||
if ('not_needed' === e) {
|
||||
url.value = objUrl
|
||||
} catch (e: any) {
|
||||
if (e === 'not_needed') {
|
||||
return
|
||||
}
|
||||
console.error(e)
|
||||
|
|
@ -81,25 +71,28 @@ const defaultLoader = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
const stopTimer = async () => {
|
||||
const stopTimer = async (): Promise<void> => {
|
||||
if (error.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (url.value) {
|
||||
isPreloading.value = false
|
||||
url.value = null;
|
||||
return;
|
||||
url.value = null
|
||||
return
|
||||
}
|
||||
|
||||
await awaiter(() => isPreloading.value)
|
||||
clearTimeout(loadTimer)
|
||||
if (loadTimer !== null) {
|
||||
clearTimeout(loadTimer)
|
||||
}
|
||||
|
||||
isPreloading.value = false
|
||||
url.value = null;
|
||||
url.value = null
|
||||
cancelRequest.abort('not_needed')
|
||||
}
|
||||
|
||||
const loadContent = async () => {
|
||||
const loadContent = async (): Promise<void> => {
|
||||
if (props.loader) {
|
||||
return props.loader()
|
||||
}
|
||||
|
|
@ -107,11 +100,18 @@ const loadContent = async () => {
|
|||
return defaultLoader()
|
||||
}
|
||||
|
||||
const clearCache = async () => {
|
||||
const clearCache = async (): Promise<void> => {
|
||||
if (!props.image) return
|
||||
|
||||
cache.remove(props.image)
|
||||
url.value = '';
|
||||
url.value = ''
|
||||
return loadContent()
|
||||
}
|
||||
|
||||
const pImg = e => e.target.naturalHeight > e.target.naturalWidth ? e.target.classList.add('image-portrait') : null
|
||||
const pImg = (e: Event): void => {
|
||||
const target = e.target as HTMLImageElement
|
||||
if (target.naturalHeight > target.naturalWidth) {
|
||||
target.classList.add('image-portrait')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
<style scoped>
|
||||
code {
|
||||
color: var(--bulma-code) !important
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="modal is-active" v-if="false === externalModel">
|
||||
|
|
@ -31,62 +37,41 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
code {
|
||||
color: var(--bulma-code) !important
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const toast = useNotification()
|
||||
const emitter = defineEmits<{ (e: 'closeModel'): void }>()
|
||||
|
||||
const emitter = defineEmits(['closeModel'])
|
||||
const isLoading = ref<Boolean>(false)
|
||||
const props = defineProps<{
|
||||
link?: string
|
||||
preset?: string
|
||||
useUrl?: boolean
|
||||
externalModel?: boolean
|
||||
}>()
|
||||
|
||||
const isLoading = ref<boolean>(false)
|
||||
const data = ref<any>({})
|
||||
|
||||
const props = defineProps({
|
||||
link: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
preset: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
useUrl: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
externalModel: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
const handle_event = (e: KeyboardEvent) => {
|
||||
if (e.key !== 'Escape') {
|
||||
return
|
||||
const handle_event = (e: KeyboardEvent): void => {
|
||||
if (e.key === 'Escape') {
|
||||
emitter('closeModel')
|
||||
}
|
||||
emitter('closeModel')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(async (): Promise<void> => {
|
||||
document.addEventListener('keydown', handle_event)
|
||||
|
||||
let url = props.useUrl ? props.link : '/api/yt-dlp/url/info';
|
||||
let url = props.useUrl ? props.link || '' : '/api/yt-dlp/url/info'
|
||||
|
||||
if (!props.useUrl) {
|
||||
let params = new URLSearchParams();
|
||||
const params = new URLSearchParams()
|
||||
if (props.preset) {
|
||||
params.append('preset', props.preset);
|
||||
params.append('preset', props.preset)
|
||||
}
|
||||
params.append('url', props.link);
|
||||
url += '?' + params.toString();
|
||||
params.append('url', props.link || '')
|
||||
url += '?' + params.toString()
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -96,10 +81,9 @@ onMounted(async () => {
|
|||
|
||||
try {
|
||||
data.value = JSON.parse(body)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
data.value = body
|
||||
}
|
||||
|
||||
} catch (e: any) {
|
||||
console.error(e)
|
||||
toast.error(`Error: ${e.message}`)
|
||||
|
|
@ -108,5 +92,7 @@ onMounted(async () => {
|
|||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => document.removeEventListener('keydown', handle_event))
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keydown', handle_event)
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,76 +1,77 @@
|
|||
<template>
|
||||
<div ref="targetEl" :style="`min-height:${fixedMinHeight ? fixedMinHeight : (minHeight)}px`">
|
||||
<slot v-if="shouldRender"/>
|
||||
<div ref="targetEl" :style="`min-height:${fixedMinHeight || props.minHeight || 0}px`">
|
||||
<slot v-if="shouldRender" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {nextTick, ref} from 'vue'
|
||||
import {useIntersectionObserver} from '@vueuse/core'
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, onBeforeUnmount } from 'vue'
|
||||
import { useIntersectionObserver } from '@vueuse/core'
|
||||
|
||||
function onIdle(cb = () => {
|
||||
}) {
|
||||
if ("requestIdleCallback" in window) {
|
||||
window.requestIdleCallback(cb);
|
||||
const props = defineProps<{
|
||||
renderOnIdle?: boolean
|
||||
unrender?: boolean
|
||||
minHeight?: number
|
||||
unrenderDelay?: number
|
||||
}>()
|
||||
|
||||
const shouldRender = ref(false)
|
||||
const targetEl = ref<HTMLElement | null>(null)
|
||||
const fixedMinHeight = ref(0)
|
||||
|
||||
let unrenderTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let renderTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function onIdle(cb: () => void): void {
|
||||
if ('requestIdleCallback' in window) {
|
||||
(window as any).requestIdleCallback(cb)
|
||||
} else {
|
||||
setTimeout(() => nextTick(cb), 300);
|
||||
setTimeout(() => nextTick(cb), 300)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
renderOnIdle: Boolean,
|
||||
unrender: Boolean,
|
||||
minHeight: Number,
|
||||
unrenderDelay: {
|
||||
type: Number,
|
||||
default: 6000,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const shouldRender = ref(false);
|
||||
const targetEl = ref();
|
||||
const fixedMinHeight = ref(0);
|
||||
let unrenderTimer;
|
||||
let renderTimer;
|
||||
const { stop } = useIntersectionObserver(targetEl, ([{ isIntersecting }]) => {
|
||||
if (isIntersecting) {
|
||||
if (unrenderTimer) clearTimeout(unrenderTimer)
|
||||
|
||||
const {stop} = useIntersectionObserver(targetEl, ([{isIntersecting}]) => {
|
||||
if (isIntersecting) {
|
||||
// perhaps the user re-scrolled to a component that was set to unrender. In that case stop the un-rendering timer
|
||||
clearTimeout(unrenderTimer);
|
||||
// if we're dealing under-rendering lets add a waiting period of 200ms before rendering.
|
||||
// If a component enters the viewport and also leaves it within 200ms it will not render at all.
|
||||
// This saves work and improves performance when user scrolls very fast
|
||||
renderTimer = setTimeout(() => (shouldRender.value = true), props.unrender ? 200 : 0);
|
||||
shouldRender.value = true;
|
||||
if (!props.unrender) {
|
||||
stop();
|
||||
}
|
||||
} else if (props.unrender) {
|
||||
// if the component was set to render, cancel that
|
||||
clearTimeout(renderTimer);
|
||||
unrenderTimer = setTimeout(() => {
|
||||
if (targetEl.value?.clientHeight) {
|
||||
fixedMinHeight.value = targetEl.value.clientHeight;
|
||||
}
|
||||
shouldRender.value = false;
|
||||
}, props.unrenderDelay);
|
||||
}
|
||||
}, {
|
||||
rootMargin: "600px",
|
||||
}
|
||||
);
|
||||
renderTimer = setTimeout(() => { shouldRender.value = true }, props.unrender ? 200 : 0)
|
||||
|
||||
if (props.renderOnIdle) {
|
||||
onIdle(() => {
|
||||
shouldRender.value = true;
|
||||
if (!props.unrender) {
|
||||
stop();
|
||||
}
|
||||
});
|
||||
shouldRender.value = true
|
||||
|
||||
if (!props.unrender) {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
else if (props.unrender) {
|
||||
if (renderTimer) {
|
||||
clearTimeout(renderTimer)
|
||||
}
|
||||
|
||||
return {targetEl, shouldRender, fixedMinHeight};
|
||||
},
|
||||
};
|
||||
unrenderTimer = setTimeout(() => {
|
||||
if (targetEl.value?.clientHeight) {
|
||||
fixedMinHeight.value = targetEl.value.clientHeight
|
||||
}
|
||||
shouldRender.value = false
|
||||
}, props.unrenderDelay ?? 6000)
|
||||
}
|
||||
}, { rootMargin: '600px', })
|
||||
|
||||
if (props.renderOnIdle) {
|
||||
onIdle(() => {
|
||||
shouldRender.value = true
|
||||
if (!props.unrender) {
|
||||
stop()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (renderTimer) {
|
||||
clearTimeout(renderTimer)
|
||||
|
||||
}
|
||||
if (unrenderTimer) {
|
||||
clearTimeout(unrenderTimer)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,67 +1,45 @@
|
|||
<template>
|
||||
<div class="notification" :class="message_class">
|
||||
<button class="delete" @click="$emit('close')" v-if="!useToggle && useClose"></button>
|
||||
<div @click="$emit('toggle')" class="is-clickable is-pulled-right is-unselectable" v-if="useToggle">
|
||||
<div class="notification" :class="props.message_class">
|
||||
<button class="delete" @click="emit('close')" v-if="!props.useToggle && props.useClose"></button>
|
||||
|
||||
<div @click="emit('toggle')" class="is-clickable is-pulled-right is-unselectable" v-if="props.useToggle">
|
||||
<span class="icon">
|
||||
<i class="fas" :class="{ 'fa-arrow-up': toggle, 'fa-arrow-down': !toggle }"></i>
|
||||
<i class="fas" :class="{ 'fa-arrow-up': props.toggle, 'fa-arrow-down': !props.toggle }"></i>
|
||||
</span>
|
||||
<span>{{ toggle ? 'Close' : 'Open' }}</span>
|
||||
<span>{{ props.toggle ? 'Close' : 'Open' }}</span>
|
||||
</div>
|
||||
<div class="notification-title is-unselectable" :class="{ 'is-clickable': useToggle }" v-if="title || icon"
|
||||
@click="useToggle ? $emit('toggle', toggle) : null">
|
||||
<template v-if="icon">
|
||||
|
||||
<div class="notification-title is-unselectable" :class="{ 'is-clickable': props.useToggle }"
|
||||
v-if="props.title || props.icon" @click="props.useToggle ? emit('toggle', props.toggle) : null">
|
||||
<template v-if="props.icon">
|
||||
<span class="icon-text">
|
||||
<span class="icon"><i :class="icon"></i></span>
|
||||
<span>{{ title }}</span>
|
||||
<span class="icon"><i :class="props.icon"></i></span>
|
||||
<span>{{ props.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>{{ title }}</template>
|
||||
<template v-else>{{ props.title }}</template>
|
||||
</div>
|
||||
<div class="notification-content content" v-if="false === useToggle || toggle">
|
||||
<template v-if="message">{{ message }}</template>
|
||||
|
||||
<div class="notification-content content" v-if="!props.useToggle || props.toggle">
|
||||
<template v-if="props.message">{{ props.message }}</template>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: false
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: false
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: false
|
||||
},
|
||||
message_class: {
|
||||
type: String,
|
||||
default: 'is-info',
|
||||
required: false
|
||||
},
|
||||
useToggle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
toggle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
useClose: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
})
|
||||
<script setup lang="ts">
|
||||
const props = defineProps < {
|
||||
title?: string
|
||||
icon?: string
|
||||
message?: string
|
||||
message_class?: string
|
||||
useToggle?: boolean
|
||||
toggle?: boolean
|
||||
useClose?: boolean
|
||||
} > ()
|
||||
|
||||
defineEmits(['toggle', 'close'])
|
||||
const emit = defineEmits < {
|
||||
(e: 'toggle', value ?: boolean): void
|
||||
(e: 'close'): void
|
||||
}> ()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<div class="column is-12">
|
||||
<label class="label is-inline is-unselectable" for="url">
|
||||
<span class="icon"><i class="fa-solid fa-link" /></span>
|
||||
URLs. separated by <span class="is-bold">{{ getSeparatorsName(separator) }}</span>
|
||||
URLs separated by <span class="is-bold">{{ getSeparatorsName(separator) }}</span>
|
||||
</label>
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded">
|
||||
|
|
@ -69,18 +69,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow" v-if="!config.app.basic_mode"
|
||||
v-tooltip="`Automatically start downloading after adding. [${auto_start ? 'Enabled' : 'Disabled'}]`">
|
||||
<input id="auto_start" type="checkbox" v-model="auto_start" :disabled="addInProgress"
|
||||
class="switch is-success" />
|
||||
<label for="auto_start" class="is-unselectable"></label>
|
||||
</div>
|
||||
|
||||
<div class="column" v-if="!config.app.basic_mode">
|
||||
<button type="button" class="button is-info" @click="showAdvanced = !showAdvanced"
|
||||
:class="{ 'is-loading': !socket.isConnected }" :disabled="!socket.isConnected">
|
||||
<span class="icon"><i class="fa-solid fa-cog" /></span>
|
||||
<span>Options</span>
|
||||
<span>Advanced Options</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -96,42 +89,30 @@
|
|||
|
||||
<div class="columns is-multiline is-mobile" v-if="showAdvanced && !config.app.basic_mode">
|
||||
<div class="column is-4-tablet is-12-mobile" v-if="!config.app.basic_mode">
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<label class="button is-static">
|
||||
<span class="icon"><i class="fa-solid fa-object-ungroup" /></span>
|
||||
<span>Separator</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth">
|
||||
<select class="is-fullwidth" :disabled="!socket.isConnected || addInProgress" v-model="separator">
|
||||
<option v-for="(sep, index) in separators" :key="`sep-${index}`" :value="sep.value">
|
||||
{{ sep.name }} ({{ sep.value }})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input id="auto_start" type="checkbox" v-model="auto_start" :disabled="addInProgress"
|
||||
class="switch is-success" />
|
||||
<label for="auto_start" class="is-unselectable">
|
||||
{{ auto_start ? 'Auto start' : 'Manual start' }}
|
||||
</label>
|
||||
</div>
|
||||
<span class="help is-bold is-unselectable">
|
||||
<span class="help">
|
||||
<span class="icon"><i class="fa-solid fa-info" /></span>
|
||||
<span>Use this to separate multiple URLs in the input field.</span>
|
||||
<span class="is-bold">Whether to start the download automatically or wait.</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="column is-8-tablet is-12-mobile">
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<label for="output_format" v-tooltip="'Default: ' + config.app.output_template"
|
||||
class="button is-static is-unselectable">
|
||||
<label for="output_format" class="button is-static is-unselectable">
|
||||
<span class="icon"><i class="fa-solid fa-file" /></span>
|
||||
<span>Template</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
<input type="text" class="input" v-model="form.template" id="output_format"
|
||||
:disabled="!socket.isConnected || addInProgress"
|
||||
placeholder="Uses default output template naming if empty.">
|
||||
:disabled="!socket.isConnected || addInProgress" :placeholder="get_output_template()">
|
||||
</div>
|
||||
</div>
|
||||
<span class="help is-bold is-unselectable">
|
||||
|
|
@ -251,6 +232,7 @@
|
|||
import 'assets/css/bulma-switch.css'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import type { item_request } from '~/@types/item'
|
||||
import { getSeparatorsName, separators } from '~/utils/utils'
|
||||
|
||||
const props = defineProps<{ item?: Partial<item_request> }>()
|
||||
const emitter = defineEmits<{
|
||||
|
|
@ -261,20 +243,6 @@ const emitter = defineEmits<{
|
|||
const config = useConfigStore()
|
||||
const socket = useSocketStore()
|
||||
const toast = useNotification()
|
||||
const box = useConfirm()
|
||||
|
||||
const separators = [
|
||||
{ name: 'Comma', value: ',', },
|
||||
{ name: 'Semicolon', value: ';', },
|
||||
{ name: 'Colon', value: ':', },
|
||||
{ name: 'Pipe', value: '|', },
|
||||
{ name: 'Space', value: ' ', }
|
||||
]
|
||||
|
||||
const getSeparatorsName = (value: string): string => {
|
||||
const sep = separators.find(s => s.value === value)
|
||||
return sep ? `${sep.name} (${value})` : 'Unknown'
|
||||
}
|
||||
|
||||
const showAdvanced = useStorage<boolean>('show_advanced', false)
|
||||
const separator = useStorage<string>('url_separator', separators[0].value)
|
||||
|
|
@ -384,7 +352,6 @@ const reset_config = () => {
|
|||
} as item_request
|
||||
|
||||
showAdvanced.value = false
|
||||
separator.value = separators[0].value
|
||||
|
||||
toast.success('Local configuration has been reset.')
|
||||
dialog_confirm.value.visible = false
|
||||
|
|
@ -477,4 +444,15 @@ const removeFromArchive = async (url: string) => {
|
|||
toast.error(`Error: ${e.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
const get_output_template = () => {
|
||||
if (form.value.preset && !hasFormatInConfig.value) {
|
||||
const preset = config.presets.find(p => p.name === form.value.preset)
|
||||
if (preset && preset.template) {
|
||||
return preset.template
|
||||
}
|
||||
}
|
||||
return config.app.output_template || '%(title)s.%(ext)s'
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
|
||||
<div class="field">
|
||||
<label class="label is-unselectable" for="random_bg_opacity">
|
||||
Background visibility <code>{{ parseFloat(1.0 - bg_opacity).toFixed(2) }}</code>
|
||||
Background visibility <code>{{ parseFloat(String(1.0 - bg_opacity)).toFixed(2) }}</code>
|
||||
</label>
|
||||
<div class="control">
|
||||
<input id="random_bg_opacity" style="width: 100%" type="range" v-model="bg_opacity" min="0.50"
|
||||
|
|
@ -69,6 +69,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label is-unselectable" for="show_thumbnail">URLs Separator</label>
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select class="is-fullwidth" v-model="separator">
|
||||
<option v-for="(sep, index) in separators" :key="`sep-${index}`" :value="sep.value">
|
||||
{{ sep.name }} ({{ sep.value }})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<div class="field">
|
||||
|
|
@ -126,24 +139,20 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { POSITION } from 'vue-toastification'
|
||||
import { separators } from '~/utils/utils'
|
||||
|
||||
defineProps({
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const bg_enable = useStorage('random_bg', true)
|
||||
const bg_opacity = useStorage('random_bg_opacity', 0.95)
|
||||
const selectedTheme = useStorage('theme', 'auto')
|
||||
const allow_toasts = useStorage('allow_toasts', true)
|
||||
const reduce_confirm = useStorage('reduce_confirm', false)
|
||||
const toast_position = useStorage('toast_position', POSITION.TOP_RIGHT)
|
||||
const toast_dismiss_on_click = useStorage('toast_dismiss_on_click', true)
|
||||
const show_thumbnail = useStorage('show_thumbnail', true)
|
||||
defineProps<{ isLoading: boolean }>()
|
||||
|
||||
const bg_enable = useStorage<boolean>('random_bg', true)
|
||||
const bg_opacity = useStorage<number>('random_bg_opacity', 0.95)
|
||||
const selectedTheme = useStorage<'auto' | 'light' | 'dark'>('theme', 'auto')
|
||||
const allow_toasts = useStorage<boolean>('allow_toasts', true)
|
||||
const reduce_confirm = useStorage<boolean>('reduce_confirm', false)
|
||||
const toast_position = useStorage<POSITION>('toast_position', POSITION.TOP_RIGHT)
|
||||
const toast_dismiss_on_click = useStorage<boolean>('toast_dismiss_on_click', true)
|
||||
const show_thumbnail = useStorage<boolean>('show_thumbnail', true)
|
||||
const separator = useStorage<string>('url_separator', separators[0].value)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -18,27 +18,28 @@
|
|||
</p>
|
||||
|
||||
<p class="control">
|
||||
<button class="button is-danger is-light" v-tooltip.bottom="'Filter'"
|
||||
@click="toggleFilter = !toggleFilter">
|
||||
<button class="button is-danger is-light" @click="toggleFilter = !toggleFilter">
|
||||
<span class="icon"><i class="fas fa-filter" /></span>
|
||||
<span class="is-hidden-mobile">Filter</span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<p class="control" v-if="!config.app.basic_mode && false === config.app.basic_mode">
|
||||
<button class="button is-warning" @click="pauseDownload" v-if="false === config.paused"
|
||||
v-tooltip.bottom="'Pause non-active downloads.'">
|
||||
<button class="button is-warning" @click="pauseDownload" v-if="false === config.paused">
|
||||
<span class="icon"><i class="fas fa-pause" /></span>
|
||||
<span class="is-hidden-mobile">Pause</span>
|
||||
</button>
|
||||
<button class="button is-danger" @click="socket.emit('resume', {})" v-else
|
||||
v-tooltip.bottom="'Resume downloading.'">
|
||||
<span class="icon"><i class="fas fa-play" /></span>
|
||||
<span class="is-hidden-mobile">Resume</span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<p class="control" v-if="!config.app.basic_mode && false === config.app.basic_mode">
|
||||
<button v-tooltip.bottom="'Toggle download form'" class="button is-primary has-tooltip-bottom"
|
||||
@click="config.showForm = !config.showForm">
|
||||
<button class="button is-primary has-tooltip-bottom" @click="config.showForm = !config.showForm">
|
||||
<span class="icon"><i class="fa-solid fa-plus" /></span>
|
||||
<span class="is-hidden-mobile">New Download</span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
|
|
@ -47,6 +48,7 @@
|
|||
@click="() => changeDisplay()">
|
||||
<span class="icon"><i class="fa-solid"
|
||||
:class="{ 'fa-table': display_style === 'cards', 'fa-table-list': display_style === 'list' }" /></span>
|
||||
<span class="is-hidden-mobile">{{ display_style === 'cards' ? 'Cards' : 'List' }}</span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
|
|
@ -70,6 +72,9 @@
|
|||
@getItemInfo="(id: string) => view_info(`/api/history/${id}`, true)" @clear_search="query = ''" />
|
||||
<GetInfo v-if="info_view.url" :link="info_view.url" :preset="info_view.preset" :useUrl="info_view.useUrl"
|
||||
@closeModel="close_info()" />
|
||||
<ConfirmDialog v-if="dialog_confirm.visible" :visible="dialog_confirm.visible" :title="dialog_confirm.title"
|
||||
:html_message="dialog_confirm.html_message" :options="dialog_confirm.options" @confirm="dialog_confirm.confirm"
|
||||
@cancel="() => dialog_confirm.visible = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -91,9 +96,16 @@ const info_view = ref({
|
|||
preset: '',
|
||||
useUrl: false,
|
||||
}) as Ref<{ url: string, preset: string, useUrl: boolean }>
|
||||
const item_form = ref<item_request|object>({})
|
||||
const item_form = ref<item_request | object>({})
|
||||
const query = ref()
|
||||
const toggleFilter = ref(false)
|
||||
const dialog_confirm = ref({
|
||||
visible: false,
|
||||
title: 'Confirm Action',
|
||||
confirm: () => { },
|
||||
html_message: '',
|
||||
options: [],
|
||||
})
|
||||
|
||||
watch(toggleFilter, () => {
|
||||
if (!toggleFilter.value) {
|
||||
|
|
@ -123,12 +135,18 @@ watch(() => stateStore.queue, () => {
|
|||
useHead({ title: `YTPTube: ( ${Object.keys(stateStore.queue).length || 0}/${config.app.max_workers} | ${Object.keys(stateStore.history).length || 0} )` })
|
||||
}, { deep: true })
|
||||
|
||||
const pauseDownload = () => {
|
||||
if (false === box.confirm('Pause all non-active downloads?')) {
|
||||
return false
|
||||
}
|
||||
|
||||
socket.emit('pause', {})
|
||||
const pauseDownload = () => {
|
||||
dialog_confirm.value.visible = true
|
||||
dialog_confirm.value.html_message = `<span class="is-bold">Pause All non-active downloads?</span><br>
|
||||
<span class="has-text-danger">
|
||||
<span class="icon"><i class="fa-solid fa-exclamation-triangle"></i></span>
|
||||
<span>This will not stop downloads that are currently in progress.</span>
|
||||
</span>`
|
||||
dialog_confirm.value.confirm = () => {
|
||||
socket.emit('pause', {})
|
||||
dialog_confirm.value.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
const close_info = () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,26 @@
|
|||
import type { convert_args_response } from "~/@types/responses";
|
||||
|
||||
const separators = [
|
||||
{ name: 'Comma', value: ',', },
|
||||
{ name: 'Semicolon', value: ';', },
|
||||
{ name: 'Colon', value: ':', },
|
||||
{ name: 'Pipe', value: '|', },
|
||||
{ name: 'Space', value: ' ', }
|
||||
]
|
||||
|
||||
/**
|
||||
* Get the name of a separator based on its value
|
||||
*
|
||||
* @param {string} value - The separator value
|
||||
*
|
||||
* @returns {string} The name of the separator, or 'Unknown' if not found
|
||||
*/
|
||||
const getSeparatorsName = (value: string): string => {
|
||||
const sep = separators.find(s => s.value === value)
|
||||
return sep ? `${sep.name} (${value})` : 'Unknown'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert options to JSON
|
||||
*
|
||||
|
|
@ -21,4 +42,4 @@ const convertCliOptions = async (opts: string): Promise<convert_args_response> =
|
|||
return data
|
||||
}
|
||||
|
||||
export { convertCliOptions }
|
||||
export { convertCliOptions, separators, getSeparatorsName }
|
||||
|
|
|
|||
Loading…
Reference in a new issue