sweeping changes to internals

This commit is contained in:
ArabCoders 2024-12-14 19:27:13 +03:00
parent cf8f971785
commit 0239e509de
19 changed files with 183 additions and 214 deletions

27
.vscode/launch.json vendored
View file

@ -5,18 +5,18 @@
"version": "0.2.0",
"configurations": [
{
"name": "Node: App.Vue",
"name": "Node: Nuxt3",
"request": "launch",
"runtimeArgs": [
"run",
"serve",
"dev",
"--",
"--port",
"3000"
"8082"
],
"runtimeExecutable": "npm",
"runtimeExecutable": "yarn",
"type": "node",
"cwd": "${workspaceFolder}/frontend",
"cwd": "${workspaceFolder}/ui",
"env": {
"VUE_APP_BASE_URL": "http://localhost:8081",
}
@ -73,23 +73,6 @@
}
],
"justMyCode": true
},
{
"name": "Node: NuxtJS",
"request": "launch",
"runtimeArgs": [
"run",
"dev",
"--",
"--port",
"8082"
],
"runtimeExecutable": "yarn",
"type": "node",
"cwd": "${workspaceFolder}/ui",
"env": {
"VUE_APP_BASE_URL": "http://localhost:8081",
}
}
]
}

View file

@ -201,7 +201,7 @@ class Download:
self.proc.start()
self.info.status = 'preparing'
asyncio.create_task(self.notifier.updated(self.info), name=f"notifier-{self.id}")
asyncio.create_task(self.notifier.updated(dl=self.info), name=f"notifier-{self.id}")
asyncio.create_task(self.progress_update(), name=f"update-{self.id}")
return await asyncio.get_running_loop().run_in_executor(None, self.proc.join)
@ -329,7 +329,7 @@ class Download:
LOG.debug(f'Status Update: {self.info._id=} {status=}')
if isinstance(status, str):
asyncio.create_task(self.notifier.updated(self.info), name=f"notifier-u-{self.id}")
asyncio.create_task(self.notifier.updated(dl=self.info), name=f"notifier-u-{self.id}")
continue
self.tmpfilename = status.get('tmpfilename')
@ -349,7 +349,9 @@ class Download:
if self.info.status == 'error' and 'error' in status:
self.info.error = status.get('error')
asyncio.create_task(self.notifier.error(self.info, self.info.error), name=f"notifier-e-{self.id}")
asyncio.create_task(
self.notifier.error(dl=self.info, message=self.info.error),
name=f"notifier-e-{self.id}")
if 'downloaded_bytes' in status:
total = status.get('total_bytes') or status.get('total_bytes_estimate')
@ -368,4 +370,4 @@ class Download:
self.info.file_size = None
pass
asyncio.create_task(self.notifier.updated(self.info), name=f"notifier-u-{self.id}")
asyncio.create_task(self.notifier.updated(dl=self.info), name=f"notifier-u-{self.id}")

View file

@ -12,7 +12,6 @@ from ItemDTO import ItemDTO
from DataStore import DataStore
from Utils import Notifier, ObjectSerializer, calcDownloadPath, ExtractInfo, isDownloaded, mergeConfig
from AsyncPool import AsyncPool
from concurrent.futures import ThreadPoolExecutor
LOG = logging.getLogger('DownloadQueue')
TYPE_DONE: str = 'done'
@ -295,9 +294,9 @@ class DownloadQueue:
await item.close()
LOG.debug(f'Deleting from queue {itemMessage}')
self.queue.delete(id)
asyncio.create_task(self.notifier.canceled(id), name=f'notifier-c-{id}')
asyncio.create_task(self.notifier.canceled(id=id, dl=item), name=f'notifier-c-{id}')
self.done.put(item)
asyncio.create_task(self.notifier.completed(item), name=f'notifier-d-{id}')
asyncio.create_task(self.notifier.completed(dl=item), name=f'notifier-d-{id}')
LOG.info(f'Deleted from queue {itemMessage}')
status[id] = 'ok'
@ -319,7 +318,7 @@ class DownloadQueue:
itemMessage = f"{id=} {item.info.id=} {item.info.title=}"
LOG.debug(f'Deleting completed download {itemMessage}')
self.done.delete(id)
asyncio.create_task(self.notifier.cleared(id), name=f'notifier-c-{id}')
asyncio.create_task(self.notifier.cleared(id, dl=item), name=f'notifier-c-{id}')
LOG.info(f'Deleted completed download {itemMessage}')
status[id] = 'ok'
@ -418,12 +417,12 @@ class DownloadQueue:
self.queue.delete(key=id)
if entry.is_canceled() is True:
asyncio.create_task(self.notifier.canceled(id), name=f'notifier-c-{id}')
asyncio.create_task(self.notifier.canceled(id, dl=entry.info), name=f'notifier-c-{id}')
entry.info.status = 'canceled'
entry.info.error = 'Canceled by user.'
self.done.put(value=entry)
asyncio.create_task(self.notifier.completed(entry.info), name=f'notifier-d-{id}')
asyncio.create_task(self.notifier.completed(dl=entry.info), name=f'notifier-d-{id}')
self.event.set()

View file

@ -15,6 +15,7 @@ class ItemDTO:
url: str
quality: str
format: str
preset: str = "default"
folder: str
download_dir: str = None
temp_dir: str = None

View file

@ -333,10 +333,10 @@ class Notifier:
async def completed(self, dl: dict):
await self.emit('completed', dl)
async def canceled(self, id: str):
async def canceled(self, id: str, dl: dict = None):
await self.emit('canceled', id)
async def cleared(self, id: str):
async def cleared(self, id: str, dl: dict = None):
await self.emit('cleared', id)
async def error(self, dl: dict, message: str):

View file

@ -717,19 +717,28 @@ class Main:
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:
async def item_cancel(sid, id: str):
if not id:
await self.notifier.warning('Invalid request.')
return
status: dict[str, str] = {}
status = await self.queue.cancel(ids)
status.update({'identifier': identifier})
status = await self.queue.cancel([id])
status.update({'identifier': id})
await self.sio.emit('cancel_items', self.serializer.encode(status))
await self.sio.emit('item_cancel', self.serializer.encode(status))
@self.sio.event
async def item_delete(sid, id: str):
if not id:
await self.notifier.warning('Invalid request.')
return
status: dict[str, str] = {}
status = await self.queue.clear([id])
status.update({'identifier': id})
await self.sio.emit('item_delete', self.serializer.encode(status))
@self.sio.event()
async def archive_item(sid, data):

View file

@ -46,6 +46,12 @@ hr {
white-space: nowrap;
}
.user-hint {
user-select: none;
cursor: help;
border-bottom: 1px dotted;
}
@media (prefers-color-scheme: dark) {
* {
unicode-bidi: plaintext;

View file

@ -4,7 +4,7 @@
<span class="icon">
<i :class="showCompleted ? 'fa-solid fa-arrow-up' : 'fa-solid fa-arrow-down'" />
</span>
<span>History <span v-if="hasItems">({{ getTotal }})</span></span>
<span>History <span v-if="hasItems">({{ stateStore.count('history') }})</span></span>
</span>
</h1>
@ -24,7 +24,7 @@
</div>
<div class="column is-half-mobile">
<button type="button" class="button is-fullwidth is-danger" :disabled="!hasSelected"
@click="$emit('deleteItem', 'completed', selectedElms); selectedElms = []">
@click="deleteSelectedItems">
<span class="icon-text is-block">
<span class="icon">
<i class="fa-solid fa-trash-can" />
@ -75,7 +75,7 @@
</div>
<div class="columns is-multiline">
<LazyLoader :unrender="true" :min-height="210" class="column is-6" v-for="item in sortCompleted" :key="item._id">
<LateLoader :unrender="true" :min-height="210" class="column is-6" v-for="item in sortCompleted" :key="item._id">
<div class="card"
:class="{ 'is-bordered-danger': item.status === 'error', 'is-bordered-info': item.live_in || item.is_live }">
<header class="card-header has-tooltip">
@ -127,7 +127,8 @@
</span>
</div>
<div class="column is-half-mobile has-text-centered">
<span :date-datetime="item.datetime" v-tooltip="moment(item.datetime).format('YYYY-M-DD H:mm Z')">
<span class="user-hint" :date-datetime="item.datetime"
v-tooltip="moment(item.datetime).format('YYYY-M-DD H:mm Z')">
{{ moment(item.datetime).fromNow() }}
</span>
</div>
@ -150,33 +151,26 @@
</div>
<div class="columns is-mobile is-multiline">
<div class="column is-half-mobile" v-if="item.status != 'finished'">
<a class="button is-warning is-fullwidth" v-tooltip="'Re-queue incomplete download.'"
@click="reQueueItem(item)">
<a class="button is-warning is-fullwidth" v-tooltip="'Re-queue item.'" @click="reQueueItem(item)">
<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>Re-queue</span>
</span>
</a>
</div>
<div class="column is-half-mobile">
<a class="button is-danger is-fullwidth" @click="$emit('deleteItem', 'completed', item._id)">
<a class="button is-danger is-fullwidth" @click="socket.emit('item_delete', item._id)">
<span class="icon-text is-block">
<span class="icon">
<i class="fa-solid fa-trash-can" />
</span>
<span class="icon"><i class="fa-solid fa-trash-can" /></span>
<span>Remove</span>
</span>
</a>
</div>
<div class="column is-half-mobile" v-if="config.app?.keep_archive && item.status != 'finished'">
<a class="button is-danger is-light is-fullwidth" v-tooltip="'Add link to archive.'"
@click="$emit('archiveItem', 'completed', item)">
@click="archiveItem(item)">
<span class="icon-text is-block">
<span class="icon">
<i class="fa-solid fa-box-archive" />
</span>
<span class="icon"><i class="fa-solid fa-box-archive" /></span>
<span>Archive</span>
</span>
</a>
@ -194,11 +188,11 @@
</div>
</div>
</div>
</LazyLoader>
</LateLoader>
</div>
<div class="content has-text-centered" v-if="!hasItems">
<p v-if="config.isConnected">
<p v-if="socket.isConnected">
<span class="icon-text">
<span class="icon has-text-success">
<i class="fa-solid fa-circle-check" />
@ -219,15 +213,14 @@
</template>
<script setup>
import { defineProps, computed, ref, watch, defineEmits } from 'vue';
import moment from "moment";
import moment from 'moment'
import { useStorage } from '@vueuse/core'
import LazyLoader from './LazyLoader'
import { makeDownload, formatBytes, ucFirst } from '~/utils/index'
import toast from '~/plugins/toast'
const emits = defineEmits(['deleteItem', 'addItem', 'playItem', 'archiveItem'])
const config = useConfigStore()
const stateStore = useStateStore()
const socket = useSocketStore()
const selectedElms = ref([]);
const masterSelectAll = ref(false);
@ -257,7 +250,6 @@ const sortCompleted = computed(() => {
const hasSelected = computed(() => selectedElms.value.length > 0)
const hasItems = computed(() => stateStore.count('history') > 0)
const getTotal = computed(() => stateStore.count('history'));
const showMessage = (item) => {
if (!item?.msg || item.msg === item?.error) {
@ -295,18 +287,34 @@ const hasCompleted = computed(() => {
return false;
})
const deleteSelectedItems = () => {
if (selectedElms.value.length < 1) {
toast.error('No items selected.');
return;
}
if (false === confirm('Are you sure you want to delete selected items?')) {
return;
}
for (const key in selectedElms.value) {
const item = stateStore.history[selectedElms.value[key]];
if (item.status === 'finished') {
archiveItem(item);
}
socket.emit('item_delete', item._id);
}
}
const clearCompleted = () => {
const state = confirm('Are you sure you want to clear all completed downloads?');
if (false === state) {
return;
}
const keys = {};
for (const key in stateStore.history) {
if (stateStore.history[key].status === 'finished') {
keys[key] = stateStore.history[key]._id;
stateStore.remove('history', key);
if (ag(stateStore.get('history', key, {}), 'status') === 'finished') {
socket.emit('item_delete', stateStore.history[key]._id);
}
}
}
@ -318,7 +326,7 @@ const clearIncomplete = () => {
for (const key in stateStore.history) {
if (stateStore.history[key].status !== 'finished') {
stateStore.remove('history', key);
socket.emit('item_delete', stateStore.history[key]._id);
}
}
}
@ -349,25 +357,34 @@ const requeueIncomplete = () => {
}
for (const key in stateStore.history) {
const item = stateStore.history[key];
if (item.status !== 'finished') {
emits('deleteItem', 'completed', key);
emits('addItem', {
url: item.url,
format: item.format,
quality: item.quality,
folder: item.folder,
ytdlp_config: item.ytdlp_config,
ytdlp_cookies: item.ytdlp_cookies,
output_template: item.output_template,
});
const item = stateStore.get('history', key, {});
if ('finished' === item.status) {
continue;
}
emits('deleteItem', 'completed', key);
emits('addItem', {
url: item.url,
format: item.preset,
folder: item.folder,
ytdlp_config: item.ytdlp_config,
ytdlp_cookies: item.ytdlp_cookies,
output_template: item.output_template,
});
}
}
const reQueueItem = (item) => {
emits('deleteItem', 'completed', item._id);
emits('addItem', {
const archiveItem = item => {
if (!confirm(`Archive '${item.title ?? item.id ?? item.url ?? '??'}'?`)) {
return
}
socket.emit('archive_item', item);
socket.emit('item_delete', item._id);
}
const reQueueItem = item => {
socket.emit('item_delete', item._id)
socket.emit('add_url', {
url: item.url,
format: item.format,
quality: item.quality,

View file

@ -128,13 +128,11 @@
</template>
<script setup>
import { useStorage, useEventBus } from '@vueuse/core'
import { useStorage } from '@vueuse/core'
const config = useConfigStore();
const socket = useSocketStore();
const bus = useEventBus('item_added', 'task_edit');
const emits = defineEmits(['addItem']);
const toast = useToast();
const selectedFormat = useStorage('selectedFormat', 'any')
const selectedPreset = useStorage('selectedPreset', 'default')
@ -152,8 +150,7 @@ const addDownload = () => {
addInProgress.value = true;
socket.emit('add_url', {
url: url.value,
format: selectedFormat.value,
quality: selectedQuality.value,
preset: selectedPreset.value,
folder: downloadPath.value,
ytdlp_config: ytdlpConfig.value,
ytdlp_cookies: ytdlpCookies.value,
@ -165,8 +162,7 @@ const resetConfig = () => {
if (!confirm('Are you sure you want to reset the local configuration? this will NOT delete any downloads or server configuration.')) {
return;
}
selectedFormat.value = 'any';
selectedQuality.value = '';
selectedPreset.value = 'default';
ytdlpConfig.value = '';
ytdlpCookies.value = '';
output_template.value = null;
@ -174,34 +170,17 @@ const resetConfig = () => {
downloadPath.value = '';
}
bus.on((event, data) => {
const allowedEvents = ['item_added'];
if (!allowedEvents.includes(event)) {
return;
}
if ('item_added' === event) {
if (data?.status === 'ok') {
url.value = '';
}
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)
toast.error(msg)
return
}
notification('success', 'Add success', msg, 3000)
await navigateTo('/')
url.value = '';
}
onMounted(() => socket.on('status', statusHandler))

View file

@ -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.history"
<LateLoader :unrender="true" :min-height="265" class="column is-6" v-for="item in stateStore.queue"
:key="item._id">
<div class="card">
<header class="card-header has-tooltip" v-tooltip="item.title">
@ -98,7 +98,7 @@
</div>
</div>
</div>
</LazyLoader>
</LateLoader>
</div>
<div class="content has-text-centered" v-if="!hasQueuedItems">
@ -125,9 +125,9 @@
<script setup>
import moment from 'moment'
import { useStorage } from '@vueuse/core'
import LazyLoader from '~/components/LazyLoader'
import { ucFirst } from '~/utils/index'
const toast = useToast();
const config = useConfigStore();
const stateStore = useStateStore();
const socket = useSocketStore();
@ -135,7 +135,6 @@ 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) {
@ -149,8 +148,7 @@ watch(masterSelectAll, (value) => {
})
const hasSelected = computed(() => selectedElms.value.length > 0)
const hasQueuedItems = computed(() => stateStore.count('history') > 0)
const getTotal = computed(() => stateStore.count('history'));
const hasQueuedItems = computed(() => stateStore.count('queue') > 0)
const setIcon = item => {
if (item.status === 'downloading' && item.is_live) {
@ -222,11 +220,11 @@ const confirmCancel = item => {
if (true !== confirm(`Are you sure you want to cancel (${item.title})?`)) {
return false;
}
deleteItem('queue', item._id);
cancelItems(item._id);
return true;
}
const deleteItem = (type, item) => {
const cancelItems = item => {
const items = []
if (typeof item === 'object') {
@ -241,51 +239,6 @@ const deleteItem = (type, item) => {
return;
}
actionId.value = makeId();
socket.emit('cancel_items', {
identifier: actionId.value,
ids: items,
});
items.forEach(id => socket.emit('item_cancel', id));
}
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

@ -0,0 +1,2 @@
import { useToast } from "vue-toastification";
export default () => useToast()

View file

@ -21,12 +21,13 @@
</div>
<div class="navbar-item">
<NuxtLink v-tooltip.bottom="'Toggle Add Form'" class="button is-dark has-tooltip-bottom" to="/add">
<button v-tooltip.bottom="'Toggle Add Form'" class="button is-dark has-tooltip-bottom"
@click="config.showForm = !config.showForm">
<span class="icon-text">
<span class="icon"><i class="fa-solid fa-plus" /></span>
<span>Add</span>
</span>
</NuxtLink>
</button>
</div>
<div class="navbar-item" v-if="config.tasks.length > 0">
@ -56,6 +57,7 @@
</div>
</nav>
<NewDownload v-if="config.showForm" />
<NuxtPage />
<div class="columns mt-3 is-mobile">
@ -151,17 +153,4 @@ watch(selectedTheme, value => {
})
const reloadPage = () => window.location.reload()
const toggleForm = () => {
console.log('emitting show_form');
bus.emit('show_form', { foo: 'bar' })
dEvent('show_form', { foo: 'bar' })
}
</script>
<style>
.user-hint {
user-select: none;
cursor: help;
border-bottom: 1px dotted;
}
</style>

View file

@ -8,7 +8,9 @@ export default defineNuxtConfig({
port: 8082,
host: "0.0.0.0",
},
css: [
'vue-toastification/dist/index.css'
],
runtimeConfig: {
public: {
domain: '/',
@ -16,7 +18,9 @@ export default defineNuxtConfig({
version: '2.0.0',
}
},
build: {
transpile: ['vue-toastification'],
},
app: {
buildAssetsDir: "assets",
head: {

View file

@ -23,9 +23,10 @@
"nuxt3-notifications": "^1.2.0",
"pinia": "^2.1.7",
"plyr": "^3.7.8",
"socket.io-client": "^4.7.2",
"vue": "^3.4.21",
"vue-router": "^4.3.0",
"socket.io-client": "^4.7.2"
},
"vue-toastification": "^2.0.0-rc.5"
},
"devDependencies": {}
}

View file

@ -7,4 +7,7 @@
<script setup>
useHead({ title: 'Index' })
const config = useConfigStore()
</script>

9
ui/plugins/toast.js Normal file
View file

@ -0,0 +1,9 @@
import Toast from 'vue-toastification'
import 'vue-toastification/dist/index.css'
export default defineNuxtPlugin(nuxtApp => nuxtApp.vueApp.use(Toast, {
transition: "Vue-Toastification__bounce",
//position: "bottom-right",
maxToasts: 5,
newestOnTop: true
}))

View file

@ -1,10 +1,11 @@
import { io } from "socket.io-client";
import { notification, ag } from "~/utils/index"
import { ag } from "~/utils/index"
export const useSocketStore = defineStore('socket', () => {
const runtimeConfig = useRuntimeConfig()
const config = useConfigStore()
const stateStore = useStateStore()
const toast = useToast()
const socket = ref(null);
const isConnected = ref(false);
@ -31,31 +32,34 @@ export const useSocketStore = defineStore('socket', () => {
socket.value.on('added', stream => {
const item = JSON.parse(stream);
stateStore.add('queue', item._id, item);
notification('success', `Item queued successfully: ${ag(stateStore.get('queue', item._id, {}), 'title')}`);
toast.success(`Item queued successfully: ${ag(stateStore.get('queue', item._id, {}), 'title')}`);
});
socket.value.on('error', stream => {
const [item, error] = JSON.parse(stream);
notification('error', `${item?.id}: Error: ${error}`);
toast.error(`${item?.id}: Error: ${error}`);
});
socket.value.on('completed', stream => {
const item = JSON.parse(stream);
console.log(item)
if (true === stateStore.has('queue', item._id)) {
stateStore.move('queue', 'history', item._id);
return
stateStore.remove('queue', item._id);
}
stateStore.add('history', item);
stateStore.add('history', item._id, item);
});
socket.value.on('canceled', stream => {
const id = JSON.parse(stream);
if (true !== stateStore.has('queue', id)) {
console.log(stream)
return
}
notification('info', `Download canceled: ${ag(stateStore.get('queue', id, {}), 'title')}`);
toast.info(`Download canceled: ${ag(stateStore.get('queue', id, {}), 'title')}`);
if (true === stateStore.has('queue', id)) {
stateStore.remove('queue', id);
@ -64,9 +68,12 @@ export const useSocketStore = defineStore('socket', () => {
socket.value.on('cleared', stream => {
const id = JSON.parse(stream);
if (true !== stateStore.has('history', id)) {
console.log(stream)
return
}
stateStore.remove('history', id);
});

View file

@ -1683,13 +1683,13 @@ braces@^3.0.3, braces@~3.0.2:
fill-range "^7.1.1"
browserslist@^4.0.0, browserslist@^4.23.3, browserslist@^4.24.0:
version "4.24.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580"
integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==
version "4.24.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2"
integrity sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==
dependencies:
caniuse-lite "^1.0.30001669"
electron-to-chromium "^1.5.41"
node-releases "^2.0.18"
caniuse-lite "^1.0.30001688"
electron-to-chromium "^1.5.73"
node-releases "^2.0.19"
update-browserslist-db "^1.1.1"
buffer-crc32@^1.0.0:
@ -1750,7 +1750,7 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669:
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688:
version "1.0.30001688"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz#f9d3ede749f083ce0db4c13db9d828adaf2e8d0a"
integrity sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==
@ -2248,7 +2248,7 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.5.41:
electron-to-chromium@^1.5.73:
version "1.5.73"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz#f32956ce40947fa3c8606726a96cd8fb5bb5f720"
integrity sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==
@ -2864,10 +2864,10 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.13.0:
version "2.15.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37"
integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==
is-core-module@^2.16.0:
version "2.16.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.0.tgz#6c01ffdd5e33c49c1d2abfa93334a85cb56bd81c"
integrity sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==
dependencies:
hasown "^2.0.2"
@ -3468,7 +3468,7 @@ node-gyp-build@^4.2.2:
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8"
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
node-releases@^2.0.18:
node-releases@^2.0.19:
version "2.0.19"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
@ -4182,11 +4182,11 @@ resolve-from@^5.0.0:
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
resolve@^1.22.1:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
version "1.22.9"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.9.tgz#6da76e4cdc57181fa4471231400e8851d0a924f3"
integrity sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==
dependencies:
is-core-module "^2.13.0"
is-core-module "^2.16.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
@ -4640,9 +4640,9 @@ terser@^5.17.4:
source-map-support "~0.5.20"
text-decoder@^1.1.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.2.tgz#63dd2401c43895cecb292e2407db184b50ad60ac"
integrity sha512-/MDslo7ZyWTA2vnk1j7XoDVfXsGk3tp+zFEJHJGm0UjIlQifonVFwlVbQDFh8KJzTBnT8ie115TYqir6bclddA==
version "1.2.3"
resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65"
integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==
dependencies:
b4a "^1.6.4"
@ -5054,6 +5054,11 @@ vue-router@^4.3.0, vue-router@^4.4.5:
dependencies:
"@vue/devtools-api" "^6.6.4"
vue-toastification@^2.0.0-rc.5:
version "2.0.0-rc.5"
resolved "https://registry.yarnpkg.com/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz#92798604d806ae473cfb76ed776fae294280f8f8"
integrity sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==
vue@^3.4.21, vue@^3.5.13:
version "3.5.13"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a"