+
+
+ © {{ Year }} -
YTPTube
+
({{ config.app?.version || 'unknown' }})
+ -
yt-dlp
+
({{ config?.app.ytdlp_version || 'unknown' }})
+
-
-
- v{{ VERSION }}
+
+
+
+ {{ moment.unix(config.app?.started).fromNow() }}
+
+
@@ -95,15 +85,13 @@ import 'assets/css/bulma.css'
import 'assets/css/style.css'
import 'assets/css/all.css'
import { useStorage } from '@vueuse/core'
-import { useConfigStore } from '~/store/ConfigStore'
+import moment from "moment";
-const runtimeConfig = useRuntimeConfig()
-const config = useConfigStore()
+const Year = new Date().getFullYear()
const selectedTheme = useStorage('theme', (() => window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')())
-const showMenu = ref(false)
-const VERSION = ref(runtimeConfig.public.version)
-const expandContainer = ref(false)
const bus = useEventBus('show_form')
+const socket = useSocketStore()
+const config = useConfigStore()
const applyPreferredColorScheme = scheme => {
for (let s = 0; s < document.styleSheets.length; s++) {
@@ -167,3 +155,11 @@ const toggleForm = () => {
dEvent('show_form', { foo: 'bar' })
}
+
+
diff --git a/ui/nuxt.config.ts b/ui/nuxt.config.ts
index c82945ba..a5761247 100644
--- a/ui/nuxt.config.ts
+++ b/ui/nuxt.config.ts
@@ -12,7 +12,7 @@ export default defineNuxtConfig({
runtimeConfig: {
public: {
domain: '/',
- wss: ':8081',
+ wss: ':8081/',
version: '2.0.0',
}
},
diff --git a/ui/pages/console.vue b/ui/pages/console.vue
new file mode 100644
index 00000000..e384ef38
--- /dev/null
+++ b/ui/pages/console.vue
@@ -0,0 +1,157 @@
+
+
+
+
Terminal
+
+ You can use this terminal window to execute non-interactive commands. The interface is jailed to the
+ yt-dlp
+
+
+
+
+
+
+
+
+
+
+
+ yt-dlp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/pages/index.vue b/ui/pages/index.vue
index 95522b1c..e7289771 100644
--- a/ui/pages/index.vue
+++ b/ui/pages/index.vue
@@ -7,18 +7,9 @@
diff --git a/ui/store/ConfigStore.js b/ui/stores/ConfigStore.js
similarity index 92%
rename from ui/store/ConfigStore.js
rename to ui/stores/ConfigStore.js
index 8c5f66d4..e33ecdf1 100644
--- a/ui/store/ConfigStore.js
+++ b/ui/stores/ConfigStore.js
@@ -1,5 +1,3 @@
-import { defineStore } from 'pinia';
-
const CONFIG_KEYS = {
isConnected: false,
showForm: false,
@@ -12,6 +10,12 @@ const CONFIG_KEYS = {
keep_archive: false,
output_template: '',
},
+ presets: [
+ {
+ name: 'Default - Use Predefined yt-dlp Format',
+ format: 'default',
+ }
+ ],
directories: [],
};
diff --git a/ui/stores/SocketStore.js b/ui/stores/SocketStore.js
new file mode 100644
index 00000000..659798cb
--- /dev/null
+++ b/ui/stores/SocketStore.js
@@ -0,0 +1,97 @@
+import { io } from "socket.io-client";
+
+export const useSocketStore = defineStore('socket', () => {
+ const runtimeConfig = useRuntimeConfig()
+ const config = useConfigStore()
+ const stateStore = useStateStore()
+
+ const socket = ref(null);
+ const isConnected = ref(false);
+
+ const connect = () => {
+ socket.value = io(runtimeConfig.public.wss)
+
+ socket.value.on('connect', () => isConnected.value = true);
+ socket.value.on('disconnect', () => isConnected.value = false);
+
+ socket.value.on('initial_data', stream => {
+ const initialData = JSON.parse(stream)
+
+ config.setAll({
+ app: initialData['config'],
+ tasks: initialData['tasks'],
+ directories: initialData['directories'],
+ })
+
+ stateStore.addAll('queue', initialData['queue'] ?? {})
+ stateStore.addAll('history', initialData['done'] ?? {})
+ })
+
+ 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}`);
+ });
+
+ socket.value.on('error', stream => {
+ const [item, error] = JSON.parse(stream);
+ toast.error(`${item?.id}: Error: ${error}`);
+ });
+
+ socket.value.on('completed', stream => {
+ const item = JSON.parse(stream);
+ if (true === stateStore.has('queue', item._id)) {
+ stateStore.move('queue', 'history', item._id);
+ return
+ }
+ stateStore.add('history', item);
+ });
+
+ socket.value.on('canceled', stream => {
+ const id = JSON.parse(stream);
+
+ if (true !== stateStore.has('queue', id)) {
+ return
+ }
+
+ toast.info('Download canceled: ' + stateStore.get('queue', id)?.title);
+
+ if (true === stateStore.has('queue', id)) {
+ stateStore.remove('queue', id);
+ }
+ });
+
+ socket.value.on('cleared', stream => {
+ const id = JSON.parse(stream);
+ if (true !== stateStore.has('history', id)) {
+ return
+ }
+ stateStore.remove('history', id);
+ });
+
+ socket.value.on("updated", stream => {
+ const data = JSON.parse(stream);
+ let dl = stateStore.get('queue', data._id, {});
+ data.deleting = dl?.deleting;
+ stateStore.update('queue', data._id, data);
+ });
+
+ socket.value.on("update", stream => {
+ const data = JSON.parse(stream);
+ if (true === stateStore.has('history', data._id)) {
+ stateStore.update('history', data._id, data);
+ return;
+ }
+ });
+ }
+
+ const on = (event, callback) => socket.value.on(event, callback);
+ const off = (event, callback) => socket.value.off(event, callback);
+ const emit = (event, data) => socket.value.emit(event, data);
+
+ if (false === isConnected.value) {
+ connect();
+ }
+
+ return { connect, on, off, emit, socket, isConnected };
+});
diff --git a/ui/store/StateStore.js b/ui/stores/StateStore.js
similarity index 96%
rename from ui/store/StateStore.js
rename to ui/stores/StateStore.js
index e008c3df..ecf12c5a 100644
--- a/ui/store/StateStore.js
+++ b/ui/stores/StateStore.js
@@ -1,5 +1,3 @@
-import { defineStore } from 'pinia';
-
export const useStateStore = defineStore('state', () => {
const state = reactive({
queue: {},