working version of custom fields.
This commit is contained in:
parent
45401639ad
commit
00fc97c570
4 changed files with 109 additions and 32 deletions
|
|
@ -70,6 +70,12 @@ class DLField:
|
|||
kind: FieldType = FieldType.TEXT
|
||||
"""The kind of the field. i.e. string, bool"""
|
||||
|
||||
icon: str = ""
|
||||
"""The icon of the field, it can be a font-awesome icon"""
|
||||
|
||||
order: int = 0
|
||||
"""The order of the field, used to sort the fields in the UI."""
|
||||
|
||||
value: str = ""
|
||||
"""The default value of the field, It's currently unused."""
|
||||
|
||||
|
|
@ -254,6 +260,10 @@ class DLFields(metaclass=Singleton):
|
|||
msg = "Value must be a string."
|
||||
raise ValueError(msg)
|
||||
|
||||
if item.get("order") is not None and not isinstance(item.get("order"), int):
|
||||
msg = "Order must be an integer."
|
||||
raise ValueError(msg)
|
||||
|
||||
if not isinstance(item.get("extras", {}), dict):
|
||||
msg = "Extras must be a dictionary."
|
||||
raise ValueError(msg) # noqa: TRY004
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@
|
|||
<section class="modal-card-body">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-12 is-clearfix is-unselectable">
|
||||
<span class="title is-4">
|
||||
<span class="icon-text">
|
||||
<span class="icon"><i class="fas fa-plus" /></span>
|
||||
<span>Custom Fields</span>
|
||||
</span>
|
||||
</span>
|
||||
<div class="is-pulled-right">
|
||||
<div class="field is-grouped">
|
||||
<p class="control">
|
||||
|
|
@ -24,9 +30,13 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-hidden-mobile">
|
||||
<span class="subtitle">The idea behind the custom fields is to allow you to add new fields to the download
|
||||
form to automate some of the yt-dlp options.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-12" v-for="(item, index) in items" :key="item.id || index">
|
||||
<div class="column is-12" v-for="(item, index) in sortedDLFields" :key="item.id || index">
|
||||
<div class="box">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-12 is-12-mobile">
|
||||
|
|
@ -38,7 +48,10 @@
|
|||
|
||||
<div class="column is-6 is-12-mobile">
|
||||
<div class="field">
|
||||
<label class="label">Field Type</label>
|
||||
<label class="label">
|
||||
<span class="icon"><i class="fas fa-cog" /></span>
|
||||
<span>Field Type</span>
|
||||
</label>
|
||||
<div class="select is-fullwidth" :class="{ 'is-loading': isLoading }">
|
||||
<select v-model="item.kind" :disabled="isLoading" class="is-capitalized">
|
||||
<option v-for="kind in Object.values(FieldTypes)" :key="`kind-${kind}`" :value="kind"
|
||||
|
|
@ -46,13 +59,16 @@
|
|||
</select>
|
||||
</div>
|
||||
<span class="help is-bold">
|
||||
Field Type.
|
||||
Field Type. String is a single line input, Text is a multi-line input, Bool is a checkbox.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-6 is-12-mobile">
|
||||
<div class="field">
|
||||
<label class="label">Field Name</label>
|
||||
<label class="label">
|
||||
<span class="icon"><i class="fas fa-font" /></span>
|
||||
<span>Field Name</span>
|
||||
</label>
|
||||
<input type="text" v-model="item.name" class="input" :disabled="isLoading" />
|
||||
<span class="help is-bold">
|
||||
The name of the field, it will be shown in the UI.
|
||||
|
|
@ -61,7 +77,10 @@
|
|||
</div>
|
||||
<div class="column is-6 is-12-mobile">
|
||||
<div class="field">
|
||||
<label class="label">Associated yt-dlp option</label>
|
||||
<label class="label">
|
||||
<span class="icon"><i class="fas fa-terminal" /></span>
|
||||
<span>Associated yt-dlp option</span>
|
||||
</label>
|
||||
<input type="text" v-model="item.field" class="input" :disabled="isLoading" />
|
||||
<span class="help is-bold">
|
||||
The long form of yt-dlp option name, e.g. <code>--no-overwrites</code> not <code>-w</code>.
|
||||
|
|
@ -70,7 +89,10 @@
|
|||
</div>
|
||||
<div class="column is-6 is-12-mobile">
|
||||
<div class="field">
|
||||
<label class="label">Description</label>
|
||||
<label class="label">
|
||||
<span class="icon"><i class="fas fa-info-circle" /></span>
|
||||
<span>Field Description</span>
|
||||
</label>
|
||||
<input type="text" v-model="item.description" class="input" :disabled="isLoading" />
|
||||
<span class="help is-bold">
|
||||
A short description of the field, it will be shown in the UI.
|
||||
|
|
@ -78,34 +100,36 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="column is-6 is-12-mobile">
|
||||
<div class="field">
|
||||
<label class="label is-unselectable" :for="`p-${index}`">Persist the value?</label>
|
||||
|
||||
<input :id="`p-${index}`" type="checkbox" v-model="item.extras.persist" :disabled="isLoading"
|
||||
class="switch is-success" />
|
||||
<label class="label is-unselectable" :for="`p-${index}`">
|
||||
{{ item.extras?.persist ? 'Yes' : 'No' }}
|
||||
<label class="label">
|
||||
<span class="icon"><i class="fas fa-sort-numeric-up" /></span>
|
||||
<span>Field Order</span>
|
||||
</label>
|
||||
<span class="help is-bold">If enabled the value will persist.</span>
|
||||
<input type="number" v-model="item.order" class="input" :disabled="isLoading" />
|
||||
<span class="help is-bold">
|
||||
The order of the field, used to sort the fields in the UI. Lower numbers will appear first.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-6 is-12-mobile">
|
||||
<div class="field">
|
||||
<label class="label">Value</label>
|
||||
<input type="text" v-model="item.value" class="input" :disabled="true" />
|
||||
<label class="label">
|
||||
<span class="icon"><i class="fas fa-image" /></span>
|
||||
<span>Field Icon</span>
|
||||
</label>
|
||||
<input type="text" v-model="item.icon" class="input" :disabled="isLoading" />
|
||||
<span class="help is-bold">
|
||||
This field will be used in the future to store values for select type field.
|
||||
The icon of the field, must be from <NuxtLink
|
||||
href="https://fontawesome.com/icons/image?f=classic&s=solid" target="_blank">
|
||||
font-awesome</NuxtLink> icon. e.g. <code>fa-solid fa-image</code>. Leave empty for no icon.
|
||||
</span>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-12">
|
||||
<Message message_class="has-background-warning-90 has-text-dark" v-if="items.length === 0">
|
||||
<span class="icon">
|
||||
|
|
@ -211,10 +235,11 @@ const addNewField = () => items.value.push({
|
|||
kind: 'string',
|
||||
field: '',
|
||||
value: '',
|
||||
icon: '',
|
||||
order: items.value.reduce((max, item) => Math.max(max, item.order || 1), 0) + 1,
|
||||
extras: {}
|
||||
})
|
||||
|
||||
|
||||
const deleteField = (index: number) => items.value.splice(index, 1)
|
||||
|
||||
const validateItem = (item: DLField, index: number): boolean => {
|
||||
|
|
@ -222,12 +247,17 @@ const validateItem = (item: DLField, index: number): boolean => {
|
|||
const requiredFields = ['name', 'field', 'kind', 'description']
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (!item[field as keyof DLField] || item[field as keyof DLField]?.trim() === '') {
|
||||
if (!item[field as keyof DLField]) {
|
||||
toast.error(`${item.name || index}: Field ${field} is required.`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.order || item.order < 1) {
|
||||
toast.error(`${item.name || index}: Order must be a positive number.`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!Object.values(FieldTypes).includes(item.kind)) {
|
||||
toast.error(`${item.name || index}: Invalid field type: ${item.kind}`)
|
||||
return false
|
||||
|
|
@ -242,4 +272,5 @@ const validateItem = (item: DLField, index: number): boolean => {
|
|||
}
|
||||
|
||||
onMounted(async () => await loadContent())
|
||||
const sortedDLFields = computed(() => items.value.sort((a, b) => (a.order || 0) - (b.order || 0)))
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -149,10 +149,11 @@
|
|||
</DLInput>
|
||||
</div>
|
||||
<template v-if="config.dl_fields.length > 0">
|
||||
<div class="column is-6-tablet is-12-mobile" v-for="(fi, index) in config.dl_fields"
|
||||
<div class="column is-6-tablet is-12-mobile" v-for="(fi, index) in sortedDLFields"
|
||||
:key="fi.id || `dlf-${index}`">
|
||||
<DLInput :id="fi?.id || `dlf-${index}`" :type="fi.kind" :description="fi.description" :label="fi.name"
|
||||
v-model="dlFields[fi.field]" :field="fi.field" />
|
||||
:icon="fi.icon" v-model="dlFields[fi.field]" :field="fi.field"
|
||||
:disabled="!socket.isConnected || addInProgress" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="column is-12 is-hidden-tablet">
|
||||
|
|
@ -254,7 +255,7 @@ const show_description = useStorage<boolean>('show_description', true)
|
|||
|
||||
const addInProgress = ref<boolean>(false)
|
||||
const showFields = ref<boolean>(false)
|
||||
const dlFields: Record<string, any> = ref({})
|
||||
const dlFields = useStorage<Record<string, any>>('dl_fields', {})
|
||||
|
||||
const form = useStorage<item_request>('local_config_v1', {
|
||||
id: null,
|
||||
|
|
@ -278,10 +279,31 @@ const dialog_confirm = ref({
|
|||
const FORCE_FLAG = '--no-download-archive'
|
||||
|
||||
const addDownload = async () => {
|
||||
if (form.value?.cli && '' !== form.value.cli) {
|
||||
const options = await convertOptions(form.value.cli)
|
||||
if (null === options) {
|
||||
return
|
||||
let form_cli = (form.value?.cli || '').trim()
|
||||
|
||||
if (false === config.app.basic_mode) {
|
||||
if (dlFields.value && Object.keys(dlFields.value).length > 0 && config.dl_fields && config.dl_fields.length > 0) {
|
||||
const joined = []
|
||||
for (const [key, value] of Object.entries(dlFields.value)) {
|
||||
if (!config.dl_fields.some(f => f.field === key)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ([undefined, null, '', false].includes(value as any)) {
|
||||
continue
|
||||
}
|
||||
joined.push(true === value ? `${key}` : `${key} ${value}`)
|
||||
}
|
||||
if (joined.length > 0) {
|
||||
form_cli = form_cli ? `${form_cli} ${joined.join(' ')}` : joined.join(' ')
|
||||
}
|
||||
}
|
||||
|
||||
if (form_cli && form_cli.trim()) {
|
||||
const options = await convertOptions(form_cli)
|
||||
if (null === options) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -298,7 +320,7 @@ const addDownload = async () => {
|
|||
folder: config.app.basic_mode ? null : form.value.folder,
|
||||
template: config.app.basic_mode ? null : form.value.template,
|
||||
cookies: config.app.basic_mode ? '' : form.value.cookies,
|
||||
cli: config.app.basic_mode ? null : form.value.cli,
|
||||
cli: config.app.basic_mode ? null : form_cli,
|
||||
auto_start: config.app.basic_mode ? true : auto_start.value
|
||||
} as item_request
|
||||
|
||||
|
|
@ -323,15 +345,20 @@ const addDownload = async () => {
|
|||
return
|
||||
}
|
||||
|
||||
let had_errors = false
|
||||
|
||||
data.forEach((item: Record<string, any>) => {
|
||||
if (false !== item.status) {
|
||||
return
|
||||
}
|
||||
toast.error(`Error: ${item.msg || 'Failed to add download.'}`)
|
||||
had_errors = true
|
||||
})
|
||||
|
||||
form.value.url = ''
|
||||
emitter('clear_form')
|
||||
if (false === had_errors) {
|
||||
form.value.url = ''
|
||||
emitter('clear_form')
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
console.error(e)
|
||||
|
|
@ -357,6 +384,7 @@ const reset_config = () => {
|
|||
folder: '',
|
||||
extras: {},
|
||||
} as item_request
|
||||
dlFields.value = {}
|
||||
|
||||
showAdvanced.value = false
|
||||
|
||||
|
|
@ -484,4 +512,6 @@ const forceDownload = computed({
|
|||
}
|
||||
},
|
||||
})
|
||||
|
||||
const sortedDLFields = computed(() => config.dl_fields.sort((a, b) => (a.order || 0) - (b.order || 0)))
|
||||
</script>
|
||||
|
|
|
|||
6
ui/app/types/dl_fields.d.ts
vendored
6
ui/app/types/dl_fields.d.ts
vendored
|
|
@ -16,6 +16,12 @@ type DLField = {
|
|||
/** The kind of the field. i.e. string, bool */
|
||||
kind: DLFieldType;
|
||||
|
||||
/** The icon of the field, it can be a font-awesome icon */
|
||||
icon?: string;
|
||||
|
||||
/** The order of the field, used to sort the fields in the UI. */
|
||||
order: number = 1;
|
||||
|
||||
/** The default value of the field, It's currently unused */
|
||||
value: string;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue