Merge pull request #1 from schweppes-0x/feature/improve-presets-ui

improve the current Presets UI by splitting the options into key:value pairs
This commit is contained in:
schweppes 2026-02-15 13:50:52 +01:00 committed by GitHub
commit 72de23f784
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 156 additions and 3 deletions

View file

@ -57,6 +57,33 @@ class Preset(BaseModel):
if not value:
return ""
import shlex
try:
tokens = shlex.split(value, posix=True)
processed_tokens = []
prev_was_option = False
for token in tokens:
if token.startswith("-"):
processed_tokens.append(token)
prev_was_option = True
elif "=" in token:
key, val = token.split("=", 1)
if not key.startswith("-"):
key = "--" + key
processed_tokens.append(f"{key}={val}")
prev_was_option = False
elif prev_was_option:
processed_tokens.append(token)
prev_was_option = False
else:
processed_tokens.append("--" + token)
prev_was_option = False
value = " ".join(processed_tokens)
except ValueError:
pass
try:
from app.features.ytdlp.utils import arg_converter

View file

@ -164,8 +164,43 @@
<span class="icon"><i class="fa-solid fa-terminal" /></span>
<span>Command options for yt-dlp</span>
</label>
<TextareaAutocomplete id="cli_options" v-model="form.cli" :options="ytDlpOpt"
:disabled="addInProgress" />
<div class="columns is-multiline is-mobile">
<template v-for="(item, index) in cliOptions" :key="index">
<div class="column is-5">
<div class="field">
<div class="control has-icons-left">
<InputAutocomplete v-model="item.key" :options="ytDlpOpt" :disabled="addInProgress"
placeholder="--option" :multiple="false" :openOnFocus="true" />
<span class="icon is-small is-left"><i class="fa-solid fa-key" /></span>
</div>
</div>
</div>
<div class="column is-6">
<div class="field">
<div class="control has-icons-left">
<input type="text" class="input" v-model="item.value" :disabled="addInProgress"
placeholder="value">
<span class="icon is-small is-left"><i class="fa-solid fa-v" /></span>
</div>
</div>
</div>
<div class="column is-1">
<div class="control">
<button type="button" class="button is-danger" @click="removeCliOption(index)"
:disabled="addInProgress">
<span class="icon"><i class="fa-solid fa-trash" /></span>
</button>
</div>
</div>
</template>
<div class="column is-12">
<button type="button" class="button is-link" @click="addCliOption"
:disabled="addInProgress">
<span class="icon"><i class="fa-solid fa-plus" /></span>
<span>Add Option</span>
</button>
</div>
</div>
<span class="help is-bold">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>
@ -251,8 +286,8 @@
<script setup lang="ts">
import { useStorage } from '@vueuse/core'
import TextareaAutocomplete from '~/components/TextareaAutocomplete.vue'
import TextDropzone from '~/components/TextDropzone.vue'
import InputAutocomplete from '~/components/InputAutocomplete.vue'
import type { ImportedItem } from '~/types';
import type { AutoCompleteOptions } from '~/types/autocomplete';
import type { Preset } from '~/types/presets'
@ -280,6 +315,92 @@ const showOptions = ref<boolean>(false)
const ytDlpOpt = ref<AutoCompleteOptions>([])
const cookiesDropzoneRef = ref<InstanceType<typeof TextDropzone> | null>(null)
interface CliOption {
key: string
value: string
}
const cliOptions = ref<CliOption[]>([])
const parseCliString = (cli: string): CliOption[] => {
if (!cli || !cli.trim()) {
return []
}
const options: CliOption[] = []
const tokens = cli.trim().split(/\s+/)
let i = 0
while (i < tokens.length) {
const token = tokens[i]
if (!token) {
i++
continue
}
if (token.startsWith('--')) {
const equalsIndex = token.indexOf('=')
if (equalsIndex !== -1) {
const key = token.slice(2)
const value = token.slice(equalsIndex + 1)
options.push({ key, value })
} else {
const nextToken = tokens[i + 1]
if (nextToken && !nextToken.startsWith('-')) {
options.push({ key: token.slice(2), value: nextToken })
i++
} else {
options.push({ key: token.slice(2), value: '' })
}
}
} else if (token.startsWith('-')) {
options.push({ key: token.slice(1), value: '' })
}
i++
}
return options
}
const cliToString = (options: CliOption[]): string => {
return options
.filter(opt => opt.key.trim())
.map(opt => {
let key = opt.key.trim()
if (!key.startsWith('-')) {
key = '--' + key
}
const value = opt.value.trim()
if (!value) {
return key
}
if (value.includes(' ') || value.includes('"')) {
return `${key} "${value.replace(/"/g, '\\"')}"`
}
return `${key} ${value}`
})
.join(' ')
}
const addCliOption = () => {
cliOptions.value.push({ key: '', value: '' })
}
const removeCliOption = (index: number) => {
cliOptions.value.splice(index, 1)
}
const initCliOptions = () => {
cliOptions.value = parseCliString(form.cli || '')
}
initCliOptions()
watch(() => props.preset, () => {
initCliOptions()
}, { immediate: false })
if (form.priority === undefined) {
form.priority = 0
}
@ -312,6 +433,8 @@ const checkInfo = async (): Promise<void> => {
await nextTick()
}
form.cli = cliToString(cliOptions.value)
if (form.cli && '' !== form.cli) {
const options = await convertOptions(form.cli)
if (null === options) {
@ -388,6 +511,7 @@ const importItem = async (): Promise<void> => {
if (item.cli) {
form.cli = item.cli
initCliOptions()
}
if (item.template) {
@ -434,6 +558,8 @@ const import_existing_preset = async (): Promise<void> => {
form.description = preset.description || ''
form.priority = preset.priority ?? 0
initCliOptions()
await nextTick()
selected_preset.value = ''
}