completed the ts migration
This commit is contained in:
parent
c94a4e5c75
commit
964a24cdd5
5 changed files with 217 additions and 278 deletions
|
|
@ -373,14 +373,18 @@ def check_id(file: Path) -> bool | str:
|
||||||
|
|
||||||
id = match.groupdict().get("id")
|
id = match.groupdict().get("id")
|
||||||
|
|
||||||
for f in file.parent.iterdir():
|
try:
|
||||||
if id not in f.stem:
|
for f in file.parent.iterdir():
|
||||||
continue
|
if id not in f.stem:
|
||||||
|
continue
|
||||||
|
|
||||||
if f.suffix != file.suffix:
|
if f.suffix != file.suffix:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return f.absolute()
|
return f.absolute()
|
||||||
|
except OSError as e:
|
||||||
|
LOG.error(f"Error checking file '{file}': {e!s}")
|
||||||
|
return False
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -324,6 +324,7 @@ async def prepare_zip_file(request: Request, config: Config, cache: Cache):
|
||||||
for f in json:
|
for f in json:
|
||||||
if not isinstance(f, str):
|
if not isinstance(f, str):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ref, status = get_file(download_path=config.download_path, file=f)
|
ref, status = get_file(download_path=config.download_path, file=f)
|
||||||
if status == web.HTTPNotFound.status_code:
|
if status == web.HTTPNotFound.status_code:
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@
|
||||||
<div class="field is-grouped is-grouped-centered">
|
<div class="field is-grouped is-grouped-centered">
|
||||||
<div class="control" v-if="item.status != 'finished' || !item.filename">
|
<div class="control" v-if="item.status != 'finished' || !item.filename">
|
||||||
<button class="button is-warning is-fullwidth is-small" v-tooltip="'Retry download'"
|
<button class="button is-warning is-fullwidth is-small" v-tooltip="'Retry download'"
|
||||||
@click="(event) => retryItem(item, event)">
|
@click="() => retryItem(item, true)">
|
||||||
<span class="icon"><i class="fa-solid fa-rotate-right" /></span>
|
<span class="icon"><i class="fa-solid fa-rotate-right" /></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -188,7 +188,8 @@
|
||||||
<hr class="dropdown-divider" />
|
<hr class="dropdown-divider" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="isEmbedable(item.url)">
|
<template v-else-if="isEmbedable(item.url)">
|
||||||
<NuxtLink class="dropdown-item has-text-danger" @click="embed_url = getEmbedable(item.url)">
|
<NuxtLink class="dropdown-item has-text-danger"
|
||||||
|
@click="embed_url = getEmbedable(item.url) as string">
|
||||||
<span class="icon"><i class="fa-solid fa-play" /></span>
|
<span class="icon"><i class="fa-solid fa-play" /></span>
|
||||||
<span>Play video</span>
|
<span>Play video</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -279,7 +280,8 @@
|
||||||
v-if="item.extras?.thumbnail" />
|
v-if="item.extras?.thumbnail" />
|
||||||
<img v-else src="/images/placeholder.png" />
|
<img v-else src="/images/placeholder.png" />
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="isEmbedable(item.url)" @click="embed_url = getEmbedable(item.url)" class="play-overlay">
|
<span v-else-if="isEmbedable(item.url)" @click="embed_url = getEmbedable(item.url) as string"
|
||||||
|
class="play-overlay">
|
||||||
<div class="play-icon embed-icon"></div>
|
<div class="play-icon embed-icon"></div>
|
||||||
<img @load="e => pImg(e)" :src="uri('/api/thumbnail?url=' + encodePath(item.extras.thumbnail))"
|
<img @load="e => pImg(e)" :src="uri('/api/thumbnail?url=' + encodePath(item.extras.thumbnail))"
|
||||||
v-if="item.extras?.thumbnail" />
|
v-if="item.extras?.thumbnail" />
|
||||||
|
|
@ -322,7 +324,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="columns is-mobile is-multiline">
|
<div class="columns is-mobile is-multiline">
|
||||||
<div class="column is-half-mobile" v-if="item.status != 'finished' || !item.filename">
|
<div class="column is-half-mobile" v-if="item.status != 'finished' || !item.filename">
|
||||||
<a class="button is-warning is-fullwidth" @click="(event) => retryItem(item, event)">
|
<a class="button is-warning is-fullwidth" @click="() => retryItem(item, false)">
|
||||||
<span class="icon-text is-block">
|
<span class="icon-text is-block">
|
||||||
<span class="icon"><i class="fa-solid fa-rotate-right" /></span>
|
<span class="icon"><i class="fa-solid fa-rotate-right" /></span>
|
||||||
<span>Retry</span>
|
<span>Retry</span>
|
||||||
|
|
@ -360,7 +362,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="isEmbedable(item.url)">
|
<template v-else-if="isEmbedable(item.url)">
|
||||||
<NuxtLink class="dropdown-item has-text-danger" @click="embed_url = getEmbedable(item.url)">
|
<NuxtLink class="dropdown-item has-text-danger"
|
||||||
|
@click="embed_url = getEmbedable(item.url) as string">
|
||||||
<span class="icon"><i class="fa-solid fa-play" /></span>
|
<span class="icon"><i class="fa-solid fa-play" /></span>
|
||||||
<span>Play video</span>
|
<span>Play video</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -459,24 +462,22 @@
|
||||||
@cancel="() => dialog_confirm.visible = false" />
|
@cancel="() => dialog_confirm.visible = false" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
<script setup>
|
|
||||||
import { useStorage } from '@vueuse/core'
|
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import type { StoreItem } from '~/types/store'
|
||||||
|
|
||||||
const emitter = defineEmits(['getInfo', 'add_new', 'getItemInfo', 'clear_search'])
|
const emitter = defineEmits<{
|
||||||
|
(e: 'getInfo', url: string, preset: string): void
|
||||||
|
(e: 'add_new', item: Partial<StoreItem>): void
|
||||||
|
(e: 'getItemInfo', id: string): void
|
||||||
|
(e: 'clear_search'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<{
|
||||||
thumbnails: {
|
thumbnails?: boolean
|
||||||
type: Boolean,
|
query?: string
|
||||||
default: true
|
}>()
|
||||||
},
|
|
||||||
query: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: '',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
const stateStore = useStateStore()
|
const stateStore = useStateStore()
|
||||||
|
|
@ -484,19 +485,25 @@ const socket = useSocketStore()
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
const box = useConfirm()
|
const box = useConfirm()
|
||||||
|
|
||||||
const showCompleted = useStorage('showCompleted', true)
|
const showCompleted = useStorage<boolean>('showCompleted', true)
|
||||||
const hideThumbnail = useStorage('hideThumbnailHistory', false)
|
const hideThumbnail = useStorage<boolean>('hideThumbnailHistory', false)
|
||||||
const direction = useStorage('sortCompleted', 'desc')
|
const direction = useStorage<'asc' | 'desc'>('sortCompleted', 'desc')
|
||||||
const display_style = useStorage('display_style', 'cards')
|
const display_style = useStorage<'cards' | 'list'>('display_style', 'cards')
|
||||||
const bg_enable = useStorage('random_bg', true)
|
const bg_enable = useStorage<boolean>('random_bg', true)
|
||||||
const bg_opacity = useStorage('random_bg_opacity', 0.95)
|
const bg_opacity = useStorage<number>('random_bg_opacity', 0.95)
|
||||||
|
|
||||||
const selectedElms = ref([])
|
const selectedElms = ref<string[]>([])
|
||||||
const masterSelectAll = ref(false)
|
const masterSelectAll = ref(false)
|
||||||
const table_container = ref(false)
|
const table_container = ref(false)
|
||||||
const embed_url = ref('')
|
const embed_url = ref('')
|
||||||
const video_item = ref(null)
|
const video_item = ref<StoreItem | null>(null)
|
||||||
const dialog_confirm = ref({
|
const dialog_confirm = ref<{
|
||||||
|
visible: boolean
|
||||||
|
title: string
|
||||||
|
confirm: (opts?: any) => void
|
||||||
|
message: string
|
||||||
|
options: { key: string; label: string }[]
|
||||||
|
}>({
|
||||||
visible: false,
|
visible: false,
|
||||||
title: 'Confirm Action',
|
title: 'Confirm Action',
|
||||||
confirm: () => { },
|
confirm: () => { },
|
||||||
|
|
@ -506,60 +513,57 @@ const dialog_confirm = ref({
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const showThumbnails = computed(() => props.thumbnails && !hideThumbnail.value)
|
const showThumbnails = computed(() => (props.thumbnails || true) && !hideThumbnail.value)
|
||||||
|
|
||||||
const playVideo = item => video_item.value = item
|
const playVideo = (item: StoreItem) => { video_item.value = item }
|
||||||
const closeVideo = () => video_item.value = null
|
const closeVideo = () => { video_item.value = null }
|
||||||
|
|
||||||
const filteredItems = items => !props.query ? items : items.filter(filterItem)
|
const filteredItems = (items: StoreItem[]) => !props.query ? items : items.filter(filterItem)
|
||||||
|
|
||||||
const filterItem = item => {
|
const filterItem = (item: StoreItem) => {
|
||||||
const q = props.query?.toLowerCase()
|
const q = props.query?.toLowerCase()
|
||||||
if (!q) {
|
if (!q) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return Object.values(item).some(v => typeof v === 'string' && v.toLowerCase().includes(q))
|
return Object.values(item).some(v =>
|
||||||
|
typeof v === 'string' && v.toLowerCase().includes(q)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(masterSelectAll, (value) => {
|
watch(masterSelectAll, (value) => {
|
||||||
for (const key in stateStore.history) {
|
if (value) {
|
||||||
const element = stateStore.history[key]
|
selectedElms.value = Object.values(stateStore.history).map((element: StoreItem) => element._id)
|
||||||
if (value) {
|
} else {
|
||||||
selectedElms.value.push(element._id)
|
selectedElms.value = []
|
||||||
} else {
|
|
||||||
selectedElms.value = []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const sortCompleted = computed(() => {
|
const sortCompleted = computed(() => {
|
||||||
const thisDirection = direction.value
|
const thisDirection = direction.value
|
||||||
return Object.values(stateStore.history).sort((a, b) => {
|
return Object.values(stateStore.history as Record<string, StoreItem>).sort((a, b) => {
|
||||||
if ('asc' === thisDirection) {
|
if ('asc' === thisDirection) {
|
||||||
return new Date(a.datetime) - new Date(b.datetime)
|
return new Date(a.datetime).getTime() - new Date(b.datetime).getTime()
|
||||||
}
|
}
|
||||||
return new Date(b.datetime) - new Date(a.datetime)
|
return new Date(b.datetime).getTime() - new Date(a.datetime).getTime()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasSelected = computed(() => selectedElms.value.length > 0)
|
const hasSelected = computed(() => selectedElms.value.length > 0)
|
||||||
const hasItems = computed(() => filteredItems(sortCompleted.value).length > 0)
|
const hasItems = computed(() => filteredItems(sortCompleted.value as StoreItem[]).length > 0)
|
||||||
|
|
||||||
const showMessage = (item) => {
|
const showMessage = (item: StoreItem) => {
|
||||||
if (!item?.msg || item.msg === item?.error) {
|
if (!item?.msg || item.msg === item?.error) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
return (item.msg?.length || 0) > 0
|
||||||
return item.msg.length > 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasIncomplete = computed(() => {
|
const hasIncomplete = computed(() => {
|
||||||
if (Object.keys(stateStore.history)?.length < 0) {
|
if (Object.keys(stateStore.history)?.length < 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in stateStore.history) {
|
for (const key in stateStore.history) {
|
||||||
const element = stateStore.history[key]
|
const element = stateStore.history[key] as StoreItem
|
||||||
if (element.status !== 'finished') {
|
if (element.status !== 'finished') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -571,9 +575,8 @@ const hasCompleted = computed(() => {
|
||||||
if (Object.keys(stateStore.history)?.length < 0) {
|
if (Object.keys(stateStore.history)?.length < 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in stateStore.history) {
|
for (const key in stateStore.history) {
|
||||||
const element = stateStore.history[key]
|
const element = stateStore.history[key] as StoreItem
|
||||||
if (element.status === 'finished') {
|
if (element.status === 'finished') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -585,9 +588,8 @@ const hasDownloaded = computed(() => {
|
||||||
if (Object.keys(stateStore.history)?.length < 0) {
|
if (Object.keys(stateStore.history)?.length < 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in stateStore.history) {
|
for (const key in stateStore.history) {
|
||||||
const element = stateStore.history[key]
|
const element = stateStore.history[key] as StoreItem
|
||||||
if (element.status === 'finished' && element.filename) {
|
if (element.status === 'finished' && element.filename) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -600,18 +602,19 @@ const deleteSelectedItems = () => {
|
||||||
toast.error('No items selected.')
|
toast.error('No items selected.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg = `${config.app.remove_files ? 'Remove' : 'Clear'} '${selectedElms.value.length}' items?`
|
let msg = `${config.app.remove_files ? 'Remove' : 'Clear'} '${selectedElms.value.length}' items?`
|
||||||
if (true === config.app.remove_files) {
|
if (true === config.app.remove_files) {
|
||||||
msg += ' This will remove any associated files if they exists.'
|
msg += ' This will remove any associated files if they exists.'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false === box.confirm(msg, config.app.remove_files)) {
|
if (false === box.confirm(msg, config.app.remove_files)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in selectedElms.value) {
|
for (const key in selectedElms.value) {
|
||||||
const item = stateStore.history[selectedElms.value[key]]
|
const item_id = selectedElms.value[key]
|
||||||
|
if (!item_id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const item = stateStore.get('history', item_id, {} as StoreItem) as StoreItem
|
||||||
if ('finished' === item.status) {
|
if ('finished' === item.status) {
|
||||||
socket.emit('archive_item', item)
|
socket.emit('archive_item', item)
|
||||||
}
|
}
|
||||||
|
|
@ -628,10 +631,9 @@ const clearCompleted = () => {
|
||||||
if (false === box.confirm(msg)) {
|
if (false === box.confirm(msg)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in stateStore.history) {
|
for (const key in stateStore.history) {
|
||||||
if ('finished' === ag(stateStore.get('history', key, {}), 'status')) {
|
if ('finished' === ag(stateStore.get('history', key, {} as StoreItem), 'status')) {
|
||||||
socket.emit('item_delete', { id: stateStore.history[key]._id, remove_file: false, })
|
socket.emit('item_delete', { id: stateStore.history[key]?._id, remove_file: false, })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -640,18 +642,17 @@ const clearIncomplete = () => {
|
||||||
if (false === box.confirm('Clear all in-complete downloads?')) {
|
if (false === box.confirm('Clear all in-complete downloads?')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in stateStore.history) {
|
for (const key in stateStore.history) {
|
||||||
if (stateStore.history[key].status !== 'finished') {
|
if ((stateStore.history[key] as StoreItem).status !== 'finished') {
|
||||||
socket.emit('item_delete', {
|
socket.emit('item_delete', {
|
||||||
id: stateStore.history[key]._id,
|
id: stateStore.history[key]?._id,
|
||||||
remove_file: false,
|
remove_file: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setIcon = item => {
|
const setIcon = (item: StoreItem) => {
|
||||||
if ('finished' === item.status) {
|
if ('finished' === item.status) {
|
||||||
if (!item.filename) {
|
if (!item.filename) {
|
||||||
return 'fa-solid fa-exclamation'
|
return 'fa-solid fa-exclamation'
|
||||||
|
|
@ -661,73 +662,59 @@ const setIcon = item => {
|
||||||
}
|
}
|
||||||
return item.is_live ? 'fa-solid fa-globe' : 'fa-solid fa-circle-check'
|
return item.is_live ? 'fa-solid fa-globe' : 'fa-solid fa-circle-check'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('error' === item.status) {
|
if ('error' === item.status) {
|
||||||
return 'fa-solid fa-circle-xmark'
|
return 'fa-solid fa-circle-xmark'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('cancelled' === item.status) {
|
if ('cancelled' === item.status) {
|
||||||
return 'fa-solid fa-eject'
|
return 'fa-solid fa-eject'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('not_live' === item.status) {
|
if ('not_live' === item.status) {
|
||||||
return item.extras?.is_premiere ? 'fa-solid fa-star' : 'fa-solid fa-headset'
|
return item.extras?.is_premiere ? 'fa-solid fa-star' : 'fa-solid fa-headset'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('skip' === item.status) {
|
if ('skip' === item.status) {
|
||||||
return 'fa-solid fa-ban'
|
return 'fa-solid fa-ban'
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'fa-solid fa-circle'
|
return 'fa-solid fa-circle'
|
||||||
}
|
}
|
||||||
|
|
||||||
const setIconColor = item => {
|
const setIconColor = (item: StoreItem) => {
|
||||||
if ('finished' === item.status) {
|
if ('finished' === item.status) {
|
||||||
if (!item.filename) {
|
if (!item.filename) {
|
||||||
return 'has-text-warning'
|
return 'has-text-warning'
|
||||||
}
|
}
|
||||||
return 'has-text-success'
|
return 'has-text-success'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('not_live' === item.status) {
|
if ('not_live' === item.status) {
|
||||||
return 'has-text-info'
|
return 'has-text-info'
|
||||||
}
|
}
|
||||||
|
if ('cancelled' === item.status || "skip" === item.status) {
|
||||||
if ('cancelled' === item.status || "skipped" === item.status) {
|
|
||||||
return 'has-text-warning'
|
return 'has-text-warning'
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'has-text-danger'
|
return 'has-text-danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
const setStatus = item => {
|
const setStatus = (item: StoreItem) => {
|
||||||
if ('finished' === item.status) {
|
if ('finished' === item.status) {
|
||||||
if (item.extras?.is_premiere) {
|
if (item.extras?.is_premiere) {
|
||||||
return 'Premiered'
|
return 'Premiered'
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.is_live ? 'Streamed' : 'Completed'
|
return item.is_live ? 'Streamed' : 'Completed'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('error' === item.status) {
|
if ('error' === item.status) {
|
||||||
return 'Error'
|
return 'Error'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('cancelled' === item.status) {
|
if ('cancelled' === item.status) {
|
||||||
return 'Cancelled'
|
return 'Cancelled'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('not_live' === item.status) {
|
if ('not_live' === item.status) {
|
||||||
if (item.extras?.is_premiere) {
|
if (item.extras?.is_premiere) {
|
||||||
return 'Premiere'
|
return 'Premiere'
|
||||||
}
|
}
|
||||||
return display_style.value === 'cards' ? 'Stream' : 'Live'
|
return display_style.value === 'cards' ? 'Stream' : 'Live'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('skip' === item.status) {
|
if ('skip' === item.status) {
|
||||||
return 'Skipped'
|
return 'Skipped'
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.status
|
return item.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -735,9 +722,8 @@ const retryIncomplete = () => {
|
||||||
if (false === box.confirm('Retry all incomplete downloads?')) {
|
if (false === box.confirm('Retry all incomplete downloads?')) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in stateStore.history) {
|
for (const key in stateStore.history) {
|
||||||
const item = stateStore.get('history', key, {})
|
const item = stateStore.get('history', key, {} as StoreItem) as StoreItem
|
||||||
if ('finished' === item.status) {
|
if ('finished' === item.status) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -745,96 +731,85 @@ const retryIncomplete = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addArchiveDialog = (item) => {
|
const addArchiveDialog = (item: StoreItem) => {
|
||||||
dialog_confirm.value.visible = true
|
dialog_confirm.value.visible = true
|
||||||
dialog_confirm.value.title = 'Archive Item'
|
dialog_confirm.value.title = 'Archive Item'
|
||||||
dialog_confirm.value.message = `Archive '${item.title ?? item.id ?? item.url ?? '??'}'?`
|
dialog_confirm.value.message = `Archive '${item.title || item.id || item.url || '??'}'?`
|
||||||
dialog_confirm.value.confirm = opts => archiveItem(item, opts)
|
dialog_confirm.value.confirm = (opts: any) => archiveItem(item, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
const archiveItem = async (item, opts = {}) => {
|
const archiveItem = async (item: StoreItem, opts = {}) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const req = await request(`/api/archive/${item._id}`, {
|
const req = await request(`/api/archive/${item._id}`, {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await req.json()
|
const data = await req.json()
|
||||||
|
|
||||||
dialog_confirm.value.visible = false
|
dialog_confirm.value.visible = false
|
||||||
|
|
||||||
if (!req.ok) {
|
if (!req.ok) {
|
||||||
toast.error(data.error)
|
toast.error(data.error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
toast.success(data.message ?? `Archived '${item.title || item.id || item.url || '??'}'.`)
|
||||||
toast.success(data.message ?? `Archived '${item.title ?? item.id ?? item.url ?? '??'}'.`)
|
} catch (e: any) {
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
if (!(opts as any)?.remove_history) {
|
||||||
if (!opts?.remove_history) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.emit('item_delete', { id: item._id, remove_file: false })
|
socket.emit('item_delete', { id: item._id, remove_file: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeItem = item => {
|
const removeItem = (item: StoreItem) => {
|
||||||
let msg = `${config.app.remove_files ? 'Remove' : 'Clear'} '${item.title ?? item.id ?? item.url ?? '??'}'?`
|
let msg = `${config.app.remove_files ? 'Remove' : 'Clear'} '${item.title || item.id || item.url || '??'}'?`
|
||||||
if (item.status === 'finished' && config.app.remove_files) {
|
if (item.status === 'finished' && config.app.remove_files) {
|
||||||
msg += ' This will remove any associated files if they exists.'
|
msg += ' This will remove any associated files if they exists.'
|
||||||
}
|
}
|
||||||
|
if (false === box.confirm(msg, Boolean(item.filename && config.app.remove_files))) {
|
||||||
if (false === box.confirm(msg, item.filename && config.app.remove_files)) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.emit('item_delete', {
|
socket.emit('item_delete', {
|
||||||
id: item._id,
|
id: item._id,
|
||||||
remove_file: config.app.remove_files
|
remove_file: config.app.remove_files
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const retryItem = (item, re_add = false) => {
|
const retryItem = (item: StoreItem, re_add = false) => {
|
||||||
const item_req = {
|
const item_req: Partial<StoreItem> = {
|
||||||
url: item.url,
|
url: item.url,
|
||||||
preset: item.preset,
|
preset: item.preset,
|
||||||
folder: item.folder,
|
folder: item.folder,
|
||||||
cookies: item.cookies,
|
cookies: item.cookies,
|
||||||
template: item.template,
|
template: item.template,
|
||||||
cli: item?.cli,
|
cli: item?.cli,
|
||||||
extras: toRaw(item.extras ?? {}) ?? {},
|
extras: toRaw(item?.extras || {}) ?? {},
|
||||||
};
|
}
|
||||||
|
|
||||||
socket.emit('item_delete', { id: item._id, remove_file: false })
|
socket.emit('item_delete', { id: item._id, remove_file: false })
|
||||||
|
|
||||||
if (true === re_add) {
|
if (true === re_add) {
|
||||||
toast.info('Cleared the item from history, and added it to the new download form.')
|
toast.info('Cleared the item from history, and added it to the new download form.')
|
||||||
emitter('add_new', item_req)
|
emitter('add_new', item_req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.emit('add_url', item_req)
|
socket.emit('add_url', item_req)
|
||||||
}
|
}
|
||||||
|
|
||||||
const pImg = e => e.target.naturalHeight > e.target.naturalWidth ? e.target.classList.add('image-portrait') : null
|
const pImg = (e: Event) => {
|
||||||
|
const target = e.target as HTMLImageElement
|
||||||
|
target.naturalHeight > target.naturalWidth && target.classList.add('image-portrait')
|
||||||
|
}
|
||||||
|
|
||||||
watch(video_item, v => {
|
watch(video_item, v => {
|
||||||
if (!bg_enable.value) {
|
if (!bg_enable.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
document.querySelector('body')?.setAttribute("style", `opacity: ${v ? 1 : bg_opacity.value}`)
|
||||||
document.querySelector('body').setAttribute("style", `opacity: ${v ? 1 : bg_opacity.value}`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(embed_url, v => {
|
watch(embed_url, v => {
|
||||||
if (!bg_enable.value) {
|
if (!bg_enable.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
document.querySelector('body')?.setAttribute("style", `opacity: ${v ? 1 : bg_opacity.value}`)
|
||||||
document.querySelector('body').setAttribute("style", `opacity: ${v ? 1 : bg_opacity.value}`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const downloadSelected = async () => {
|
const downloadSelected = async () => {
|
||||||
|
|
@ -842,100 +817,89 @@ const downloadSelected = async () => {
|
||||||
toast.error('No items selected.')
|
toast.error('No items selected.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let files_list: string[] = []
|
||||||
let files_list = []
|
|
||||||
for (const key in selectedElms.value) {
|
for (const key in selectedElms.value) {
|
||||||
const item = stateStore.history[selectedElms.value[key]]
|
const item_id = selectedElms.value[key]
|
||||||
|
if (!item_id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const item = stateStore.get('history', item_id, {} as StoreItem) as StoreItem
|
||||||
if ('finished' !== item.status || !item.filename) {
|
if ('finished' !== item.status || !item.filename) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
files_list.push(item.folder ? item.folder + '/' + item.filename : item.filename)
|
files_list.push(item.folder ? item.folder + '/' + item.filename : item.filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedElms.value = []
|
selectedElms.value = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await request('/api/file/download', {
|
const response = await request('/api/file/download', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
body: JSON.stringify(files_list),
|
body: JSON.stringify(files_list),
|
||||||
});
|
})
|
||||||
|
const json = await response.json()
|
||||||
const json = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
toast.error(json.error || 'Failed to start download.');
|
toast.error(json.error || 'Failed to start download.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = json.token
|
const token = json.token
|
||||||
|
|
||||||
const body = document.querySelector('body')
|
const body = document.querySelector('body')
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a')
|
||||||
link.href = uri(`/api/file/download/${token}`);
|
link.href = uri(`/api/file/download/${token}`)
|
||||||
link.setAttribute('target', '_blank');
|
link.setAttribute('target', '_blank')
|
||||||
body.appendChild(link);
|
body?.appendChild(link)
|
||||||
link.click();
|
link.click()
|
||||||
body.removeChild(link);
|
body?.removeChild(link)
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e)
|
||||||
toast.error(`Error: ${e.message}`);
|
toast.error(`Error: ${e.message}`)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggle_class = e => ['is-text-overflow', 'is-word-break'].forEach(c => e.currentTarget.classList.toggle(c))
|
const toggle_class = (e: Event) => ['is-text-overflow', 'is-word-break'].forEach(c => (e.currentTarget as HTMLElement).classList.toggle(c))
|
||||||
|
|
||||||
const removeFromArchiveDialog = (item) => {
|
const removeFromArchiveDialog = (item: StoreItem) => {
|
||||||
dialog_confirm.value.visible = true
|
dialog_confirm.value.visible = true
|
||||||
dialog_confirm.value.title = 'Remove from Archive'
|
dialog_confirm.value.title = 'Remove from Archive'
|
||||||
dialog_confirm.value.message = `Remove '${item.title ?? item.id ?? item.url ?? '??'}' from archive?`
|
dialog_confirm.value.message = `Remove '${item.title || item.id || item.url || '??'}' from archive?`
|
||||||
dialog_confirm.value.confirm = () => removeFromArchive(item)
|
dialog_confirm.value.confirm = () => removeFromArchive(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeFromArchive = async (item, opts) => {
|
const removeFromArchive = async (item: StoreItem, opts?: { remove_history?: boolean }) => {
|
||||||
try {
|
try {
|
||||||
const req = await request(`/api/archive/${item._id}`, {
|
const req = await request(`/api/archive/${item._id}`, {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await req.json()
|
const data = await req.json()
|
||||||
|
|
||||||
if (!req.ok) {
|
if (!req.ok) {
|
||||||
toast.error(data.error)
|
toast.error(data.error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
toast.success(data.message || `Removed '${item.title || item.id || item.url || '??'}' from archive.`)
|
||||||
toast.success(data.message ?? `Removed '${item.title ?? item.id ?? item.url ?? '??'}' from archive.`)
|
} catch (e: any) {
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast.error(`Error: ${e.message}`)
|
toast.error(`Error: ${e.message}`)
|
||||||
} finally {
|
} finally {
|
||||||
dialog_confirm.value.visible = false
|
dialog_confirm.value.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts?.remove_history) {
|
if (opts?.remove_history) {
|
||||||
socket.emit('item_delete', { id: item._id, remove_file: false })
|
socket.emit('item_delete', { id: item._id, remove_file: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const is_queued = item => {
|
const is_queued = (item: StoreItem) => {
|
||||||
if (!item?.status || 'not_live' !== item.status) {
|
if (!item?.status || 'not_live' !== item.status) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.live_in || item.extras?.live_in || item.extras?.release_in ? 'fa-spin fa-spin-10' : ''
|
return item.live_in || item.extras?.live_in || item.extras?.release_in ? 'fa-spin fa-spin-10' : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const makePath = item => {
|
const makePath = (item: StoreItem) => {
|
||||||
if (!item?.filename) {
|
if (!item?.filename) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const real_path = eTrim(item.download_dir, '/') + '/' + sTrim(item.filename, '/')
|
const real_path = eTrim(item.download_dir, '/') + '/' + sTrim(item.filename, '/')
|
||||||
|
|
||||||
return real_path.replace(config.app.download_path, '').replace(/^\//, '')
|
return real_path.replace(config.app.download_path, '').replace(/^\//, '')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@
|
||||||
<td>
|
<td>
|
||||||
<div class="progress-bar is-unselectable">
|
<div class="progress-bar is-unselectable">
|
||||||
<div class="progress-percentage">{{ updateProgress(item) }}</div>
|
<div class="progress-percentage">{{ updateProgress(item) }}</div>
|
||||||
<div class="progress" :style="{ width: percentPipe(item.percent) + '%' }"></div>
|
<div class="progress" :style="{ width: percentPipe(item.percent as number) + '%' }"></div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="has-text-centered is-text-overflow is-unselectable">
|
<td class="has-text-centered is-text-overflow is-unselectable">
|
||||||
|
|
@ -119,7 +119,8 @@
|
||||||
<Dropdown icons="fa-solid fa-cogs" @open_state="s => table_container = !s"
|
<Dropdown icons="fa-solid fa-cogs" @open_state="s => table_container = !s"
|
||||||
:button_classes="'is-small'" label="Actions">
|
:button_classes="'is-small'" label="Actions">
|
||||||
<template v-if="isEmbedable(item.url)">
|
<template v-if="isEmbedable(item.url)">
|
||||||
<NuxtLink class="dropdown-item has-text-danger" @click="embed_url = getEmbedable(item.url)">
|
<NuxtLink class="dropdown-item has-text-danger"
|
||||||
|
@click="embed_url = getEmbedable(item.url) as string">
|
||||||
<span class="icon"><i class="fa-solid fa-play" /></span>
|
<span class="icon"><i class="fa-solid fa-play" /></span>
|
||||||
<span>Play video</span>
|
<span>Play video</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -202,7 +203,8 @@
|
||||||
</header>
|
</header>
|
||||||
<div v-if="showThumbnails" class="card-image">
|
<div v-if="showThumbnails" class="card-image">
|
||||||
<figure class="image is-3by1">
|
<figure class="image is-3by1">
|
||||||
<span v-if="isEmbedable(item.url)" @click="embed_url = getEmbedable(item.url)" class="play-overlay">
|
<span v-if="isEmbedable(item.url)" @click="embed_url = getEmbedable(item.url) as string"
|
||||||
|
class="play-overlay">
|
||||||
<div class="play-icon embed-icon"></div>
|
<div class="play-icon embed-icon"></div>
|
||||||
<img @load="e => pImg(e)" :src="uri('/api/thumbnail?url=' + encodePath(item.extras.thumbnail))"
|
<img @load="e => pImg(e)" :src="uri('/api/thumbnail?url=' + encodePath(item.extras.thumbnail))"
|
||||||
v-if="item.extras?.thumbnail" />
|
v-if="item.extras?.thumbnail" />
|
||||||
|
|
@ -220,7 +222,7 @@
|
||||||
<div class="column is-12">
|
<div class="column is-12">
|
||||||
<div class="progress-bar is-unselectable">
|
<div class="progress-bar is-unselectable">
|
||||||
<div class="progress-percentage">{{ updateProgress(item) }}</div>
|
<div class="progress-percentage">{{ updateProgress(item) }}</div>
|
||||||
<div class="progress" :style="{ width: percentPipe(item.percent) + '%' }"></div>
|
<div class="progress" :style="{ width: percentPipe(item.percent as number) + '%' }"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable">
|
<div class="column is-half-mobile has-text-centered is-text-overflow is-unselectable">
|
||||||
|
|
@ -267,7 +269,8 @@
|
||||||
<div class="column is-half-mobile">
|
<div class="column is-half-mobile">
|
||||||
<Dropdown icons="fa-solid fa-cogs" @open_state="s => table_container = !s" label="Actions">
|
<Dropdown icons="fa-solid fa-cogs" @open_state="s => table_container = !s" label="Actions">
|
||||||
<template v-if="isEmbedable(item.url)">
|
<template v-if="isEmbedable(item.url)">
|
||||||
<NuxtLink class="dropdown-item has-text-danger" @click="embed_url = getEmbedable(item.url)">
|
<NuxtLink class="dropdown-item has-text-danger"
|
||||||
|
@click="embed_url = getEmbedable(item.url) as string">
|
||||||
<span class="icon"><i class="fa-solid fa-play" /></span>
|
<span class="icon"><i class="fa-solid fa-play" /></span>
|
||||||
<span>Play video</span>
|
<span>Play video</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -312,26 +315,27 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import type { StoreItem } from '~/types/store'
|
||||||
|
|
||||||
|
const emitter = defineEmits<{
|
||||||
|
(e: 'getInfo', url: string, preset: string): void
|
||||||
|
(e: 'getItemInfo', id: string): void
|
||||||
|
(e: 'clear_search'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
thumbnails?: boolean
|
||||||
|
query?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
const emitter = defineEmits(['getInfo', 'clear_search', 'getItemInfo'])
|
|
||||||
const props = defineProps({
|
|
||||||
thumbnails: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
const stateStore = useStateStore()
|
const stateStore = useStateStore()
|
||||||
const socket = useSocketStore()
|
const socket = useSocketStore()
|
||||||
const box = useConfirm()
|
const box = useConfirm()
|
||||||
|
const toast = useNotification()
|
||||||
|
|
||||||
const showQueue = useStorage('showQueue', true)
|
const showQueue = useStorage('showQueue', true)
|
||||||
const hideThumbnail = useStorage('hideThumbnailQueue', false)
|
const hideThumbnail = useStorage('hideThumbnailQueue', false)
|
||||||
|
|
@ -339,135 +343,116 @@ const display_style = useStorage('display_style', 'cards')
|
||||||
const bg_enable = useStorage('random_bg', true)
|
const bg_enable = useStorage('random_bg', true)
|
||||||
const bg_opacity = useStorage('random_bg_opacity', 0.95)
|
const bg_opacity = useStorage('random_bg_opacity', 0.95)
|
||||||
|
|
||||||
const selectedElms = ref([])
|
const selectedElms = ref<string[]>([])
|
||||||
const masterSelectAll = ref(false)
|
const masterSelectAll = ref(false)
|
||||||
const embed_url = ref('')
|
const embed_url = ref('')
|
||||||
const table_container = ref(false)
|
const table_container = ref(false)
|
||||||
|
|
||||||
const showThumbnails = computed(() => props.thumbnails && !hideThumbnail.value)
|
const showThumbnails = computed(() => !!props.thumbnails && !hideThumbnail.value)
|
||||||
|
|
||||||
watch(masterSelectAll, (value) => {
|
watch(masterSelectAll, (value) => {
|
||||||
for (const key in stateStore.queue) {
|
if (value) {
|
||||||
const element = stateStore.queue[key]
|
selectedElms.value = Object.values(stateStore.queue).map((element: StoreItem) => element._id)
|
||||||
if (value) {
|
} else {
|
||||||
selectedElms.value.push(element._id)
|
selectedElms.value = []
|
||||||
} else {
|
|
||||||
selectedElms.value = []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const filteredItems = computed(() => {
|
const filteredItems = computed<StoreItem[]>(() => {
|
||||||
const q = props.query?.toLowerCase();
|
const q = props.query?.toLowerCase()
|
||||||
if (!q) {
|
if (!q) {
|
||||||
return Object.values(stateStore.queue)
|
return Object.values(stateStore.queue)
|
||||||
}
|
}
|
||||||
return Object.values(stateStore.queue).filter(i => Object.values(i).some(v => typeof v === 'string' && v.toLowerCase().includes(q)));
|
return Object.values(stateStore.queue).filter((i: StoreItem) =>
|
||||||
});
|
Object.values(i).some(v => typeof v === 'string' && v.toLowerCase().includes(q))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const hasSelected = computed(() => selectedElms.value.length > 0)
|
const hasSelected = computed(() => 0 < selectedElms.value.length)
|
||||||
const hasQueuedItems = computed(() => stateStore.count('queue') > 0)
|
const hasQueuedItems = computed(() => 0 < stateStore.count('queue'))
|
||||||
const hasManualStart = computed(() => {
|
const hasManualStart = computed(() => {
|
||||||
if (stateStore.count('queue') < 0) {
|
if (0 > stateStore.count('queue')) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in stateStore.queue) {
|
for (const key in stateStore.queue) {
|
||||||
const item = stateStore.queue[key]
|
const item = stateStore.queue[key] as StoreItem
|
||||||
if (!item.status && item.auto_start === false) {
|
if (!item.status && false === item.auto_start) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasPausable = computed(() => {
|
const hasPausable = computed(() => {
|
||||||
if (stateStore.count('queue') < 0) {
|
if (0 > stateStore.count('queue')) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in stateStore.queue) {
|
for (const key in stateStore.queue) {
|
||||||
const item = stateStore.queue[key]
|
const item = stateStore.queue[key] as StoreItem
|
||||||
if (!item.status && item.auto_start === true) {
|
if (!item.status && true === item.auto_start) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const setIcon = (item: StoreItem): string => {
|
||||||
const setIcon = item => {
|
|
||||||
if (!item.auto_start) {
|
if (!item.auto_start) {
|
||||||
return 'fa-hourglass-half'
|
return 'fa-hourglass-half'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('downloading' === item.status && item.is_live) {
|
if ('downloading' === item.status && item.is_live) {
|
||||||
return 'fa-globe fa-spin'
|
return 'fa-globe fa-spin'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('downloading' === item.status) {
|
if ('downloading' === item.status) {
|
||||||
return 'fa-download'
|
return 'fa-download'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('postprocessing' === item.status) {
|
if ('postprocessing' === item.status) {
|
||||||
return 'fa-cog fa-spin'
|
return 'fa-cog fa-spin'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === item.status && true === config.paused) {
|
if (null === item.status && true === config.paused) {
|
||||||
return 'fa-pause-circle'
|
return 'fa-pause-circle'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item.status) {
|
if (!item.status) {
|
||||||
return 'fa-question'
|
return 'fa-question'
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'fa-spinner fa-spin'
|
return 'fa-spinner fa-spin'
|
||||||
}
|
}
|
||||||
|
|
||||||
const setStatus = item => {
|
const setStatus = (item: StoreItem): string => {
|
||||||
if (!item.auto_start) {
|
if (!item.auto_start) {
|
||||||
return 'Pending'
|
return 'Pending'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === item.status && true === config.paused) {
|
if (null === item.status && true === config.paused) {
|
||||||
return 'Paused'
|
return 'Paused'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('downloading' === item.status && item.is_live) {
|
if ('downloading' === item.status && item.is_live) {
|
||||||
return 'Streaming'
|
return 'Streaming'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('preparing' === item.status) {
|
if ('preparing' === item.status) {
|
||||||
return ag(item, 'extras.external_downloader') ? 'External-DL' : 'Preparing..';
|
// @ts-ignore
|
||||||
|
return ag(item, 'extras.external_downloader') ? 'External-DL' : 'Preparing..'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item.status) {
|
if (!item.status) {
|
||||||
return 'Unknown...'
|
return 'Unknown...'
|
||||||
}
|
}
|
||||||
|
|
||||||
return ucFirst(item.status)
|
return ucFirst(item.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setIconColor = item => {
|
const setIconColor = (item: StoreItem): string => {
|
||||||
if (item.status === 'downloading') {
|
if ('downloading' === item.status) {
|
||||||
return 'has-text-success'
|
return 'has-text-success'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('postprocessing' === item.status) {
|
if ('postprocessing' === item.status) {
|
||||||
return 'has-text-info'
|
return 'has-text-info'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item.auto_start || (null === item.status && true === config.paused)) {
|
if (!item.auto_start || (null === item.status && true === config.paused)) {
|
||||||
return 'has-text-warning'
|
return 'has-text-warning'
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const ETAPipe = value => {
|
const ETAPipe = (value: number | null): string => {
|
||||||
if (value === null || 0 === value) {
|
if (null === value || 0 === value) {
|
||||||
return 'Live'
|
return 'Live'
|
||||||
}
|
}
|
||||||
if (value < 60) {
|
if (value < 60) {
|
||||||
|
|
@ -481,11 +466,10 @@ const ETAPipe = value => {
|
||||||
return `${hours}h ${Math.floor(minutes / 60)}m ${Math.round(minutes % 60)}s`
|
return `${hours}h ${Math.floor(minutes / 60)}m ${Math.round(minutes % 60)}s`
|
||||||
}
|
}
|
||||||
|
|
||||||
const speedPipe = value => {
|
const speedPipe = (value: number | null): string => {
|
||||||
if (null === value || 0 === value) {
|
if (null === value || 0 === value) {
|
||||||
return '0KB/s'
|
return '0KB/s'
|
||||||
}
|
}
|
||||||
|
|
||||||
const k = 1024
|
const k = 1024
|
||||||
const dm = 2
|
const dm = 2
|
||||||
const sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s', 'EB/s', 'ZB/s', 'YB/s']
|
const sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s', 'EB/s', 'ZB/s', 'YB/s']
|
||||||
|
|
@ -493,46 +477,39 @@ const speedPipe = value => {
|
||||||
return parseFloat((value / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
return parseFloat((value / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
const percentPipe = value => {
|
const percentPipe = (value: number | null): string => {
|
||||||
if (value === null || 0 === value) {
|
if (null === value || 0 === value) {
|
||||||
return '00.00'
|
return '00.00'
|
||||||
}
|
}
|
||||||
return parseFloat(value).toFixed(2)
|
return parseFloat(String(value)).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateProgress = (item) => {
|
const updateProgress = (item: StoreItem): string => {
|
||||||
let string = ''
|
let string = ''
|
||||||
|
|
||||||
if (!item.auto_start) {
|
if (!item.auto_start) {
|
||||||
return 'Manual start'
|
return 'Manual start'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === item.status && true === config.paused) {
|
if (null === item.status && true === config.paused) {
|
||||||
return 'Global Pause'
|
return 'Global Pause'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('postprocessing' === item.status) {
|
if ('postprocessing' === item.status) {
|
||||||
return 'Post-processors are running.'
|
return 'Post-processors are running.'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('preparing' === item.status) {
|
if ('preparing' === item.status) {
|
||||||
|
// @ts-ignore
|
||||||
return ag(item, 'extras.external_downloader') ? 'External downloader.' : 'Preparing'
|
return ag(item, 'extras.external_downloader') ? 'External downloader.' : 'Preparing'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null != item.status) {
|
if (null != item.status) {
|
||||||
string += item.percent && !item.is_live ? percentPipe(item.percent) + '%' : 'Live'
|
string += item.percent && !item.is_live ? percentPipe(item.percent) + '%' : 'Live'
|
||||||
}
|
}
|
||||||
|
|
||||||
string += item.speed ? ' - ' + speedPipe(item.speed) : ' - Waiting..'
|
string += item.speed ? ' - ' + speedPipe(item.speed) : ' - Waiting..'
|
||||||
|
|
||||||
if (null != item.status && item.eta) {
|
if (null != item.status && item.eta) {
|
||||||
string += ' - ' + ETAPipe(item.eta)
|
string += ' - ' + ETAPipe(item.eta)
|
||||||
}
|
}
|
||||||
|
return string
|
||||||
return string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmCancel = item => {
|
const confirmCancel = (item: StoreItem) => {
|
||||||
if (true !== box.confirm(`Cancel '${item.title}'?`)) {
|
if (true !== box.confirm(`Cancel '${item.title}'?`)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -542,97 +519,84 @@ const confirmCancel = item => {
|
||||||
|
|
||||||
const cancelSelected = () => {
|
const cancelSelected = () => {
|
||||||
if (true !== box.confirm(`Cancel '${selectedElms.value.length}' selected items?`)) {
|
if (true !== box.confirm(`Cancel '${selectedElms.value.length}' selected items?`)) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
cancelItems(selectedElms.value)
|
cancelItems(selectedElms.value)
|
||||||
selectedElms.value = []
|
selectedElms.value = []
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelItems = item => {
|
const cancelItems = (item: string | string[]) => {
|
||||||
const items = []
|
const items: string[] = []
|
||||||
|
if ('object' === typeof item) {
|
||||||
if (typeof item === 'object') {
|
|
||||||
for (const key in item) {
|
for (const key in item) {
|
||||||
items.push(item[key])
|
items.push((item as any)[key])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
items.push(item)
|
items.push(item)
|
||||||
}
|
}
|
||||||
|
if (0 > items.length) {
|
||||||
if (items.length < 0) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
items.forEach(id => socket.emit('item_cancel', id))
|
items.forEach(id => socket.emit('item_cancel', id))
|
||||||
}
|
}
|
||||||
|
|
||||||
const startItem = item => socket.emit('item_start', item._id)
|
const startItem = (item: StoreItem) => socket.emit('item_start', item._id)
|
||||||
const pauseItem = item => socket.emit('item_pause', item._id)
|
const pauseItem = (item: StoreItem) => socket.emit('item_pause', item._id)
|
||||||
|
|
||||||
const startItems = () => {
|
const startItems = () => {
|
||||||
if (selectedElms.value.length < 1) {
|
if (1 > selectedElms.value.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let filtered: string[] = []
|
||||||
let filtered = []
|
|
||||||
|
|
||||||
selectedElms.value.forEach(id => {
|
selectedElms.value.forEach(id => {
|
||||||
const item = stateStore.get('queue', id)
|
const item = stateStore.get('queue', id) as StoreItem
|
||||||
if (item && !item.auto_start && !item.status) {
|
if (item && !item.auto_start && !item.status) {
|
||||||
filtered.push(id)
|
filtered.push(id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
selectedElms.value = []
|
selectedElms.value = []
|
||||||
|
if (1 > filtered.length) {
|
||||||
if (filtered.length < 1) {
|
|
||||||
toast.error('No eligible items to start.')
|
toast.error('No eligible items to start.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (true !== box.confirm(`Start '${filtered.length}' selected items?`)) {
|
if (true !== box.confirm(`Start '${filtered.length}' selected items?`)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered.forEach(id => socket.emit('item_start', id))
|
filtered.forEach(id => socket.emit('item_start', id))
|
||||||
}
|
}
|
||||||
|
|
||||||
const pauseSelected = () => {
|
const pauseSelected = () => {
|
||||||
if (selectedElms.value.length < 1) {
|
if (1 > selectedElms.value.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let filtered: string[] = []
|
||||||
let filtered = []
|
|
||||||
|
|
||||||
selectedElms.value.forEach(id => {
|
selectedElms.value.forEach(id => {
|
||||||
const item = stateStore.get('queue', id)
|
const item = stateStore.get('queue', id) as StoreItem
|
||||||
if (item && item.auto_start && !item.status) {
|
if (item && item.auto_start && !item.status) {
|
||||||
filtered.push(id)
|
filtered.push(id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
selectedElms.value = []
|
selectedElms.value = []
|
||||||
|
if (1 > filtered.length) {
|
||||||
if (filtered.length < 1) {
|
|
||||||
toast.error('No eligible items to pause.')
|
toast.error('No eligible items to pause.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (true !== box.confirm(`Pause '${filtered.length}' selected items?`)) {
|
if (true !== box.confirm(`Pause '${filtered.length}' selected items?`)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered.forEach(id => socket.emit('item_pause', id))
|
filtered.forEach(id => socket.emit('item_pause', id))
|
||||||
}
|
}
|
||||||
|
|
||||||
const pImg = e => e.target.naturalHeight > e.target.naturalWidth ? e.target.classList.add('image-portrait') : null
|
const pImg = (e: Event) => {
|
||||||
|
const target = e.target as HTMLImageElement
|
||||||
|
target.naturalHeight > target.naturalWidth ? target.classList.add('image-portrait') : null
|
||||||
|
}
|
||||||
|
|
||||||
watch(embed_url, v => {
|
watch(embed_url, v => {
|
||||||
if (!bg_enable.value) {
|
if (!bg_enable.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
document.querySelector('body').setAttribute("style", `opacity: ${v ? 1 : bg_opacity.value}`)
|
document.querySelector('body')?.setAttribute('style', `opacity: ${v ? 1 : bg_opacity.value}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
10
ui/app/types/store.d.ts
vendored
10
ui/app/types/store.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
export type ItemStatus = 'finished' | 'preparing' | 'error' | 'cancelled' | 'downloading' | 'postprocessing' | 'not_live' | null;
|
type ItemStatus = 'finished' | 'preparing' | 'error' | 'cancelled' | 'downloading' | 'postprocessing' | 'not_live' | 'skip' | null;
|
||||||
export type StoreItem = {
|
type StoreItem = {
|
||||||
/** Unique identifier for the item */
|
/** Unique identifier for the item */
|
||||||
_id: string
|
_id: string
|
||||||
/** Error message if available */
|
/** Error message if available */
|
||||||
|
|
@ -59,6 +59,10 @@ export type StoreItem = {
|
||||||
/** If the item has audio stream */
|
/** If the item has audio stream */
|
||||||
is_video?: boolean
|
is_video?: boolean
|
||||||
/** If the item has video stream */
|
/** If the item has video stream */
|
||||||
|
live_in?: string
|
||||||
|
/** Live stream start time if available */
|
||||||
|
is_premiere?: boolean
|
||||||
|
/** If the item is a premiere */
|
||||||
}
|
}
|
||||||
/** The item temporary filename */
|
/** The item temporary filename */
|
||||||
tmpfilename?: string | null
|
tmpfilename?: string | null
|
||||||
|
|
@ -79,3 +83,5 @@ export type StoreItem = {
|
||||||
/** Time remaining for the item download if available */
|
/** Time remaining for the item download if available */
|
||||||
eta?: number | null
|
eta?: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type { ItemStatus, StoreItem }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue