migrated more code to ts
This commit is contained in:
parent
4e1a1e65ed
commit
dcaeb7acc8
9 changed files with 249 additions and 263 deletions
|
|
@ -198,7 +198,7 @@
|
||||||
</label>
|
</label>
|
||||||
<div class="columns is-multiline is-mobile">
|
<div class="columns is-multiline is-mobile">
|
||||||
<template v-for="_, key in form.request.headers" :key="key">
|
<template v-for="_, key in form.request.headers" :key="key">
|
||||||
<div class="column is-5">
|
<div class="column is-5" v-if="form.request.headers[key]">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control has-icons-left">
|
<div class="control has-icons-left">
|
||||||
<input type="text" class="input" v-model="form.request.headers[key].key"
|
<input type="text" class="input" v-model="form.request.headers[key].key"
|
||||||
|
|
@ -211,7 +211,7 @@
|
||||||
<span>The header key to send with the notification.</span>
|
<span>The header key to send with the notification.</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-6">
|
<div class="column is-6" v-if="form.request.headers[key]">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control has-icons-left">
|
<div class="control has-icons-left">
|
||||||
<input type="text" class="input" v-model="form.request.headers[key].value"
|
<input type="text" class="input" v-model="form.request.headers[key].value"
|
||||||
|
|
@ -267,11 +267,14 @@
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import type { notification, notificationImport } from '~/types/notification'
|
||||||
|
|
||||||
|
const emitter = defineEmits(['cancel', 'submit'])
|
||||||
|
const toast = useNotification()
|
||||||
|
const box = useConfirm()
|
||||||
|
|
||||||
const emitter = defineEmits(['cancel', 'submit']);
|
|
||||||
const toast = useNotification();
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
reference: {
|
reference: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -279,11 +282,11 @@ const props = defineProps({
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
allowedEvents: {
|
allowedEvents: {
|
||||||
type: Array,
|
type: Array as () => string[],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object as () => notification,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
addInProgress: {
|
addInProgress: {
|
||||||
|
|
@ -293,71 +296,75 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const form = reactive(props.item);
|
const form = reactive<notification>({ ...props.item })
|
||||||
const requestMethods = ['POST', 'PUT'];
|
const requestMethods = ['POST', 'PUT']
|
||||||
const requestType = ['json', 'form'];
|
const requestType = ['json', 'form']
|
||||||
const showImport = useStorage('showImport', false);
|
const showImport = useStorage('showImport', false)
|
||||||
const import_string = ref('');
|
const import_string = ref('')
|
||||||
const box = useConfirm()
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!form.request.data_key) {
|
if (!form.request.data_key) {
|
||||||
form.request.data_key = 'data';
|
form.request.data_key = 'data'
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
const checkInfo = async () => {
|
const checkInfo = async () => {
|
||||||
let required;
|
let required: string[]
|
||||||
|
|
||||||
if (!isApprise.value) {
|
if (!isApprise.value) {
|
||||||
required = ['name', 'request.url', 'request.method', 'request.type', 'request.data_key'];
|
required = ['name', 'request.url', 'request.method', 'request.type', 'request.data_key']
|
||||||
} else {
|
} else {
|
||||||
required = ['name', 'request.url'];
|
required = ['name', 'request.url']
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of required) {
|
for (const key of required) {
|
||||||
if (key.includes('.')) {
|
if (key.includes('.')) {
|
||||||
const [parent, child] = key.split('.');
|
const [parent, child] = key.split('.') as [keyof typeof form, string]
|
||||||
if (!form[parent][child]) {
|
const parentObj = form[parent] as Record<string, any> | undefined
|
||||||
toast.error(`The field ${parent}.${child} is required.`);
|
|
||||||
return;
|
if (!parentObj || !parentObj[child]) {
|
||||||
|
toast.error(`The field ${parent}.${child} is required.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const value = (form as Record<string, any>)[key]
|
||||||
|
if (!value) {
|
||||||
|
toast.error(`The field ${key} is required.`)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else if (!form[key]) {
|
|
||||||
toast.error(`The field ${key} is required.`);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isApprise.value) {
|
if (!isApprise.value) {
|
||||||
try {
|
try {
|
||||||
new URL(form.request.url);
|
new URL(form.request.url)
|
||||||
} catch (e) {
|
} catch (_) {
|
||||||
toast.error('Invalid URL');
|
toast.error('Invalid URL')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = []
|
const headers = []
|
||||||
|
|
||||||
for (const header of form.request.headers) {
|
for (const header of form.request.headers) {
|
||||||
if (!header.key || !header.value) {
|
if (!header.key || !header.value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
headers.push({ key: String(header.key).trim(), value: String(header.value).trim() })
|
headers.push({ key: String(header.key).trim(), value: String(header.value).trim() })
|
||||||
}
|
}
|
||||||
|
form.request.headers = headers
|
||||||
|
|
||||||
form.request.headers = headers;
|
emitter('submit', { reference: toRaw(props.reference), item: toRaw(form) })
|
||||||
emitter('submit', { reference: toRaw(props.reference), item: toRaw(form) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const importItem = async () => {
|
const importItem = async () => {
|
||||||
let val = import_string.value.trim()
|
const val = import_string.value.trim()
|
||||||
if (!val) {
|
if (!val) {
|
||||||
toast.error('The import string is required.')
|
toast.error('The import string is required.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item = decode(val)
|
const item = decode(val) as notificationImport
|
||||||
|
|
||||||
if ('notification' !== item._type) {
|
if ('notification' !== item._type) {
|
||||||
toast.error(`Invalid import string. Expected type 'notification', got '${item._type}'.`)
|
toast.error(`Invalid import string. Expected type 'notification', got '${item._type}'.`)
|
||||||
|
|
@ -365,7 +372,7 @@ const importItem = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (form.target) {
|
if (form.name || form.request?.url) {
|
||||||
if (false === box.confirm('Overwrite the current form fields?', true)) {
|
if (false === box.confirm('Overwrite the current form fields?', true)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -375,24 +382,25 @@ const importItem = async () => {
|
||||||
form.name = item.name
|
form.name = item.name
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.url) {
|
if (!form.request) {
|
||||||
form.url = item.url
|
form.request = {} as any
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.request) {
|
if (item.request) {
|
||||||
form.request = item.request
|
form.request = item.request
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.data_key) {
|
if (item.request?.data_key) {
|
||||||
form.data_key = item.data_key
|
form.request.data_key = item.request.data_key
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.on) {
|
if (item.on) {
|
||||||
form.on = item.on
|
form.on = item.on
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
import_string.value = ''
|
import_string.value = ''
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast.error(`Failed to import task. ${e.message}`)
|
toast.error(`Failed to import task. ${e.message}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,46 @@
|
||||||
|
<style scoped>
|
||||||
|
.notification-item {
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-info {
|
||||||
|
border-color: var(--bulma-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-success {
|
||||||
|
border-color: var(--bulma-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-warning {
|
||||||
|
border-color: var(--bulma-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-error {
|
||||||
|
border-color: var(--bulma-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-list {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
cursor: pointer;
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message.expanded {
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
<a class="navbar-link">
|
<a class="navbar-link">
|
||||||
|
|
@ -73,64 +116,21 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
const store = useNotificationStore()
|
const store = useNotificationStore()
|
||||||
const copiedId = ref(null)
|
|
||||||
const expandedId = ref(null)
|
|
||||||
|
|
||||||
const toggleExpand = id => expandedId.value = expandedId.value === id ? null : id
|
const copiedId = ref<string | null>(null)
|
||||||
|
const expandedId = ref<string | null>(null)
|
||||||
|
|
||||||
const copy_text = (id, text) => {
|
const toggleExpand = (id: string) => expandedId.value = expandedId.value === id ? null : id
|
||||||
|
|
||||||
|
const copy_text = (id: string, text: string): void => {
|
||||||
copiedId.value = id
|
copiedId.value = id
|
||||||
copyText(text, false, false)
|
copyText(text, false, false)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (copiedId.value === id) {
|
if (copiedId.value === id) copiedId.value = null
|
||||||
copiedId.value = null
|
|
||||||
}
|
|
||||||
}, 2000)
|
}, 2000)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.notification-item {
|
|
||||||
border-left: 4px solid transparent;
|
|
||||||
padding-left: 0.75rem;
|
|
||||||
border-bottom: 1px solid #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-info {
|
|
||||||
border-color: var(--bulma-info);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-success {
|
|
||||||
border-color: var(--bulma-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-warning {
|
|
||||||
border-color: var(--bulma-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-error {
|
|
||||||
border-color: var(--bulma-danger);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-list {
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-message {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
cursor: pointer;
|
|
||||||
max-width: 280px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-message.expanded {
|
|
||||||
white-space: normal;
|
|
||||||
word-break: break-word;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -230,102 +230,74 @@
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import type { Preset, PresetImport } from '~/types/presets'
|
||||||
|
|
||||||
const emitter = defineEmits(['cancel', 'submit']);
|
const emitter = defineEmits<{
|
||||||
|
(event: 'cancel'): void
|
||||||
|
(event: 'submit', payload: { reference: string | null, preset: Preset }): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<{
|
||||||
reference: {
|
reference?: string | null
|
||||||
type: String,
|
preset: Preset
|
||||||
required: false,
|
addInProgress?: boolean
|
||||||
default: null,
|
presets?: Preset[]
|
||||||
},
|
}>()
|
||||||
preset: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
addInProgress: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
presets: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
const form = reactive(JSON.parse(JSON.stringify(props.preset)))
|
const form = reactive<Preset>(JSON.parse(JSON.stringify(props.preset)))
|
||||||
const import_string = ref('')
|
const import_string = ref<string>('')
|
||||||
const showImport = useStorage('showImport', false)
|
const showImport = useStorage<boolean>('showImport', false)
|
||||||
const box = useConfirm()
|
const selected_preset = ref<string>('')
|
||||||
const selected_preset = ref('')
|
|
||||||
|
|
||||||
onMounted(() => {
|
const checkInfo = async (): Promise<void> => {
|
||||||
if (props.preset?.cli && '' !== props.preset?.cli) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.preset?.args && (typeof props.preset.args === 'object')) {
|
|
||||||
form.args = JSON.stringify(props.preset.args, null, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.preset?.postprocessors && (typeof props.preset.postprocessors === 'object')) {
|
|
||||||
form.postprocessors = JSON.stringify(props.preset.postprocessors, null, 2)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const checkInfo = async () => {
|
|
||||||
for (const key of ['name']) {
|
for (const key of ['name']) {
|
||||||
if (!form[key]) {
|
if (!form[key as keyof Preset]) {
|
||||||
toast.error(`The ${key} field is required.`);
|
toast.error(`The ${key} field is required.`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
form.cli = form.cli.trim()
|
form.cli = form.cli.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
let copy = JSON.parse(JSON.stringify(form));
|
const copy: Preset = JSON.parse(JSON.stringify(form))
|
||||||
|
let usedName = false
|
||||||
|
const name = String(form.name).trim().toLowerCase()
|
||||||
|
|
||||||
let usedName = false;
|
props.presets?.forEach(p => {
|
||||||
let name = String(form.name).trim().toLowerCase();
|
|
||||||
|
|
||||||
props.presets.forEach(p => {
|
|
||||||
if (p.id === props.reference) {
|
if (p.id === props.reference) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (String(p.name).toLowerCase() === name) {
|
if (String(p.name).toLowerCase() === name) {
|
||||||
usedName = true;
|
usedName = true
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
if (true === usedName) {
|
if (usedName) {
|
||||||
toast.error('The preset name is already in use.');
|
toast.error('The preset name is already in use.')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in copy) {
|
for (const key in copy) {
|
||||||
if (typeof copy[key] !== 'string') {
|
const val = copy[key as keyof Preset]
|
||||||
continue
|
if ('string' === typeof val) {
|
||||||
|
(copy as any)[key] = val.trim()
|
||||||
}
|
}
|
||||||
copy[key] = copy[key].trim()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter('submit', { reference: toRaw(props.reference), preset: toRaw(copy) });
|
emitter('submit', { reference: toRaw(props.reference ?? null), preset: toRaw(copy) })
|
||||||
}
|
}
|
||||||
|
|
||||||
const convertOptions = async args => {
|
const convertOptions = async (args: string): Promise<Record<string, any> | null> => {
|
||||||
try {
|
try {
|
||||||
const response = await convertCliOptions(args)
|
const response = await convertCliOptions(args)
|
||||||
|
|
||||||
|
|
@ -337,23 +309,22 @@ const convertOptions = async args => {
|
||||||
form.folder = response.download_path
|
form.folder = response.download_path
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.opts
|
return response.opts as Record<string, any>
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
toast.error(e.message)
|
toast.error(e.message)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const importItem = async () => {
|
const importItem = async (): Promise<void> => {
|
||||||
let val = import_string.value.trim()
|
const val = import_string.value.trim()
|
||||||
if (!val) {
|
if (!val) {
|
||||||
toast.error('The import string is required.')
|
toast.error('The import string is required.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item = decode(val)
|
const item = decode(val) as PresetImport
|
||||||
|
|
||||||
if (!item?._type || 'preset' !== item._type) {
|
if (!item?._type || 'preset' !== item._type) {
|
||||||
toast.error(`Invalid import string. Expected type 'preset', got '${item._type ?? 'unknown'}'.`)
|
toast.error(`Invalid import string. Expected type 'preset', got '${item._type ?? 'unknown'}'.`)
|
||||||
|
|
@ -368,16 +339,6 @@ const importItem = async () => {
|
||||||
form.cli = item.cli
|
form.cli = item.cli
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- backwards compatibility for old presets.
|
|
||||||
if (item.format) {
|
|
||||||
if (!item?.cli) {
|
|
||||||
form.cli = `--format '${item.format}'`
|
|
||||||
} else {
|
|
||||||
form.cli = `--format '${item.format}'\n${form.cli}`
|
|
||||||
}
|
|
||||||
form.cli = form.cli.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.template) {
|
if (item.template) {
|
||||||
form.template = item.template
|
form.template = item.template
|
||||||
}
|
}
|
||||||
|
|
@ -392,15 +353,15 @@ const importItem = async () => {
|
||||||
|
|
||||||
import_string.value = ''
|
import_string.value = ''
|
||||||
showImport.value = false
|
showImport.value = false
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast.error(`Failed to parse. ${e.message}`)
|
toast.error(`Failed to parse. ${e.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filter_presets = (flag = true) => config.presets.filter(item => item.default === flag)
|
const filter_presets = (flag = true): Preset[] => config.presets.filter(item => item.default === flag)
|
||||||
|
|
||||||
const import_existing_preset = async () => {
|
const import_existing_preset = async (): Promise<void> => {
|
||||||
if (!selected_preset.value) {
|
if (!selected_preset.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -288,52 +288,45 @@
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script lang="ts" setup>
|
||||||
import 'assets/css/bulma-switch.css'
|
import 'assets/css/bulma-switch.css'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { CronExpressionParser } from 'cron-parser'
|
import { CronExpressionParser } from 'cron-parser'
|
||||||
|
import type { exported_task, task_item } from '~/types/tasks'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<{
|
||||||
reference: {
|
reference?: string | null | undefined
|
||||||
type: String,
|
task: task_item
|
||||||
required: false,
|
addInProgress?: boolean
|
||||||
default: null,
|
}>()
|
||||||
},
|
|
||||||
task: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
addInProgress: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emitter = defineEmits(['cancel', 'submit'])
|
const emitter = defineEmits<{
|
||||||
|
(e: 'cancel'): void
|
||||||
|
(e: 'submit', payload: { reference: string | null | undefined, task: task_item }): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
const box = useConfirm()
|
const box = useConfirm()
|
||||||
const showImport = useStorage('showImport', false)
|
const showImport = useStorage('showImport', false)
|
||||||
|
|
||||||
const import_string = ref('')
|
const import_string = ref<string>('')
|
||||||
|
|
||||||
const CHANNEL_REGEX = /^https?:\/\/(?:www\.)?youtube\.com\/(?:(?:channel\/(?<channelId>UC[0-9A-Za-z_-]{22}))|(?:c\/(?<customName>[A-Za-z0-9_-]+))|(?:user\/(?<userName>[A-Za-z0-9_-]+))|(?:@(?<handle>[A-Za-z0-9_-]+)))\/?$/;
|
const CHANNEL_REGEX = /^https?:\/\/(?:www\.)?youtube\.com\/(?:(?:channel\/(?<channelId>UC[0-9A-Za-z_-]{22}))|(?:c\/(?<customName>[A-Za-z0-9_-]+))|(?:user\/(?<userName>[A-Za-z0-9_-]+))|(?:@(?<handle>[A-Za-z0-9_-]+)))\/?$/
|
||||||
|
|
||||||
const form = reactive(props.task)
|
const form = reactive<task_item>({ ...props.task })
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!props.task?.preset || '' === props.task.preset) {
|
if (!props.task?.preset || '' === props.task.preset) {
|
||||||
form.preset = toRaw(config.app.default_preset)
|
form.preset = toRaw(config.app.default_preset)
|
||||||
}
|
}
|
||||||
if (typeof form.auto_start === 'undefined' || form.auto_start === null) {
|
if (typeof form.auto_start === 'undefined' || null === form.auto_start) {
|
||||||
form.auto_start = true
|
form.auto_start = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const checkInfo = async () => {
|
const checkInfo = async (): Promise<void> => {
|
||||||
const required = ['name', 'url']
|
const required = ['name', 'url'] as const
|
||||||
for (const key of required) {
|
for (const key of required) {
|
||||||
if (!form[key]) {
|
if (!form[key]) {
|
||||||
toast.error(`The ${key} field is required.`)
|
toast.error(`The ${key} field is required.`)
|
||||||
|
|
@ -344,7 +337,7 @@ const checkInfo = async () => {
|
||||||
if (form.timer) {
|
if (form.timer) {
|
||||||
try {
|
try {
|
||||||
CronExpressionParser.parse(form.timer)
|
CronExpressionParser.parse(form.timer)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast.error(`Invalid CRON expression. ${e.message}`)
|
toast.error(`Invalid CRON expression. ${e.message}`)
|
||||||
return
|
return
|
||||||
|
|
@ -353,31 +346,29 @@ const checkInfo = async () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new URL(form.url)
|
new URL(form.url)
|
||||||
} catch (e) {
|
} catch {
|
||||||
toast.error('Invalid URL')
|
toast.error('Invalid URL')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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) return
|
||||||
return
|
form.cli = form.cli.trim()
|
||||||
}
|
|
||||||
form.cli = form.cli.trim(" ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter('submit', { reference: toRaw(props.reference), task: toRaw(form) })
|
emitter('submit', { reference: toRaw(props.reference), task: toRaw(form) })
|
||||||
}
|
}
|
||||||
|
|
||||||
const importItem = async () => {
|
const importItem = async (): Promise<void> => {
|
||||||
let val = import_string.value.trim()
|
const val = import_string.value.trim()
|
||||||
if (!val) {
|
if (!val) {
|
||||||
toast.error('The import string is required.')
|
toast.error('The import string is required.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item = decode(val)
|
const item = decode(val) as exported_task
|
||||||
|
|
||||||
if ('task' !== item._type) {
|
if ('task' !== item._type) {
|
||||||
toast.error(`Invalid import string. Expected type 'task', got '${item._type}'.`)
|
toast.error(`Invalid import string. Expected type 'task', got '${item._type}'.`)
|
||||||
|
|
@ -391,34 +382,15 @@ const importItem = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.name) {
|
form.name = item.name ?? form.name
|
||||||
form.name = item.name
|
form.url = item.url ?? form.url
|
||||||
}
|
form.template = item.template ?? form.template
|
||||||
|
form.timer = item.timer ?? form.timer
|
||||||
if (item.url) {
|
form.folder = item.folder ?? form.folder
|
||||||
form.url = item.url
|
form.cli = item.cli ?? form.cli
|
||||||
}
|
form.auto_start = item.auto_start ?? true
|
||||||
|
|
||||||
if (item.template) {
|
|
||||||
form.template = item.template
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.timer) {
|
|
||||||
form.timer = item.timer
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.folder) {
|
|
||||||
form.folder = item.folder
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.cli) {
|
|
||||||
form.cli = item.cli
|
|
||||||
}
|
|
||||||
|
|
||||||
form.auto_start = item?.auto_start ?? true
|
|
||||||
|
|
||||||
if (item.preset) {
|
if (item.preset) {
|
||||||
// -- check if the preset exists in config.presets
|
|
||||||
const preset = config.presets.find(p => p.name === item.preset)
|
const preset = config.presets.find(p => p.name === item.preset)
|
||||||
if (!preset) {
|
if (!preset) {
|
||||||
toast.warning(`Preset '${item.preset}' not found. Preset will be set to default.`)
|
toast.warning(`Preset '${item.preset}' not found. Preset will be set to default.`)
|
||||||
|
|
@ -429,13 +401,13 @@ const importItem = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
import_string.value = ''
|
import_string.value = ''
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast.error(`Failed to import string. ${e.message}`)
|
toast.error(`Failed to import string. ${e.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const convertOptions = async args => {
|
const convertOptions = async (args: string): Promise<Record<string, any> | null> => {
|
||||||
try {
|
try {
|
||||||
const response = await convertCliOptions(args)
|
const response = await convertCliOptions(args)
|
||||||
|
|
||||||
|
|
@ -447,51 +419,42 @@ const convertOptions = async args => {
|
||||||
form.folder = response.download_path
|
form.folder = response.download_path
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.opts
|
return response.opts as Record<string, any>
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
toast.error(e.message)
|
toast.error(e.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasFormatInConfig = computed(() => {
|
const hasFormatInConfig = computed<boolean>(() => !!form.cli && /(?<!\S)(-f|--format)(=|\s)(\S+)/.test(form.cli))
|
||||||
if (!form?.cli) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return /(?<!\S)(-f|--format)(=|\s)(\S+)/.test(form.cli)
|
|
||||||
})
|
|
||||||
|
|
||||||
const filter_presets = (flag = true) => config.presets.filter(item => item.default === flag)
|
const filter_presets = (flag = true) => config.presets.filter(item => item.default === flag)
|
||||||
|
|
||||||
const get_download_folder = () => {
|
const get_download_folder = (): string => {
|
||||||
if (form.preset && !hasFormatInConfig.value) {
|
if (form.preset && false === hasFormatInConfig.value) {
|
||||||
const preset = config.presets.find(p => p.name === form.preset)
|
const preset = config.presets.find(p => p.name === form.preset)
|
||||||
if (preset && preset.folder) {
|
if (preset?.folder) {
|
||||||
return preset.folder.replace(config.app.download_path, '')
|
return preset.folder.replace(config.app.download_path, '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return '/'
|
return '/'
|
||||||
}
|
}
|
||||||
|
|
||||||
const get_output_template = () => {
|
const get_output_template = (): string => {
|
||||||
if (form.preset && !hasFormatInConfig.value) {
|
if (form.preset && false === hasFormatInConfig.value) {
|
||||||
const preset = config.presets.find(p => p.name === form.preset)
|
const preset = config.presets.find(p => p.name === form.preset)
|
||||||
if (preset && preset.template) {
|
if (preset?.template) {
|
||||||
return preset.template
|
return preset.template
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return config.app.output_template || '%(title)s.%(ext)s'
|
return config.app.output_template || '%(title)s.%(ext)s'
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_yt_handle(url) {
|
const is_yt_handle = (url: string): boolean => {
|
||||||
let m = url.match(CHANNEL_REGEX);
|
const m = url.match(CHANNEL_REGEX)
|
||||||
if (m?.groups) {
|
if (m?.groups) {
|
||||||
if (m.groups?.channelId) {
|
return !m.groups.channelId
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
|
import type { StoreItem } from '~/types/store'
|
||||||
|
|
||||||
type video_track_element = {
|
type video_track_element = {
|
||||||
file: string,
|
file: string,
|
||||||
|
|
@ -58,7 +59,7 @@ const toast = useNotification()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object as () => StoreItem,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -122,8 +123,8 @@ onMounted(async () => {
|
||||||
if (props.item.extras?.thumbnail) {
|
if (props.item.extras?.thumbnail) {
|
||||||
thumbnail.value = '/api/thumbnail?url=' + encodePath(props.item.extras.thumbnail)
|
thumbnail.value = '/api/thumbnail?url=' + encodePath(props.item.extras.thumbnail)
|
||||||
} else {
|
} else {
|
||||||
if (response?.sidecar?.image && response.sidecar.image.length > 0) {
|
if (response.sidecar?.image?.[0]?.file) {
|
||||||
thumbnail.value = makeDownload(config, { "filename": response.sidecar.image[0]['file'] })
|
thumbnail.value = makeDownload(config, { "filename": response.sidecar.image[0].file })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
26
ui/app/types/notification.d.ts
vendored
Normal file
26
ui/app/types/notification.d.ts
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
type notificationRequestHeaderItem = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type notificationRequest = {
|
||||||
|
data_key: string;
|
||||||
|
headers: notificationRequestHeaderItem[];
|
||||||
|
method: string;
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type notification = {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
request: notificationRequest;
|
||||||
|
on: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type notificationImport = notification & {
|
||||||
|
_type: 'notification';
|
||||||
|
_version: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type { notificationRequestHeaderItem, notification, notificationRequest, notificationImport };
|
||||||
17
ui/app/types/presets.d.ts
vendored
Normal file
17
ui/app/types/presets.d.ts
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
type Preset = {
|
||||||
|
id?: string
|
||||||
|
name: string
|
||||||
|
cli: string
|
||||||
|
cookies: string
|
||||||
|
default: boolean
|
||||||
|
description: string
|
||||||
|
folder: string
|
||||||
|
template: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PresetImport = Preset & {
|
||||||
|
_type: 'preset'
|
||||||
|
_version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { Preset, PresetImport }
|
||||||
5
ui/app/types/store.d.ts
vendored
5
ui/app/types/store.d.ts
vendored
|
|
@ -54,6 +54,11 @@ export type StoreItem = {
|
||||||
thumbnail?: string
|
thumbnail?: string
|
||||||
/** The uploader of the item if available */
|
/** The uploader of the item if available */
|
||||||
uploader?: string
|
uploader?: string
|
||||||
|
/** Uploader name if available */
|
||||||
|
is_audio?: boolean
|
||||||
|
/** If the item has audio stream */
|
||||||
|
is_video?: boolean
|
||||||
|
/** If the item has video stream */
|
||||||
}
|
}
|
||||||
/** The item temporary filename */
|
/** The item temporary filename */
|
||||||
tmpfilename?: string | null
|
tmpfilename?: string | null
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { convert_args_response } from "~/types/responses";
|
import type { convert_args_response } from "~/types/responses";
|
||||||
|
import type { StoreItem } from "~/types/store";
|
||||||
|
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
|
|
@ -437,7 +438,7 @@ const getQueryParams = (url: string = window.location.search): Record<string, st
|
||||||
* @param base - The base endpoint type (default: 'api/download').
|
* @param base - The base endpoint type (default: 'api/download').
|
||||||
* @returns The fully constructed download URI.
|
* @returns The fully constructed download URI.
|
||||||
*/
|
*/
|
||||||
const makeDownload = (config: any, item: { folder?: string; filename: string }, base: string = 'api/download'): string => {
|
const makeDownload = (config: any, item: StoreItem | { folder?: string; filename: string }, base: string = 'api/download'): string => {
|
||||||
let baseDir = 'api/player/m3u8/video/'
|
let baseDir = 'api/player/m3u8/video/'
|
||||||
if ('m3u8' !== base) {
|
if ('m3u8' !== base) {
|
||||||
baseDir = `${base}/`
|
baseDir = `${base}/`
|
||||||
|
|
@ -448,6 +449,10 @@ const makeDownload = (config: any, item: { folder?: string; filename: string },
|
||||||
baseDir += item.folder + '/'
|
baseDir += item.folder + '/'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!item.filename) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
const url = `/${sTrim(baseDir, '/')}${encodePath(item.filename)}`
|
const url = `/${sTrim(baseDir, '/')}${encodePath(item.filename)}`
|
||||||
return uri('m3u8' === base ? `${url}.m3u8` : url)
|
return uri('m3u8' === base ? `${url}.m3u8` : url)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue