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:
commit
72de23f784
2 changed files with 156 additions and 3 deletions
|
|
@ -57,6 +57,33 @@ class Preset(BaseModel):
|
||||||
if not value:
|
if not value:
|
||||||
return ""
|
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:
|
try:
|
||||||
from app.features.ytdlp.utils import arg_converter
|
from app.features.ytdlp.utils import arg_converter
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -164,8 +164,43 @@
|
||||||
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
||||||
<span>Command options for yt-dlp</span>
|
<span>Command options for yt-dlp</span>
|
||||||
</label>
|
</label>
|
||||||
<TextareaAutocomplete id="cli_options" v-model="form.cli" :options="ytDlpOpt"
|
<div class="columns is-multiline is-mobile">
|
||||||
:disabled="addInProgress" />
|
<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="help is-bold">
|
||||||
<span class="icon"><i class="fa-solid fa-info" /></span>
|
<span class="icon"><i class="fa-solid fa-info" /></span>
|
||||||
<span>
|
<span>
|
||||||
|
|
@ -251,8 +286,8 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import TextareaAutocomplete from '~/components/TextareaAutocomplete.vue'
|
|
||||||
import TextDropzone from '~/components/TextDropzone.vue'
|
import TextDropzone from '~/components/TextDropzone.vue'
|
||||||
|
import InputAutocomplete from '~/components/InputAutocomplete.vue'
|
||||||
import type { ImportedItem } from '~/types';
|
import type { ImportedItem } from '~/types';
|
||||||
import type { AutoCompleteOptions } from '~/types/autocomplete';
|
import type { AutoCompleteOptions } from '~/types/autocomplete';
|
||||||
import type { Preset } from '~/types/presets'
|
import type { Preset } from '~/types/presets'
|
||||||
|
|
@ -280,6 +315,92 @@ const showOptions = ref<boolean>(false)
|
||||||
const ytDlpOpt = ref<AutoCompleteOptions>([])
|
const ytDlpOpt = ref<AutoCompleteOptions>([])
|
||||||
const cookiesDropzoneRef = ref<InstanceType<typeof TextDropzone> | null>(null)
|
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) {
|
if (form.priority === undefined) {
|
||||||
form.priority = 0
|
form.priority = 0
|
||||||
}
|
}
|
||||||
|
|
@ -312,6 +433,8 @@ const checkInfo = async (): Promise<void> => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.cli = cliToString(cliOptions.value)
|
||||||
|
|
||||||
if (form.cli && '' !== form.cli) {
|
if (form.cli && '' !== form.cli) {
|
||||||
const options = await convertOptions(form.cli)
|
const options = await convertOptions(form.cli)
|
||||||
if (null === options) {
|
if (null === options) {
|
||||||
|
|
@ -388,6 +511,7 @@ const importItem = async (): Promise<void> => {
|
||||||
|
|
||||||
if (item.cli) {
|
if (item.cli) {
|
||||||
form.cli = item.cli
|
form.cli = item.cli
|
||||||
|
initCliOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.template) {
|
if (item.template) {
|
||||||
|
|
@ -434,6 +558,8 @@ const import_existing_preset = async (): Promise<void> => {
|
||||||
form.description = preset.description || ''
|
form.description = preset.description || ''
|
||||||
form.priority = preset.priority ?? 0
|
form.priority = preset.priority ?? 0
|
||||||
|
|
||||||
|
initCliOptions()
|
||||||
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
selected_preset.value = ''
|
selected_preset.value = ''
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue