diff --git a/.vscode/launch.json b/.vscode/launch.json
index 866a9b9b..5c60f268 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -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",
- }
}
]
}
diff --git a/app/Download.py b/app/Download.py
index 2ecf9d28..a63a43af 100644
--- a/app/Download.py
+++ b/app/Download.py
@@ -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}")
diff --git a/app/DownloadQueue.py b/app/DownloadQueue.py
index 9a5c968b..6ed916dc 100644
--- a/app/DownloadQueue.py
+++ b/app/DownloadQueue.py
@@ -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()
diff --git a/app/ItemDTO.py b/app/ItemDTO.py
index ae567dda..5b07fbb6 100644
--- a/app/ItemDTO.py
+++ b/app/ItemDTO.py
@@ -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
diff --git a/app/Utils.py b/app/Utils.py
index 866e6965..8846af7e 100644
--- a/app/Utils.py
+++ b/app/Utils.py
@@ -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):
diff --git a/app/main.py b/app/main.py
index aa82ff2f..25a54c1e 100644
--- a/app/main.py
+++ b/app/main.py
@@ -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):
diff --git a/ui/assets/css/style.css b/ui/assets/css/style.css
index 4492540d..e655e006 100644
--- a/ui/assets/css/style.css
+++ b/ui/assets/css/style.css
@@ -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;
diff --git a/ui/components/History.vue b/ui/components/History.vue
index b335b489..01b0f7c9 100644
--- a/ui/components/History.vue
+++ b/ui/components/History.vue
@@ -4,7 +4,7 @@
- History ({{ getTotal }})
+ History ({{ stateStore.count('history') }})
@@ -24,7 +24,7 @@
-
+
-
+
{{ moment(item.datetime).fromNow() }}
@@ -150,33 +151,26 @@
-
+
-
+
@@ -219,15 +213,14 @@
diff --git a/ui/composables/useToast.js b/ui/composables/useToast.js
new file mode 100644
index 00000000..4738a4ca
--- /dev/null
+++ b/ui/composables/useToast.js
@@ -0,0 +1,2 @@
+import { useToast } from "vue-toastification";
+export default () => useToast()
diff --git a/ui/layouts/default.vue b/ui/layouts/default.vue
index a5a58ded..d1f51e7a 100644
--- a/ui/layouts/default.vue
+++ b/ui/layouts/default.vue
@@ -21,12 +21,13 @@
-
+
+
@@ -56,6 +57,7 @@
+
@@ -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' })
-}
-
-
diff --git a/ui/nuxt.config.ts b/ui/nuxt.config.ts
index a5761247..45c3e6b3 100644
--- a/ui/nuxt.config.ts
+++ b/ui/nuxt.config.ts
@@ -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: {
diff --git a/ui/package.json b/ui/package.json
index 12bb5bed..a986811f 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -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": {}
}
diff --git a/ui/pages/index.vue b/ui/pages/index.vue
index bb14d8f3..bffad7dc 100644
--- a/ui/pages/index.vue
+++ b/ui/pages/index.vue
@@ -7,4 +7,7 @@
diff --git a/ui/plugins/toast.js b/ui/plugins/toast.js
new file mode 100644
index 00000000..d6536428
--- /dev/null
+++ b/ui/plugins/toast.js
@@ -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
+}))
diff --git a/ui/stores/SocketStore.js b/ui/stores/SocketStore.js
index def06ba4..17039fe0 100644
--- a/ui/stores/SocketStore.js
+++ b/ui/stores/SocketStore.js
@@ -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);
});
diff --git a/ui/yarn.lock b/ui/yarn.lock
index c512472d..c3223955 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -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"