added export to tasks.
This commit is contained in:
parent
ada7b5db97
commit
b5803f94b1
4 changed files with 213 additions and 28 deletions
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
|
|
@ -43,6 +43,19 @@
|
|||
"YTP_ACCESS_LOG": "true",
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Node: Generate UI",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"generate"
|
||||
],
|
||||
"runtimeExecutable": "yarn",
|
||||
"type": "node",
|
||||
"cwd": "${workspaceFolder}/ui",
|
||||
"console": "internalConsole",
|
||||
"outputCapture": "std",
|
||||
},
|
||||
{
|
||||
"name": "Python: main.py (Deep)",
|
||||
"type": "debugpy",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,41 @@
|
|||
<template>
|
||||
<main class="columns mt-2">
|
||||
<div class="column">
|
||||
<form id="taskForm" @submit.prevent="checkInfo()" class="box">
|
||||
<main class="columns mt-2 is-multiline">
|
||||
|
||||
<div class="column is-12">
|
||||
<h1 class="is-pointer is-unselectable title is-5" @click="importExpanded = !importExpanded">
|
||||
<span class="icon-text">
|
||||
<span class="icon"><i class="fa-solid" :class="importExpanded ? 'fa-arrow-up' : 'fa-arrow-down'" /></span>
|
||||
<span>Import task.</span>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<form id="importForm" @submit.prevent="importTask()" v-if="importExpanded">
|
||||
<div class="box">
|
||||
<label class="label" for="import_string">
|
||||
Base64 encoded JSON string.
|
||||
</label>
|
||||
|
||||
<div class="field has-addons">
|
||||
|
||||
<div class="control has-icons-left is-expanded">
|
||||
<input type="text" class="input" id="import_string" v-model="import_string" autocomplete="off">
|
||||
<span class="icon is-small is-left"><i class="fa-solid fa-t" /></span>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button form="importForm" class="button is-primary" :disabled="!import_string" type="submit">
|
||||
<span class="icon"><i class="fa-solid fa-add" /></span>
|
||||
<span>Import</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="column is-12">
|
||||
<form autocomplete="off" id="taskForm" @submit.prevent="checkInfo()" class="box">
|
||||
<div class="columns is-multiline is-mobile">
|
||||
<div class="column is-12">
|
||||
<h1 class="title is-6" style="border-bottom: 1px solid #dbdbdb;">
|
||||
|
|
@ -40,23 +74,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-6-tablet is-12-mobile">
|
||||
<div class="field">
|
||||
<label class="label is-inline" for="folder">
|
||||
Download path
|
||||
</label>
|
||||
<div class="control has-icons-left">
|
||||
<input type="text" class="input" id="folder" placeholder="Leave empty to use default download path"
|
||||
v-model="form.folder" :disabled="addInProgress" list="folders">
|
||||
<span class="icon is-small is-left"><i class="fa-solid fa-folder" /></span>
|
||||
</div>
|
||||
<span class="help">
|
||||
<span class="icon"><i class="fa-solid fa-info" /></span>
|
||||
<span>Downloads are relative to download path, defaults to root path if not set.</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-6-tablet is-12-mobile">
|
||||
<div class="field">
|
||||
<label class="label is-inline" for="folder">
|
||||
|
|
@ -104,6 +121,23 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-6-tablet is-12-mobile" v-if="showAdvanced">
|
||||
<div class="field">
|
||||
<label class="label is-inline" for="folder">
|
||||
Download path
|
||||
</label>
|
||||
<div class="control has-icons-left">
|
||||
<input type="text" class="input" id="folder" placeholder="Leave empty to use default download path"
|
||||
v-model="form.folder" :disabled="addInProgress" list="folders">
|
||||
<span class="icon is-small is-left"><i class="fa-solid fa-folder" /></span>
|
||||
</div>
|
||||
<span class="help">
|
||||
<span class="icon"><i class="fa-solid fa-info" /></span>
|
||||
<span>Downloads are relative to download path, defaults to root path if not set.</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-6-tablet is-12-mobile" v-if="showAdvanced">
|
||||
<div class="field">
|
||||
<label class="label is-inline" for="output_template">
|
||||
|
|
@ -179,7 +213,7 @@
|
|||
<p class="control">
|
||||
<button class="button is-info" type="button" @click="showAdvanced = !showAdvanced">
|
||||
<span class="icon"><i class="fa-solid fa-cog" /></span>
|
||||
<span>Opts</span>
|
||||
<span>Advanced</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -196,13 +230,14 @@
|
|||
<script setup>
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { parseExpression } from 'cron-parser'
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const emitter = defineEmits(['cancel', 'submit']);
|
||||
const toast = useToast();
|
||||
const config = useConfigStore();
|
||||
const convertInProgress = ref(false);
|
||||
const showAdvanced = useStorage('task.showAdvanced', false);
|
||||
const importExpanded = ref(false);
|
||||
const import_string = ref('');
|
||||
|
||||
const props = defineProps({
|
||||
reference: {
|
||||
|
|
@ -311,4 +346,66 @@ const hasFormatInConfig = computed(() => {
|
|||
return false
|
||||
}
|
||||
})
|
||||
|
||||
const importTask = async () => {
|
||||
let val = import_string.value.trim()
|
||||
if (!val) {
|
||||
toast.error('The preset import string is required.')
|
||||
return
|
||||
}
|
||||
|
||||
if (false === val.startsWith('{')) {
|
||||
try {
|
||||
val = base64UrlDecode(val)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error('Invalid base64 string.')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const task = JSON.parse(val)
|
||||
|
||||
if (form.config || form.url || form.timer) {
|
||||
if (false === confirm('This will overwrite the current form fields. Are you sure?')) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (task.name) {
|
||||
form.name = task.name
|
||||
}
|
||||
|
||||
if (task.url) {
|
||||
form.url = task.url
|
||||
}
|
||||
|
||||
if (task.preset) {
|
||||
form.preset = task.preset
|
||||
}
|
||||
|
||||
if (task.timer) {
|
||||
form.timer = task.timer
|
||||
}
|
||||
|
||||
if (task.folder) {
|
||||
form.folder = task.folder
|
||||
}
|
||||
|
||||
if (task.template) {
|
||||
form.template = task.template
|
||||
}
|
||||
|
||||
if (task.config) {
|
||||
form.config = JSON.stringify(task.config, null, 2)
|
||||
}
|
||||
|
||||
import_string.value = ''
|
||||
importExpanded.value = false
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error(`Failed to import task. ${e.message}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ div.is-centered {
|
|||
<div class="card-header-title is-text-overflow is-block" v-text="item.name" />
|
||||
<div class="card-header-icon">
|
||||
<a class="has-text-primary" v-tooltip="'Export preset.'" @click.prevent="copyItem(item)">
|
||||
<span class="icon"><i class="fa-solid fa-copy" /></span>
|
||||
<span class="icon"><i class="fa-solid fa-file-export" /></span>
|
||||
</a>
|
||||
<button @click="item.raw = !item.raw">
|
||||
<span class="icon"><i class="fa-solid" :class="{
|
||||
|
|
@ -120,8 +120,7 @@ div.is-centered {
|
|||
<Message message_class="has-background-info-90 has-text-dark" title="Tips" icon="fas fa-info-circle">
|
||||
<ul>
|
||||
<li>
|
||||
When you export the preset, it doesn't include <code>Cookies</code> field for security reasons. The exported
|
||||
string is URL safe base64 encoded JSON string.
|
||||
When you export preset, it doesn't include <code>Cookies</code> field for security reasons.
|
||||
</li>
|
||||
</ul>
|
||||
</Message>
|
||||
|
|
|
|||
|
|
@ -59,9 +59,9 @@ div.is-centered {
|
|||
<NuxtLink target="_blank" :href="item.url">{{ item.name }}</NuxtLink>
|
||||
</div>
|
||||
<div class="card-header-icon">
|
||||
<a :href="item.url" class="has-text-primary" v-tooltip="'Copy url.'"
|
||||
@click.prevent="copyText(item.url)">
|
||||
<span class="icon"><i class="fa-solid fa-copy" /></span>
|
||||
<a :href="item.url" class="has-text-primary" v-tooltip="'Export task.'"
|
||||
@click.prevent="exportTask(item)">
|
||||
<span class="icon"><i class="fa-solid fa-file-export" /></span>
|
||||
</a>
|
||||
<button @click="item.raw = !item.raw">
|
||||
<span class="icon"><i class="fa-solid"
|
||||
|
|
@ -121,6 +121,17 @@ div.is-centered {
|
|||
icon="fas fa-exclamation-circle" v-if="!tasks || tasks.length < 1" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<Message message_class="has-background-info-90 has-text-dark" title="Tips" icon="fas fa-info-circle">
|
||||
<ul>
|
||||
<li>
|
||||
When you export task, the preset settings is automatically merged into the task and the preset set to
|
||||
<code>default</code> to be more portable. The exporter doesn't include <code>Cookies</code> field for
|
||||
security reasons.
|
||||
</li>
|
||||
</ul>
|
||||
</Message>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -347,4 +358,69 @@ const statusHandler = async stream => {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
const exportTask = async task => {
|
||||
let preset = config.presets.find(p => p.name === task.preset)
|
||||
if (!preset) {
|
||||
toast.error('Preset not found.')
|
||||
return
|
||||
}
|
||||
|
||||
const info = JSON.parse(JSON.stringify(task))
|
||||
preset = JSON.parse(JSON.stringify(preset))
|
||||
|
||||
let data = {
|
||||
name: info.name,
|
||||
url: info.url,
|
||||
preset: 'default',
|
||||
timer: info.timer,
|
||||
folder: info.folder,
|
||||
template: info.template,
|
||||
config: info.config,
|
||||
}
|
||||
|
||||
// -- merge preset options with task args.
|
||||
let args = {}
|
||||
|
||||
if (preset.args && Object.keys(preset.args).length > 0) {
|
||||
for (const key of Object.keys(preset.args)) {
|
||||
args[key] = preset.args[key]
|
||||
}
|
||||
}
|
||||
|
||||
if (preset.format !== 'not_set' && preset.format) {
|
||||
args.format = preset.format
|
||||
}
|
||||
|
||||
if (preset.postprocessors && preset.postprocessors.length > 0) {
|
||||
args.postprocessors = preset.postprocessors
|
||||
}
|
||||
|
||||
if (preset.folder && !info.folder) {
|
||||
data.folder = preset.folder
|
||||
}
|
||||
|
||||
if (preset.template && !info.template) {
|
||||
data.template = preset.template
|
||||
}
|
||||
|
||||
if (!data.config || Object.keys(data.config).length < 1) {
|
||||
data.config = {}
|
||||
}
|
||||
|
||||
for (const key of Object.keys(args)) {
|
||||
if (key in data.config && Array.isArray(args[key]) && Array.isArray(data.config[key])) {
|
||||
data.config[key] = data.config[key].concat(args[key])
|
||||
continue
|
||||
}
|
||||
|
||||
if (data?.config[key]) {
|
||||
continue
|
||||
}
|
||||
|
||||
data.config[key] = args[key]
|
||||
}
|
||||
|
||||
return copyText(base64UrlEncode(JSON.stringify(data)));
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue