This commit is contained in:
ArabCoders 2024-12-14 16:42:31 +03:00
parent c61fd133d8
commit cf8f971785
10 changed files with 183 additions and 53 deletions

View file

@ -342,6 +342,9 @@ class Notifier:
async def error(self, dl: dict, message: str):
await self.emit('error', (dl, message))
async def warning(self, message: str):
await self.emit('error', message)
async def emit(self, event: str, data):
tasks = []
tasks.append(self.sio.emit(event, self.serializer.encode(data)))

View file

@ -688,6 +688,49 @@ class Main:
})
await self.sio.emit('cli_close', {'exitcode': -1})
@self.sio.event()
async def add_url(sid, post: dict):
url: str = post.get('url')
quality: str = post.get('quality')
if not url:
self.notifier.warning('No URL provided.')
return
format: str = post.get('format')
folder: str = post.get('folder')
ytdlp_cookies: str = post.get('ytdlp_cookies')
ytdlp_config: dict = post.get('ytdlp_config')
output_template: str = post.get('output_template')
if ytdlp_config is None:
ytdlp_config = {}
status = await self.add(
url=url,
quality=quality,
format=format,
folder=folder,
ytdlp_cookies=ytdlp_cookies,
ytdlp_config=ytdlp_config,
output_template=output_template
)
await self.sio.emit('status', self.serializer.encode(status))
@self.sio.event
async def cancel_items(sid, post: dict):
ids = post.get('ids')
identifier: str = post.get('identifier')
if not ids:
await self.notifier.warning('Invalid request.')
return
status: dict[str, str] = {}
status = await self.queue.cancel(ids)
status.update({'identifier': identifier})
await self.sio.emit('cancel_items', self.serializer.encode(status))
@self.sio.event()
async def archive_item(sid, data):
if not isinstance(data, dict) or 'url' not in data or not self.config.keep_archive:

View file

@ -188,3 +188,22 @@ hr {
background-color: #087363;
}
}
.vue-notification {
background: unset;
border-left: none;
}
.notification-title {
font-size: 1.5em;
font-weight: bold;
}
.notification-content {
font-size: 1.2em;
}
.vue-notification-wrapper {
cursor: pointer;
padding-top: 0.5em;
}

View file

@ -4,7 +4,7 @@
<span class="icon">
<i class="fas" :class="showQueue ? 'fa-solid fa-arrow-up' : 'fa-solid fa-arrow-down'" />
</span>
<span>Queue <span v-if="hasQueuedItems">({{ getTotal }})</span></span>
<span>Queue <span v-if="hasQueuedItems">({{ stateStore.count('queue') }})</span></span>
</span>
</h1>
@ -35,7 +35,7 @@
</div>
<div class="columns is-multiline">
<LazyLoader :unrender="true" :min-height="265" class="column is-6" v-for="item in stateStore.queue"
<LazyLoader :unrender="true" :min-height="265" class="column is-6" v-for="item in stateStore.history"
:key="item._id">
<div class="card">
<header class="card-header has-tooltip" v-tooltip="item.title">
@ -57,7 +57,7 @@
<i class="fas" :class="setIcon(item)" />
</span>
<span v-if="item.status == 'downloading' && item.is_live">Live Streaming</span>
<span v-else>{{ capitalize(item.status) }}</span>
<span v-else>{{ ucFirst(item.status) }}</span>
</span>
</div>
<div class="column is-half-mobile has-text-centered">
@ -76,7 +76,7 @@
</div>
<div class="columns is-multiline is-mobile">
<div class="column is-half-mobile">
<button class="button is-danger is-fullwidth" @click="confirmDelete(item);">
<button class="button is-danger is-fullwidth" @click="confirmCancel(item);">
<span class="icon-text is-block">
<span class="icon">
<i class="fa-solid fa-trash-can" />
@ -123,12 +123,10 @@
</template>
<script setup>
import { defineProps, defineEmits, ref, watch, computed } from 'vue';
import moment from "moment";
import moment from 'moment'
import { useStorage } from '@vueuse/core'
import LazyLoader from './LazyLoader'
const emit = defineEmits(['deleteItem']);
import LazyLoader from '~/components/LazyLoader'
import { ucFirst } from '~/utils/index'
const config = useConfigStore();
const stateStore = useStateStore();
@ -137,6 +135,7 @@ const socket = useSocketStore();
const selectedElms = ref([]);
const masterSelectAll = ref(false);
const showQueue = useStorage('showQueue', true)
const actionId = ref(null);
watch(masterSelectAll, (value) => {
for (const key in stateStore.queue) {
@ -150,8 +149,8 @@ watch(masterSelectAll, (value) => {
})
const hasSelected = computed(() => selectedElms.value.length > 0)
const hasQueuedItems = computed(() => stateStore.count('queue') > 0)
const getTotal = computed(() => stateStore.count('queue'));
const hasQueuedItems = computed(() => stateStore.count('history') > 0)
const getTotal = computed(() => stateStore.count('history'));
const setIcon = item => {
if (item.status === 'downloading' && item.is_live) {
@ -219,14 +218,74 @@ const updateProgress = (item) => {
return string;
}
const confirmDelete = (item) => {
if (!confirm(`Are you sure you want to delete (${item.title})?`)) {
const confirmCancel = item => {
if (true !== confirm(`Are you sure you want to cancel (${item.title})?`)) {
return false;
}
emit('deleteItem', 'queue', item._id);
stateStore.remove('queue', item._id);
deleteItem('queue', item._id);
return true;
}
const deleteItem = (type, item) => {
const items = []
if (typeof item === 'object') {
for (const key in item) {
items.push(item[key]);
}
} else {
items.push(item);
}
if (items.length < 0) {
return;
}
actionId.value = makeId();
socket.emit('cancel_items', {
identifier: actionId.value,
ids: items,
});
}
const cancelHandler = async ev => {
if (!actionId.value) {
return;
}
const data = JSON.parse(ev)
if (data.identifier !== actionId.value) {
console.log('Invalid action identifier', data);
return;
}
if ('error' === data.status) {
notification('error', 'Failed to cancel item/s.');
return;
}
for (const key in items) {
const itemId = items[key];
if (itemId in json && json[itemId] === 'ok') {
if (true === stateStore.has('history', itemId)) {
stateStore.remove('history', itemId);
}
if (true === stateStore.has('queue', itemId)) {
notification('info', 'Download canceled: ' + ag(stateStore.get(itemId), 'title'));
stateStore.remove('queue', itemId);
}
}
}
}
onMounted(() => {
socket.on('cancel_items', cancelHandler)
})
onUnmounted(() => {
socket.off('cancel_items', cancelHandler)
})
</script>

View file

@ -13,25 +13,26 @@
<div class="navbar-end is-flex">
<div class="navbar-item">
<NuxtLink class="button is-dark has-tooltip-bottom" to="/console">
<span class="icon"><i class="fa-solid fa-terminal" /></span>
<span>Terminal</span>
<span class="icon-text">
<span class="icon"><i class="fa-solid fa-terminal" /></span>
<span>Terminal</span>
</span>
</NuxtLink>
</div>
<div class="navbar-item">
<button v-tooltip.bottom="'Toggle Add Form'" class="button is-dark has-tooltip-bottom"
@click="config.showForm = !config.showForm">
<span class="icon"><i class="fa-solid fa-plus" /></span>
<span>Add</span>
</button>
<NuxtLink v-tooltip.bottom="'Toggle Add Form'" class="button is-dark has-tooltip-bottom" to="/add">
<span class="icon-text">
<span class="icon"><i class="fa-solid fa-plus" /></span>
<span>Add</span>
</span>
</NuxtLink>
</div>
<div class="navbar-item" v-if="config.tasks.length > 0">
<button v-tooltip.bottom="'Toggle Tasks'" class="button is-dark has-tooltip-bottom"
@click="config.showTasks = !config.showTasks">
<span class="icon">
<i class="fa-solid fa-tasks" />
</span>
<span class="icon"><i class="fa-solid fa-tasks" /></span>
<span class="is-hidden-mobile">Tasks</span>
</button>
</div>
@ -46,6 +47,7 @@
<span class="icon"><i class="fas fa-moon"></i></span>
</button>
</div>
<div class="navbar-item">
<button class="button is-dark" @click="reloadPage">
<span class="icon"><i class="fas fa-refresh"></i></span>

View file

@ -150,7 +150,7 @@ const addInProgress = ref(false)
const addDownload = () => {
addInProgress.value = true;
emits('addItem', {
socket.emit('add_url', {
url: url.value,
format: selectedFormat.value,
quality: selectedQuality.value,
@ -187,4 +187,23 @@ bus.on((event, data) => {
addInProgress.value = false;
}
});
const statusHandler = async data => {
const { status, msg } = JSON.parse(data)
addInProgress.value = false
console.log(data)
if ('error' === status) {
notification('error', 'Add error', msg, 5000)
return
}
notification('success', 'Add success', msg, 3000)
await navigateTo('/')
}
onMounted(() => socket.on('status', statusHandler))
onUnmounted(() => socket.off('status', statusHandler))
</script>

View file

@ -135,7 +135,6 @@ const focusInput = () => {
command_input.value.focus()
}
onUnmounted(() => window.removeEventListener('resize', reSizeTerminal));
const writer = s => {
if (!terminal.value) {
@ -154,4 +153,10 @@ onMounted(async () => {
socket.on('cli_close', loader)
socket.on('cli_output', writer)
})
onUnmounted(() => {
socket.off('cli_close', loader)
socket.off('cli_output', writer)
window.removeEventListener('resize', reSizeTerminal)
});
</script>

View file

@ -1,28 +1,10 @@
<template>
<div>
<NewDownload v-if="config.showForm" />
<Queue />
<History />
</div>
</template>
<script setup>
const bus = useEventBus('item_added', 'show_form', 'task_edit')
const config = useConfigStore()
const socket = useSocketStore()
bus.on((event, data) => {
console.log({ e: event, d: data })
if (!['show_form'].includes(event)) {
return true;
}
if ('show_form' === event) {
addForm.value = true;
setTimeout(() => bus.emit('task_edit', data), 500);
}
})
useHead({ title: 'Index' })
</script>

View file

@ -1,4 +1,5 @@
import { io } from "socket.io-client";
import { notification, ag } from "~/utils/index"
export const useSocketStore = defineStore('socket', () => {
const runtimeConfig = useRuntimeConfig()
@ -29,13 +30,13 @@ export const useSocketStore = defineStore('socket', () => {
socket.value.on('added', stream => {
const item = JSON.parse(stream);
stateStore.add('queue', item);
toast.success(`Item queued successfully: ${stateStore.get('queue', item._id)?.title}`);
stateStore.add('queue', item._id, item);
notification('success', `Item queued successfully: ${ag(stateStore.get('queue', item._id, {}), 'title')}`);
});
socket.value.on('error', stream => {
const [item, error] = JSON.parse(stream);
toast.error(`${item?.id}: Error: ${error}`);
notification('error', `${item?.id}: Error: ${error}`);
});
socket.value.on('completed', stream => {
@ -54,7 +55,7 @@ export const useSocketStore = defineStore('socket', () => {
return
}
toast.info('Download canceled: ' + stateStore.get('queue', id)?.title);
notification('info', `Download canceled: ${ag(stateStore.get('queue', id, {}), 'title')}`);
if (true === stateStore.has('queue', id)) {
stateStore.remove('queue', id);

View file

@ -126,9 +126,6 @@ const notification = (type, title, text, duration = 3000) => {
if (3000 === duration) {
duration = 10000
}
if ('crit' === notificationType) {
log_error(`${title} ${text}`)
}
break
}