commit
238deddb9d
12 changed files with 442 additions and 289 deletions
|
|
@ -223,13 +223,17 @@ cd ui
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Create a `.env` file in the `ui/` directory based on `.env.example` to configure environment variables.
|
||||||
|
> to link the frontend to the backend API.
|
||||||
|
|
||||||
3. **Run the frontend development server:**
|
3. **Run the frontend development server:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
The frontend will be available at `http://localhost:3000` (or the port shown in the terminal)
|
The frontend will be available at `http://localhost:8082` (or the port shown in the terminal)
|
||||||
|
|
||||||
4. **Verify the setup:**
|
4. **Verify the setup:**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -438,7 +438,7 @@ class Notification(metaclass=Singleton):
|
||||||
import apprise
|
import apprise
|
||||||
|
|
||||||
try:
|
try:
|
||||||
notify = apprise.Apprise()
|
notify = apprise.Apprise(debug=self._debug)
|
||||||
apr_config = Path(Config.get_instance().apprise_config)
|
apr_config = Path(Config.get_instance().apprise_config)
|
||||||
if apr_config.exists():
|
if apr_config.exists():
|
||||||
apprise_config = notify.AppriseConfig()
|
apprise_config = notify.AppriseConfig()
|
||||||
|
|
@ -448,11 +448,14 @@ class Notification(metaclass=Singleton):
|
||||||
for t in target:
|
for t in target:
|
||||||
notify.add(t.request.url)
|
notify.add(t.request.url)
|
||||||
|
|
||||||
notify.notify(
|
status = await notify.async_notify(
|
||||||
body=ev.message or json.dumps(ev.serialize(), sort_keys=False, ensure_ascii=False),
|
body=ev.message or json.dumps(ev.serialize(), sort_keys=False, ensure_ascii=False),
|
||||||
title=ev.title or f"YTPTube Event: {ev.event}",
|
title=ev.title or f"YTPTube Event: {ev.event}",
|
||||||
notify_type=ev.event,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
msg = "Apprise failed to send notification."
|
||||||
|
raise RuntimeError(msg) # noqa: TRY301
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.error(f"Error sending Apprise notification: {e!s}")
|
LOG.error(f"Error sending Apprise notification: {e!s}")
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ FRONTEND_ROUTES: list[str] = [
|
||||||
"/logs/",
|
"/logs/",
|
||||||
"/conditions/",
|
"/conditions/",
|
||||||
"/browser/",
|
"/browser/",
|
||||||
|
"/settings/",
|
||||||
"/browser/{path:.*}",
|
"/browser/{path:.*}",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -812,6 +812,8 @@ class TestNotification:
|
||||||
# Mock apprise import
|
# Mock apprise import
|
||||||
mock_apprise = Mock()
|
mock_apprise = Mock()
|
||||||
mock_notify = Mock()
|
mock_notify = Mock()
|
||||||
|
# Mock async_notify as an AsyncMock that returns True
|
||||||
|
mock_notify.async_notify = AsyncMock(return_value=True)
|
||||||
mock_apprise.Apprise.return_value = mock_notify
|
mock_apprise.Apprise.return_value = mock_notify
|
||||||
mock_import.return_value = mock_apprise
|
mock_import.return_value = mock_apprise
|
||||||
|
|
||||||
|
|
|
||||||
2
ui/.env.example
Normal file
2
ui/.env.example
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
NUXT_API_URL="http://localhost:8081/api/"
|
||||||
|
NUXT_PUBLIC_WSS=":8081/"
|
||||||
|
|
@ -43,14 +43,13 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
<a class="navbar-link">
|
<a class="navbar-link is-arrowless">
|
||||||
<span class="icon"><i class="fas fa-bell" /></span>
|
<span class="icon"><i class="fas fa-bell" /></span>
|
||||||
<span class="tag ml-2">
|
<span class="tag ml-2" v-if="store.unreadCount > 0">
|
||||||
<span class="is-underlined">{{ store.unreadCount }}</span>
|
<span class="is-underlined">{{ store.unreadCount }}</span>
|
||||||
<span> / </span>
|
|
||||||
<span class="is-underlined">{{ store.notifications.length }}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="navbar-dropdown is-right" style="width: 400px;">
|
<div class="navbar-dropdown is-right" style="width: 400px;">
|
||||||
<template v-if="store.notifications.length > 0">
|
<template v-if="store.notifications.length > 0">
|
||||||
<div class="px-3 py-2 is-flex is-justify-content-space-between is-align-items-center">
|
<div class="px-3 py-2 is-flex is-justify-content-space-between is-align-items-center">
|
||||||
|
|
@ -113,6 +112,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="navbar-item">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="columns is-multiline">
|
|
||||||
<div class="column is-12 mt-2">
|
|
||||||
<div class="card">
|
|
||||||
<header class="card-header">
|
|
||||||
<p class="card-header-title">WebUI Settings</p>
|
|
||||||
<span class="card-header-icon">
|
|
||||||
<span class="icon"><i class="fas fa-cog" /></span>
|
|
||||||
</span>
|
|
||||||
</header>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="columns is-multiline">
|
|
||||||
<div class="column is-6">
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label is-unselectable">
|
|
||||||
<span class="icon"><i class="fa-solid fa-computer" /></span>
|
|
||||||
Page View
|
|
||||||
</label>
|
|
||||||
<div class="control">
|
|
||||||
<div class="control">
|
|
||||||
<input id="view_mode" type="checkbox" class="switch is-success" v-model="simpleMode">
|
|
||||||
<label for="view_mode" class="is-unselectable">
|
|
||||||
{{ simpleMode ? 'Simple View' : 'Regular View' }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="help is-bold">
|
|
||||||
<span class="icon"> <i class="fa-solid fa-info-circle" /></span>
|
|
||||||
The simple view is ideal for non-technical users and mobile devices.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label is-unselectable">Color scheme</label>
|
|
||||||
<div class="control">
|
|
||||||
<label for="auto" class="radio">
|
|
||||||
<input id="auto" type="radio" v-model="selectedTheme" value="auto">
|
|
||||||
<span class="icon"><i class="fa-solid fa-circle-half-stroke" /></span>
|
|
||||||
<span>Auto</span>
|
|
||||||
</label>
|
|
||||||
<label for="light" class="radio">
|
|
||||||
<input id="light" type="radio" v-model="selectedTheme" value="light">
|
|
||||||
<span class="icon has-text-warning"><i class="fa-solid fa-sun" /></span>
|
|
||||||
<span>Light</span>
|
|
||||||
</label>
|
|
||||||
<label for="dark" class="radio">
|
|
||||||
<input id="dark" type="radio" v-model="selectedTheme" value="dark">
|
|
||||||
<span class="icon"><i class="fa-solid fa-moon" /></span>
|
|
||||||
<span>Dark</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label is-unselectable">
|
|
||||||
Show Background
|
|
||||||
<template v-if="bg_enable">
|
|
||||||
<NuxtLink @click="$emit('reload_bg')" class="is-bold">Reload</NuxtLink>
|
|
||||||
<span class="icon" v-if="isLoading"><i class="fa fa-spin fa-spinner" /></span>
|
|
||||||
</template>
|
|
||||||
</label>
|
|
||||||
<div class="control">
|
|
||||||
<input id="random_bg" type="checkbox" class="switch is-success" v-model="bg_enable">
|
|
||||||
<label for="random_bg" class="is-unselectable">
|
|
||||||
{{ bg_enable ? 'Yes' : 'No' }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label is-unselectable" for="random_bg_opacity">
|
|
||||||
Background visibility <code>{{ parseFloat(String(1.0 - bg_opacity)).toFixed(2) }}</code>
|
|
||||||
</label>
|
|
||||||
<div class="control">
|
|
||||||
<input id="random_bg_opacity" style="width: 100%" type="range" v-model="bg_opacity" min="0.50"
|
|
||||||
max="1.00" step="0.05">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field" v-if="!simpleMode">
|
|
||||||
<label class="label is-unselectable" for="show_thumbnail">URLs Separator</label>
|
|
||||||
<div class="control">
|
|
||||||
<div class="select is-fullwidth">
|
|
||||||
<select class="is-fullwidth" v-model="separator">
|
|
||||||
<option v-for="(sep, index) in separators" :key="`sep-${index}`" :value="sep.value">
|
|
||||||
{{ sep.name }} ({{ sep.value }})
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="column is-6">
|
|
||||||
<div class="field">
|
|
||||||
<label class="label is-unselectable" for="show_thumbnail">Show Thumbnails</label>
|
|
||||||
<div class="control">
|
|
||||||
<input id="show_thumbnail" type="checkbox" class="switch is-success" v-model="show_thumbnail">
|
|
||||||
<label for="show_thumbnail" class="is-unselectable">
|
|
||||||
{{ show_thumbnail ? 'Yes' : 'No' }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<p class="help is-bold"> Show videos thumbnail if available.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field" v-if="show_thumbnail">
|
|
||||||
<label class="label is-unselectable">Thumbnail Aspect Ratio</label>
|
|
||||||
<div class="control">
|
|
||||||
<label for="ratio_16by9" class="radio">
|
|
||||||
<input id="ratio_16by9" type="radio" v-model="thumbnail_ratio" value="is-16by9">
|
|
||||||
<span> 16:9</span>
|
|
||||||
</label>
|
|
||||||
<label for="ratio_3by1" class="radio">
|
|
||||||
<input id="ratio_3by1" type="radio" v-model="thumbnail_ratio" value="is-3by1">
|
|
||||||
<span> 3:1</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<p class="help is-bold">Choose the aspect ratio for thumbnail display.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="allow_toasts">Show notifications</label>
|
|
||||||
<div class="control">
|
|
||||||
<input id="allow_toasts" type="checkbox" class="switch is-success" v-model="allow_toasts">
|
|
||||||
<label for="allow_toasts" class="is-unselectable">
|
|
||||||
{{ allow_toasts ? 'Yes' : 'No' }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field" v-if="allow_toasts">
|
|
||||||
<label class="label">Notifications position</label>
|
|
||||||
<div class="control">
|
|
||||||
<div class="select is-fullwidth">
|
|
||||||
<select v-model="toast_position">
|
|
||||||
<option :value="POSITION.TOP_RIGHT">{{ POSITION.TOP_RIGHT }}</option>
|
|
||||||
<option :value="POSITION.TOP_CENTER">{{ POSITION.TOP_CENTER }}</option>
|
|
||||||
<option :value="POSITION.TOP_LEFT">{{ POSITION.TOP_LEFT }}</option>
|
|
||||||
<option :value="POSITION.BOTTOM_RIGHT">{{ POSITION.BOTTOM_RIGHT }}</option>
|
|
||||||
<option :value="POSITION.BOTTOM_CENTER">{{ POSITION.BOTTOM_CENTER }}</option>
|
|
||||||
<option :value="POSITION.BOTTOM_LEFT">{{ POSITION.BOTTOM_LEFT }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field" v-if="allow_toasts">
|
|
||||||
<label class="label" for="dismiss_on_click">Dismiss notification on click</label>
|
|
||||||
<div class="control">
|
|
||||||
<input id="dismiss_on_click" type="checkbox" class="switch is-success"
|
|
||||||
v-model="toast_dismiss_on_click">
|
|
||||||
<label for="dismiss_on_click" class="is-unselectable">
|
|
||||||
{{ toast_dismiss_on_click ? 'Yes' : 'No' }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useStorage } from '@vueuse/core'
|
|
||||||
import { POSITION } from 'vue-toastification'
|
|
||||||
import { useConfigStore } from '~/stores/ConfigStore'
|
|
||||||
|
|
||||||
defineProps<{ isLoading: boolean }>()
|
|
||||||
defineEmits<{ (e: 'reload_bg'): void }>()
|
|
||||||
|
|
||||||
const bg_enable = useStorage<boolean>('random_bg', true)
|
|
||||||
const bg_opacity = useStorage<number>('random_bg_opacity', 0.95)
|
|
||||||
const selectedTheme = useStorage<'auto' | 'light' | 'dark'>('theme', 'auto')
|
|
||||||
const allow_toasts = useStorage<boolean>('allow_toasts', true)
|
|
||||||
const toast_position = useStorage<POSITION>('toast_position', POSITION.TOP_RIGHT)
|
|
||||||
const toast_dismiss_on_click = useStorage<boolean>('toast_dismiss_on_click', true)
|
|
||||||
const show_thumbnail = useStorage<boolean>('show_thumbnail', true)
|
|
||||||
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
|
||||||
const separator = useStorage<string>('url_separator', separators[0]?.value ?? ',')
|
|
||||||
const simpleMode = useStorage<boolean>('simple_mode', useConfigStore().app.simple_mode || false)
|
|
||||||
</script>
|
|
||||||
|
|
@ -5,11 +5,9 @@
|
||||||
<form class="download-form__body" autocomplete="off" @submit.prevent="addDownload">
|
<form class="download-form__body" autocomplete="off" @submit.prevent="addDownload">
|
||||||
<label class="label" for="download-url">
|
<label class="label" for="download-url">
|
||||||
What you would like to download?
|
What you would like to download?
|
||||||
<span class="is-pulled-right">
|
<span class="icon is-pointer" :class="connectionStatusColor" @click="$emit('show_settings')"
|
||||||
<span class="icon is-pointer" :class="connectionStatusColor" @click="$emit('show_settings')"
|
v-tooltip="'WebUI Settings'">
|
||||||
v-tooltip="'WebUI Settings'">
|
<i class="fas fa-cogs" /></span>
|
||||||
<i class="fas fa-cogs" /></span>
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|
@ -616,6 +614,7 @@ const connectionStatusColor = computed(() => {
|
||||||
return 'has-text-danger'
|
return 'has-text-danger'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,28 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-6-tablet is-12-mobile" v-if="!reference">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-inline" for="archive_all_after_add">
|
||||||
|
<span class="icon"><i class="fa-solid fa-box-archive" /></span>
|
||||||
|
Mark all existing items as downloaded
|
||||||
|
</label>
|
||||||
|
<div class="control is-unselectable">
|
||||||
|
<input id="archive_all_after_add" type="checkbox" v-model="archiveAllAfterAdd" :disabled="addInProgress"
|
||||||
|
class="switch is-danger" />
|
||||||
|
<label for="archive_all_after_add" class="is-unselectable">
|
||||||
|
{{ archiveAllAfterAdd ? 'Yes' : 'No' }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<span class="help">
|
||||||
|
<span class="icon"><i class="fa-solid fa-info" /></span>
|
||||||
|
<span class="is-bold">
|
||||||
|
If enabled, all existing items in the feed will be marked as downloaded after adding the task.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="column is-12">
|
<div class="column is-12">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label is-unselectable" for="cli_options">
|
<label class="label is-unselectable" for="cli_options">
|
||||||
|
|
@ -317,7 +339,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const emitter = defineEmits<{
|
const emitter = defineEmits<{
|
||||||
(e: 'cancel'): void
|
(e: 'cancel'): void
|
||||||
(e: 'submit', payload: { reference: string | null | undefined, task: task_item }): void
|
(e: 'submit', payload: { reference: string | null | undefined, task: task_item, archive_all?: boolean }): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
|
|
@ -329,6 +351,7 @@ const convertInProgress = ref<boolean>(false)
|
||||||
const import_string = ref<string>('')
|
const import_string = ref<string>('')
|
||||||
const showOptions = ref<boolean>(false)
|
const showOptions = ref<boolean>(false)
|
||||||
const ytDlpOpt = ref<AutoCompleteOptions>([])
|
const ytDlpOpt = ref<AutoCompleteOptions>([])
|
||||||
|
const archiveAllAfterAdd = ref<boolean>(false)
|
||||||
|
|
||||||
const CHANNEL_REGEX = /^https?:\/\/(?:www\.)?youtube\.com\/(?:(?:channel\/(?<channelId>UC[0-9A-Za-z_-]{22}))|(?:c\/(?<customName>[A-Za-z0-9_-]+))|(?:user\/(?<userName>[A-Za-z0-9_-]+))|(?:@(?<handle>[A-Za-z0-9_-]+)))(?<suffix>\/.*)?\/?$/
|
const CHANNEL_REGEX = /^https?:\/\/(?:www\.)?youtube\.com\/(?:(?:channel\/(?<channelId>UC[0-9A-Za-z_-]{22}))|(?:c\/(?<customName>[A-Za-z0-9_-]+))|(?:user\/(?<userName>[A-Za-z0-9_-]+))|(?:@(?<handle>[A-Za-z0-9_-]+)))(?<suffix>\/.*)?\/?$/
|
||||||
|
|
||||||
|
|
@ -389,7 +412,7 @@ const checkInfo = async (): Promise<void> => {
|
||||||
form.cli = form.cli.trim()
|
form.cli = form.cli.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter('submit', { reference: toRaw(props.reference), task: toRaw(form) })
|
emitter('submit', { reference: toRaw(props.reference), task: toRaw(form), archive_all: archiveAllAfterAdd.value })
|
||||||
}
|
}
|
||||||
|
|
||||||
const importItem = async (): Promise<void> => {
|
const importItem = async (): Promise<void> => {
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,27 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="show_settings">
|
<template v-if="show_settings">
|
||||||
<Modal @close="show_settings = false"
|
<Modal @close="closeSettings()"
|
||||||
:content-class="isMobile ? 'modal-content-max is-overflow-scroll ' : 'modal-content-max is-overflow-visible'">
|
:content-class="isMobile ? 'modal-content-max is-overflow-scroll ' : 'modal-content-max'">
|
||||||
<Settings v-if="show_settings" :isLoading="loadingImage" @reload_bg="() => loadImage(true)" />
|
<div class="columns is-multiline">
|
||||||
|
<div class="column is-12 mt-2">
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">WebUI Settings</p>
|
||||||
|
<span class="card-header-icon">
|
||||||
|
<span class="icon"><i class="fas fa-cog" /></span>
|
||||||
|
</span>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
<div class="column is-12">
|
||||||
|
<settings v-if="show_settings" :isLoading="loadingImage" @reload_bg="() => loadImage(true)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -23,9 +41,8 @@
|
||||||
<NuxtLink class="navbar-item is-text-overflow" to="/" @click.prevent="(e: MouseEvent) => changeRoute(e)"
|
<NuxtLink class="navbar-item is-text-overflow" to="/" @click.prevent="(e: MouseEvent) => changeRoute(e)"
|
||||||
v-tooltip="socket.isConnected ? 'Connected' : 'Connecting'">
|
v-tooltip="socket.isConnected ? 'Connected' : 'Connecting'">
|
||||||
<span class="is-text-overflow">
|
<span class="is-text-overflow">
|
||||||
<span class="icon">
|
<span class="icon" v-if="'connecting' === socket.connectionStatus">
|
||||||
<i v-if="'connecting' === socket.connectionStatus" class="fas fa-arrows-rotate fa-spin" />
|
<i class="fas fa-arrows-rotate fa-spin" />
|
||||||
<i v-else class="fas fa-home" />
|
|
||||||
</span>
|
</span>
|
||||||
<span class="has-text-bold" :class="connectionStatusColor">
|
<span class="has-text-bold" :class="connectionStatusColor">
|
||||||
YTPTube
|
YTPTube
|
||||||
|
|
@ -43,6 +60,12 @@
|
||||||
|
|
||||||
<div class="navbar-menu is-unselectable" :class="{ 'is-active': showMenu }">
|
<div class="navbar-menu is-unselectable" :class="{ 'is-active': showMenu }">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
|
|
||||||
|
<NuxtLink class="navbar-item" to="/" @click.prevent="(e: MouseEvent) => changeRoute(e)">
|
||||||
|
<span class="icon"><i class="fa-solid fa-home" /></span>
|
||||||
|
<span>Home</span>
|
||||||
|
</NuxtLink>
|
||||||
|
|
||||||
<NuxtLink class="navbar-item" to="/browser" @click.prevent="(e: MouseEvent) => changeRoute(e)">
|
<NuxtLink class="navbar-item" to="/browser" @click.prevent="(e: MouseEvent) => changeRoute(e)">
|
||||||
<span class="icon"><i class="fa-solid fa-folder-tree" /></span>
|
<span class="icon"><i class="fa-solid fa-folder-tree" /></span>
|
||||||
<span>Files</span>
|
<span>Files</span>
|
||||||
|
|
@ -52,23 +75,16 @@
|
||||||
<span class="icon"><i class="fa-solid fa-sliders" /></span>
|
<span class="icon"><i class="fa-solid fa-sliders" /></span>
|
||||||
<span>Presets</span>
|
<span>Presets</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div class="navbar-item has-dropdown">
|
|
||||||
<a class="navbar-link" @click="(e: MouseEvent) => openMenu(e)">
|
|
||||||
<span class="icon"><i class="fas fa-tasks" /></span>
|
|
||||||
<span>Tasks</span>
|
|
||||||
</a>
|
|
||||||
<div class="navbar-dropdown">
|
|
||||||
<NuxtLink class="navbar-item" to="/tasks" @click.prevent="(e: MouseEvent) => changeRoute(e)">
|
|
||||||
<span class="icon"><i class="fa-solid fa-tasks" /></span>
|
|
||||||
<span>List</span>
|
|
||||||
</NuxtLink>
|
|
||||||
|
|
||||||
<NuxtLink class="navbar-item" to="/task_definitions" @click.prevent="(e: MouseEvent) => changeRoute(e)">
|
<NuxtLink class="navbar-item" to="/tasks" @click.prevent="(e: MouseEvent) => changeRoute(e)">
|
||||||
<span class="icon"><i class="fa-solid fa-diagram-project" /></span>
|
<span class="icon"><i class="fa-solid fa-tasks" /></span>
|
||||||
<span>Definitions</span>
|
<span>Tasks</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
|
||||||
</div>
|
<NuxtLink class="navbar-item" to="/task_definitions" @click.prevent="(e: MouseEvent) => changeRoute(e)">
|
||||||
|
<span class="icon"><i class="fa-solid fa-diagram-project" /></span>
|
||||||
|
<span>Task Definitions</span>
|
||||||
|
</NuxtLink>
|
||||||
|
|
||||||
<NuxtLink class="navbar-item" to="/notifications" @click.prevent="(e: MouseEvent) => changeRoute(e)">
|
<NuxtLink class="navbar-item" to="/notifications" @click.prevent="(e: MouseEvent) => changeRoute(e)">
|
||||||
<span class="icon-text">
|
<span class="icon-text">
|
||||||
|
|
@ -84,63 +100,23 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<div class="navbar-item has-dropdown">
|
|
||||||
<a class="navbar-link" @click="(e: MouseEvent) => openMenu(e)">
|
|
||||||
<span class="icon"><i class="fas fa-tools" /></span>
|
|
||||||
<span>Other</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="navbar-dropdown">
|
|
||||||
<NuxtLink class="navbar-item" to="/logs" @click.prevent="(e: MouseEvent) => changeRoute(e)"
|
|
||||||
v-if="config.app.file_logging">
|
|
||||||
<span class="icon"><i class="fa-solid fa-file-lines" /></span>
|
|
||||||
<span>Logs</span>
|
|
||||||
</NuxtLink>
|
|
||||||
|
|
||||||
<NuxtLink class="navbar-item" to="/console" @click.prevent="(e: MouseEvent) => changeRoute(e)"
|
|
||||||
v-if="config.app.console_enabled">
|
|
||||||
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
|
||||||
<span>Console</span>
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="navbar-item" v-if="true === config.app.is_native">
|
<div class="navbar-item" v-if="true === config.app.is_native">
|
||||||
<button class="button is-dark" @click="shutdownApp">
|
<button class="button is-dark" @click="shutdownApp">
|
||||||
<span class="icon"><i class="fas fa-power-off" /></span>
|
<span class="icon"><i class="fas fa-power-off" /></span>
|
||||||
<span v-if="isMobile">Shutdown</span>
|
<span v-if="isMobile">Shutdown</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<NuxtLink class="navbar-item" to="/settings" @click.prevent="(e: MouseEvent) => changeRoute(e)">
|
||||||
|
<span class="icon"><i class="fa-solid fa-cog" /></span>
|
||||||
<div class="navbar-item">
|
<span>Settings</span>
|
||||||
<button class="button is-dark" @click="reloadPage">
|
</NuxtLink>
|
||||||
<span class="icon"><i class="fas fa-refresh" /></span>
|
|
||||||
<span v-if="isMobile">Reload</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<NotifyDropdown />
|
<NotifyDropdown />
|
||||||
|
|
||||||
<div class="navbar-item" v-if="!isMobile">
|
|
||||||
<button class="button is-dark has-tooltip-bottom mr-4" v-tooltip.bottom="'WebUI Settings'"
|
|
||||||
@click="show_settings = !show_settings">
|
|
||||||
<span class="icon"><i class="fas fa-cog" /></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-item" v-if="isMobile">
|
|
||||||
<button class="button is-dark" @click="show_settings = !show_settings">
|
|
||||||
<span class="icon"><i class="fas fa-cog" /></span>
|
|
||||||
<span>WebUI Settings</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div>
|
<div>
|
||||||
<NuxtLoadingIndicator />
|
<NuxtLoadingIndicator />
|
||||||
<NuxtPage v-if="config.is_loaded" />
|
<NuxtPage v-if="config.is_loaded" :isLoading="loadingImage" @reload_bg="() => loadImage(true)" />
|
||||||
<Message v-if="!config.is_loaded" class="has-background-info-90 has-text-dark mt-5"
|
<Message v-if="!config.is_loaded" class="has-background-info-90 has-text-dark mt-5"
|
||||||
title="Loading Configuration" icon="fas fa-spinner fa-spin">
|
title="Loading Configuration" icon="fas fa-spinner fa-spin">
|
||||||
<p>Loading application configuration. This usually takes less than a second.</p>
|
<p>Loading application configuration. This usually takes less than a second.</p>
|
||||||
|
|
@ -197,13 +173,13 @@ import Simple from '~/components/Simple.vue'
|
||||||
import Shutdown from '~/components/shutdown.vue'
|
import Shutdown from '~/components/shutdown.vue'
|
||||||
import Markdown from '~/components/Markdown.vue'
|
import Markdown from '~/components/Markdown.vue'
|
||||||
import Connection from '~/components/Connection.vue'
|
import Connection from '~/components/Connection.vue'
|
||||||
|
import Settings from "~/pages/settings.vue";
|
||||||
|
|
||||||
const Year = new Date().getFullYear()
|
const Year = new Date().getFullYear()
|
||||||
const selectedTheme = useStorage('theme', 'auto')
|
const selectedTheme = useStorage('theme', 'auto')
|
||||||
const socket = useSocketStore()
|
const socket = useSocketStore()
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
const loadedImage = ref()
|
const loadedImage = ref()
|
||||||
const show_settings = ref(false)
|
|
||||||
const loadingImage = ref(false)
|
const loadingImage = ref(false)
|
||||||
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)
|
||||||
|
|
@ -211,6 +187,7 @@ const showMenu = ref(false)
|
||||||
const isMobile = useMediaQuery({ maxWidth: 1024 })
|
const isMobile = useMediaQuery({ maxWidth: 1024 })
|
||||||
const app_shutdown = ref<boolean>(false)
|
const app_shutdown = ref<boolean>(false)
|
||||||
const simpleMode = useStorage<boolean>('simple_mode', config.app.simple_mode || false)
|
const simpleMode = useStorage<boolean>('simple_mode', config.app.simple_mode || false)
|
||||||
|
const show_settings = ref(false)
|
||||||
|
|
||||||
const doc = ref<{ file: string }>({ file: '' })
|
const doc = ref<{ file: string }>({ file: '' })
|
||||||
|
|
||||||
|
|
@ -315,8 +292,6 @@ watch(selectedTheme, value => {
|
||||||
} catch { }
|
} catch { }
|
||||||
})
|
})
|
||||||
|
|
||||||
const reloadPage = () => window.location.reload()
|
|
||||||
|
|
||||||
watch(bg_enable, async v => await handleImage(v))
|
watch(bg_enable, async v => await handleImage(v))
|
||||||
watch(bg_opacity, v => {
|
watch(bg_opacity, v => {
|
||||||
if (false === bg_enable.value) {
|
if (false === bg_enable.value) {
|
||||||
|
|
@ -408,18 +383,6 @@ const changeRoute = async (_: MouseEvent, callback: (() => void) | null = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openMenu = (e: MouseEvent) => {
|
|
||||||
const elm = (e.target as HTMLElement)?.closest('div.has-dropdown') as HTMLElement | null
|
|
||||||
|
|
||||||
document.querySelectorAll<HTMLElement>('div.has-dropdown').forEach(el => {
|
|
||||||
if (el !== elm) {
|
|
||||||
el.classList.remove('is-active')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
elm?.classList.toggle('is-active')
|
|
||||||
}
|
|
||||||
|
|
||||||
const useVersionUpdate = () => {
|
const useVersionUpdate = () => {
|
||||||
const newVersionIsAvailable = ref(false)
|
const newVersionIsAvailable = ref(false)
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
|
@ -436,6 +399,11 @@ const useVersionUpdate = () => {
|
||||||
|
|
||||||
const { newVersionIsAvailable } = useVersionUpdate()
|
const { newVersionIsAvailable } = useVersionUpdate()
|
||||||
|
|
||||||
|
const closeSettings = () => {
|
||||||
|
show_settings.value = false
|
||||||
|
navigateTo('/')
|
||||||
|
}
|
||||||
|
|
||||||
const shutdownApp = async () => {
|
const shutdownApp = async () => {
|
||||||
const { alertDialog, confirmDialog: confirm_message } = useDialog()
|
const { alertDialog, confirmDialog: confirm_message } = useDialog()
|
||||||
|
|
||||||
|
|
|
||||||
318
ui/app/pages/settings.vue
Normal file
318
ui/app/pages/settings.vue
Normal file
|
|
@ -0,0 +1,318 @@
|
||||||
|
<template>
|
||||||
|
<main class="m-6">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="title is-4">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon"><i class="fas fa-cog" /></span>
|
||||||
|
<p class="card-header-title">Settings</p>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="field is-horizontal" />
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Page View</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control is-expanded has-icons-left">
|
||||||
|
<input id="view_mode" type="checkbox" class="switch is-success" v-model="simpleMode">
|
||||||
|
<label for="view_mode" class="is-unselectable">
|
||||||
|
{{ simpleMode ? 'Simple View' : 'Regular View' }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="help">
|
||||||
|
<span class="icon"> <i class="fa-solid fa-info-circle" /></span>
|
||||||
|
The simple view is ideal for non-technical users and mobile devices.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<span class="field title is-4">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon"><i class="fas fa-palette" /></span>
|
||||||
|
<p class="card-header-title">Theming</p>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="field is-horizontal" />
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label">
|
||||||
|
<label class="label">Color scheme</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field is-narrow">
|
||||||
|
<div class="control">
|
||||||
|
<label for="auto" class="radio">
|
||||||
|
<input id="auto" type="radio" v-model="selectedTheme" value="auto">
|
||||||
|
<span class="icon"><i class="fa-solid fa-circle-half-stroke" /></span>
|
||||||
|
<span>Auto</span>
|
||||||
|
</label>
|
||||||
|
<label for="light" class="radio">
|
||||||
|
<input id="light" type="radio" v-model="selectedTheme" value="light">
|
||||||
|
<span class="icon has-text-warning"><i class="fa-solid fa-sun" /></span>
|
||||||
|
<span>Light</span>
|
||||||
|
</label>
|
||||||
|
<label for="dark" class="radio">
|
||||||
|
<input id="dark" type="radio" v-model="selectedTheme" value="dark">
|
||||||
|
<span class="icon"><i class="fa-solid fa-moon" /></span>
|
||||||
|
<span>Dark</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Show Background</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input id="random_bg" type="checkbox" class="switch is-success" v-model="bg_enable">
|
||||||
|
<label for="random_bg" class="is-unselectable">
|
||||||
|
{{ bg_enable ? 'Yes' : 'No' }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label"></label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<template v-if="bg_enable">
|
||||||
|
<button @click="$emit('reload_bg')" class="button is-primary" :disabled="isLoading">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon"><i class="fa"
|
||||||
|
:class="{ 'fa-spin fa-spinner': isLoading, 'fa-file-image': !isLoading }" /></span>
|
||||||
|
<span>Reload Background</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Background visibility</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field is-expanded">
|
||||||
|
<a class="button is-static is-small">
|
||||||
|
<code>{{ parseFloat(String(1.0 - bg_opacity)).toFixed(2) }}</code>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="control">
|
||||||
|
<input id="random_bg_opacity" style="width: 100%" type="range" v-model="bg_opacity" min="0.50" max="1.00"
|
||||||
|
step="0.05">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="field title is-4">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon"><i class="fas fa-home" /></span>
|
||||||
|
<p class="card-header-title">Dashboard</p>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="field is-horizontal" />
|
||||||
|
|
||||||
|
<div class="field is-horizontal" v-if="!simpleMode">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">URL Separator</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field is-narrow">
|
||||||
|
<div class="control">
|
||||||
|
<div class="select is-fullwidth">
|
||||||
|
<select v-model="separator">
|
||||||
|
<option v-for="(sep, index) in separators" :key="`sep-${index}`" :value="sep.value">
|
||||||
|
{{ sep.name }} ({{ sep.value }})
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Show Thumbnails</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="control">
|
||||||
|
<div class="field">
|
||||||
|
<input id="show_thumbnail" type="checkbox" class="switch is-success" v-model="show_thumbnail">
|
||||||
|
<label for="show_thumbnail" class="is-unselectable">
|
||||||
|
{{ show_thumbnail ? 'Yes' : 'No' }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="help">
|
||||||
|
<span class="icon"><i class="fa-solid fa-info-circle" /></span>
|
||||||
|
Show videos thumbnail if available
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal" v-if="show_thumbnail">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Aspect Ratio</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="control">
|
||||||
|
<div class="field">
|
||||||
|
<label for="ratio_16by9" class="radio">
|
||||||
|
<input id="ratio_16by9" type="radio" v-model="thumbnail_ratio" value="is-16by9">
|
||||||
|
<span> 16:9</span>
|
||||||
|
</label>
|
||||||
|
<label for="ratio_3by1" class="radio">
|
||||||
|
<input id="ratio_3by1" type="radio" v-model="thumbnail_ratio" value="is-3by1">
|
||||||
|
<span> 3:1</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="help">
|
||||||
|
<span class="icon"><i class="fa-solid fa-info-circle" /></span>
|
||||||
|
Choose the aspect ratio for thumbnail display.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="field title is-4">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon"><i class="fas fa-bell" /></span>
|
||||||
|
<p class="card-header-title">On-Screen Notifications</p>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="field is-horizontal" />
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Show notifications</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="control">
|
||||||
|
<div class="field">
|
||||||
|
<input id="allow_toasts" type="checkbox" class="switch is-success" v-model="allow_toasts">
|
||||||
|
<label for="allow_toasts" class="is-unselectable">
|
||||||
|
{{ allow_toasts ? 'Yes' : 'No' }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal" v-if="allow_toasts">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Notifications position</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field is-narrow">
|
||||||
|
<div class="control">
|
||||||
|
<div class="select is-fullwidth">
|
||||||
|
<select v-model="toast_position">
|
||||||
|
<option :value="POSITION.TOP_RIGHT">{{ POSITION.TOP_RIGHT }}</option>
|
||||||
|
<option :value="POSITION.TOP_CENTER">{{ POSITION.TOP_CENTER }}</option>
|
||||||
|
<option :value="POSITION.TOP_LEFT">{{ POSITION.TOP_LEFT }}</option>
|
||||||
|
<option :value="POSITION.BOTTOM_RIGHT">{{ POSITION.BOTTOM_RIGHT }}</option>
|
||||||
|
<option :value="POSITION.BOTTOM_CENTER">{{ POSITION.BOTTOM_CENTER }}</option>
|
||||||
|
<option :value="POSITION.BOTTOM_LEFT">{{ POSITION.BOTTOM_LEFT }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal" v-if="allow_toasts">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Dismiss notification on click</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="control">
|
||||||
|
<div class="field">
|
||||||
|
<input id="dismiss_on_click" type="checkbox" class="switch is-success" v-model="toast_dismiss_on_click">
|
||||||
|
<label for="dismiss_on_click" class="is-unselectable">
|
||||||
|
{{ toast_dismiss_on_click ? 'Yes' : 'No' }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="!simpleMode">
|
||||||
|
<span class="field is-horizontal" />
|
||||||
|
<span class="title is-4">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon"><i class="fas fa-cog" /></span>
|
||||||
|
<p class="card-header-title">Application Settings</p>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="field is-horizontal" />
|
||||||
|
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label"></label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control" v-if="config.app.file_logging">
|
||||||
|
<NuxtLink class="button is-primary" to="/logs">
|
||||||
|
<span class="icon"><i class="fa-solid fa-file-lines" /></span>
|
||||||
|
<span>Open Logs</span>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
<div class="control" v-if="config.app.console_enabled">
|
||||||
|
<NuxtLink class="button is-primary" to="/console">
|
||||||
|
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
||||||
|
<span>Open Console</span>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import { POSITION } from 'vue-toastification'
|
||||||
|
import { useConfigStore } from '~/stores/ConfigStore'
|
||||||
|
|
||||||
|
defineProps<{ isLoading: boolean }>()
|
||||||
|
defineEmits<{ (e: 'reload_bg'): void }>()
|
||||||
|
|
||||||
|
const config = useConfigStore()
|
||||||
|
|
||||||
|
const bg_enable = useStorage<boolean>('random_bg', true)
|
||||||
|
const bg_opacity = useStorage<number>('random_bg_opacity', 0.95)
|
||||||
|
const selectedTheme = useStorage<'auto' | 'light' | 'dark'>('theme', 'auto')
|
||||||
|
const allow_toasts = useStorage<boolean>('allow_toasts', true)
|
||||||
|
const toast_position = useStorage<POSITION>('toast_position', POSITION.TOP_RIGHT)
|
||||||
|
const toast_dismiss_on_click = useStorage<boolean>('toast_dismiss_on_click', true)
|
||||||
|
const show_thumbnail = useStorage<boolean>('show_thumbnail', true)
|
||||||
|
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
||||||
|
const separator = useStorage<string>('url_separator', separators[0]?.value ?? ',')
|
||||||
|
const simpleMode = useStorage<boolean>('simple_mode', config.app.simple_mode || false)
|
||||||
|
</script>
|
||||||
|
|
@ -422,6 +422,7 @@ import Modal from '~/components/Modal.vue'
|
||||||
import { useConfirm } from '~/composables/useConfirm'
|
import { useConfirm } from '~/composables/useConfirm'
|
||||||
import TaskInspect from '~/components/TaskInspect.vue'
|
import TaskInspect from '~/components/TaskInspect.vue'
|
||||||
import type { task_item, exported_task, error_response } from '~/types/tasks'
|
import type { task_item, exported_task, error_response } from '~/types/tasks'
|
||||||
|
import { sleep } from '~/utils'
|
||||||
|
|
||||||
const box = useConfirm()
|
const box = useConfirm()
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
|
|
@ -634,7 +635,7 @@ const deleteItem = async (item: task_item) => {
|
||||||
toast.success('Task deleted.')
|
toast.success('Task deleted.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateItem = async ({ reference, task }: { reference?: string | null | undefined, task: task_item }) => {
|
const updateItem = async ({ reference, task, archive_all }: { reference?: string | null | undefined, task: task_item, archive_all?: boolean }) => {
|
||||||
if (reference) {
|
if (reference) {
|
||||||
// -- find the task index.
|
// -- find the task index.
|
||||||
const index = tasks.value.findIndex((t) => t?.id === reference)
|
const index = tasks.value.findIndex((t) => t?.id === reference)
|
||||||
|
|
@ -651,6 +652,16 @@ const updateItem = async ({ reference, task }: { reference?: string | null | und
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success('Task updated.')
|
toast.success('Task updated.')
|
||||||
|
|
||||||
|
if (!reference && true === archive_all) {
|
||||||
|
const newTask = tasks.value[tasks.value.length - 1]
|
||||||
|
if (newTask) {
|
||||||
|
await sleep(1)
|
||||||
|
await nextTick()
|
||||||
|
await archiveAll(newTask, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resetForm(true)
|
resetForm(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -817,14 +828,17 @@ const get_tags = (name: string): Array<string> => {
|
||||||
|
|
||||||
const remove_tags = (name: string): string => name.replace(/\[(.*?)\]/g, '').trim();
|
const remove_tags = (name: string): string => name.replace(/\[(.*?)\]/g, '').trim();
|
||||||
|
|
||||||
const archiveAll = async (item: task_item) => {
|
const archiveAll = async (item: task_item, by_pass: boolean = false) => {
|
||||||
try {
|
try {
|
||||||
const { status } = await cDialog({
|
|
||||||
message: `Mark all '${item.name}' items as downloaded in download archive?`
|
|
||||||
})
|
|
||||||
|
|
||||||
if (true !== status) {
|
if (true !== by_pass) {
|
||||||
return;
|
const { status } = await cDialog({
|
||||||
|
message: `Mark all '${item.name}' items as downloaded in download archive?`
|
||||||
|
})
|
||||||
|
|
||||||
|
if (true !== status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item.in_progress = true
|
item.in_progress = true
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue