Feat: allow selecting browser as target for toasts
This commit is contained in:
parent
d835c027d5
commit
8e6f5fc293
3 changed files with 127 additions and 24 deletions
|
|
@ -29,8 +29,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {defineEmits, defineProps} from 'vue'
|
|
||||||
|
|
||||||
withDefaults(defineProps<{
|
withDefaults(defineProps<{
|
||||||
/** Title text for the notification */
|
/** Title text for the notification */
|
||||||
title?: string | null
|
title?: string | null
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useStorage } from '@vueuse/core'
|
||||||
import { POSITION, useToast } from "vue-toastification"
|
import { POSITION, useToast } from "vue-toastification"
|
||||||
|
|
||||||
export type notificationType = 'info' | 'success' | 'warning' | 'error'
|
export type notificationType = 'info' | 'success' | 'warning' | 'error'
|
||||||
|
export type notificationTarget = 'toast' | 'browser'
|
||||||
|
|
||||||
export interface notification {
|
export interface notification {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -25,9 +26,77 @@ export interface notificationOptions {
|
||||||
const allowToast = useStorage<boolean>('allow_toasts', true)
|
const allowToast = useStorage<boolean>('allow_toasts', true)
|
||||||
const toastPosition = useStorage<POSITION>('toast_position', POSITION.TOP_RIGHT)
|
const toastPosition = useStorage<POSITION>('toast_position', POSITION.TOP_RIGHT)
|
||||||
const toastDismissOnClick = useStorage<boolean>('toast_dismiss_on_click', true)
|
const toastDismissOnClick = useStorage<boolean>('toast_dismiss_on_click', true)
|
||||||
|
const toastTarget = useStorage<notificationTarget>('toast_target', 'toast')
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
function notify(type: notificationType, message: string, opts?: notificationOptions): void {
|
const requestBrowserPermission = async (): Promise<NotificationPermission> => {
|
||||||
|
if (!('Notification' in window)) {
|
||||||
|
return 'denied'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Notification.permission === 'granted') {
|
||||||
|
return 'granted'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Notification.permission !== 'denied') {
|
||||||
|
return await Notification.requestPermission()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Notification.permission
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendMessage = (type: notificationType, id: string, message: string, opts?: notificationOptions): void => {
|
||||||
|
const notificationStore = useNotificationStore()
|
||||||
|
|
||||||
|
const useToastNotification = !window.isSecureContext || 'toast' === toastTarget.value ||
|
||||||
|
!('Notification' in window) || 'granted' !== Notification.permission;
|
||||||
|
|
||||||
|
console.log('useToastNotification', useToastNotification,{
|
||||||
|
windowIsSecureContext: window.isSecureContext,
|
||||||
|
notificationTarget: toastTarget.value,
|
||||||
|
notificationInWindow: 'Notification' in window,
|
||||||
|
notificationPermission: Notification.permission
|
||||||
|
});
|
||||||
|
|
||||||
|
if (useToastNotification) {
|
||||||
|
switch (type) {
|
||||||
|
case 'info':
|
||||||
|
toast.info(message, opts)
|
||||||
|
break;
|
||||||
|
case 'success':
|
||||||
|
toast.success(message, opts)
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
toast.warning(message, opts)
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
toast.error(message, opts)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
toast.error(`Unknown notification type: ${type}. ${message}`, opts)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const notification = new Notification('YTPTube', {
|
||||||
|
body: message,
|
||||||
|
icon: '/favicon.ico',
|
||||||
|
badge: '/favicon.ico',
|
||||||
|
tag: `ytptube-${type}`,
|
||||||
|
silent: false
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout(() => notification.close(), 5000)
|
||||||
|
|
||||||
|
notification.onclick = () => {
|
||||||
|
notificationStore.markRead(id)
|
||||||
|
window.focus()
|
||||||
|
notification.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notify = (type: notificationType, message: string, opts?: notificationOptions): void => {
|
||||||
const notificationStore = useNotificationStore()
|
const notificationStore = useNotificationStore()
|
||||||
|
|
||||||
if (!opts) {
|
if (!opts) {
|
||||||
|
|
@ -63,23 +132,7 @@ function notify(type: notificationType, message: string, opts?: notificationOpti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
sendMessage(type, id, message, opts)
|
||||||
case 'info':
|
|
||||||
toast.info(message, opts)
|
|
||||||
break;
|
|
||||||
case 'success':
|
|
||||||
toast.success(message, opts)
|
|
||||||
break;
|
|
||||||
case 'warning':
|
|
||||||
toast.warning(message, opts)
|
|
||||||
break;
|
|
||||||
case 'error':
|
|
||||||
toast.error(message, opts)
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
toast.error(`Unknown notification type: ${type}. ${message}`, opts)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotification = () => {
|
export const useNotification = () => {
|
||||||
|
|
@ -88,6 +141,7 @@ export const useNotification = () => {
|
||||||
success: (message: string, opts?: notificationOptions) => notify('success', message, opts),
|
success: (message: string, opts?: notificationOptions) => notify('success', message, opts),
|
||||||
warning: (message: string, opts?: notificationOptions) => notify('warning', message, opts),
|
warning: (message: string, opts?: notificationOptions) => notify('warning', message, opts),
|
||||||
error: (message: string, opts?: notificationOptions) => notify('error', message, opts),
|
error: (message: string, opts?: notificationOptions) => notify('error', message, opts),
|
||||||
notify
|
notify,
|
||||||
|
requestBrowserPermission,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@
|
||||||
<span class="field title is-4">
|
<span class="field title is-4">
|
||||||
<span class="icon-text">
|
<span class="icon-text">
|
||||||
<span class="icon"><i class="fas fa-bell" /></span>
|
<span class="icon"><i class="fas fa-bell" /></span>
|
||||||
<p class="card-header-title">On-Screen Notifications</p>
|
<p class="card-header-title">Notifications</p>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="field is-horizontal" />
|
<span class="field is-horizontal" />
|
||||||
|
|
@ -222,6 +222,33 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field is-horizontal" v-if="allow_toasts">
|
<div class="field is-horizontal" v-if="allow_toasts">
|
||||||
|
<div class="field-label is-normal">
|
||||||
|
<label class="label">Notification target</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field is-narrow">
|
||||||
|
<div class="control">
|
||||||
|
<div class="select is-fullwidth">
|
||||||
|
<select v-model="toast_target" @change="onNotificationTargetChange">
|
||||||
|
<option value="toast">Toast</option>
|
||||||
|
<option value="browser" :disabled="!isSecureContext">Browser</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="help">
|
||||||
|
<span class="icon"><i class="fa-solid fa-info-circle" /></span>
|
||||||
|
<template v-if="!isSecureContext">
|
||||||
|
Browser notifications require HTTPS connection.
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
Choose where to display notifications. Browser requires permission.
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-horizontal" v-if="allow_toasts && toast_target === 'toast'">
|
||||||
<div class="field-label is-normal">
|
<div class="field-label is-normal">
|
||||||
<label class="label">Notifications position</label>
|
<label class="label">Notifications position</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -243,7 +270,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field is-horizontal" v-if="allow_toasts">
|
<div class="field is-horizontal" v-if="allow_toasts && toast_target === 'toast'">
|
||||||
<div class="field-label is-normal">
|
<div class="field-label is-normal">
|
||||||
<label class="label">Dismiss notification on click</label>
|
<label class="label">Dismiss notification on click</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -266,13 +293,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import 'assets/css/bulma-switch.css'
|
import 'assets/css/bulma-switch.css'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
import { POSITION } from 'vue-toastification'
|
import { POSITION } from 'vue-toastification'
|
||||||
import { useConfigStore } from '~/stores/ConfigStore'
|
import { useConfigStore } from '~/stores/ConfigStore'
|
||||||
|
import { useNotification } from '~/composables/useNotification'
|
||||||
|
import type { notificationTarget } from '~/composables/useNotification'
|
||||||
|
|
||||||
defineProps<{ isLoading: boolean }>()
|
defineProps<{ isLoading: boolean }>()
|
||||||
defineEmits<{ (e: 'reload_bg'): void }>()
|
defineEmits<{ (e: 'reload_bg'): void }>()
|
||||||
|
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
|
const notification = useNotification()
|
||||||
|
|
||||||
const bg_enable = useStorage<boolean>('random_bg', true)
|
const bg_enable = useStorage<boolean>('random_bg', true)
|
||||||
const bg_opacity = useStorage<number>('random_bg_opacity', 0.95)
|
const bg_opacity = useStorage<number>('random_bg_opacity', 0.95)
|
||||||
|
|
@ -280,8 +311,28 @@ const selectedTheme = useStorage<'auto' | 'light' | 'dark'>('theme', 'auto')
|
||||||
const allow_toasts = useStorage<boolean>('allow_toasts', true)
|
const allow_toasts = useStorage<boolean>('allow_toasts', true)
|
||||||
const toast_position = useStorage<POSITION>('toast_position', POSITION.TOP_RIGHT)
|
const toast_position = useStorage<POSITION>('toast_position', POSITION.TOP_RIGHT)
|
||||||
const toast_dismiss_on_click = useStorage<boolean>('toast_dismiss_on_click', true)
|
const toast_dismiss_on_click = useStorage<boolean>('toast_dismiss_on_click', true)
|
||||||
|
const toast_target = useStorage<notificationTarget>('toast_target', 'toast')
|
||||||
const show_thumbnail = useStorage<boolean>('show_thumbnail', true)
|
const show_thumbnail = useStorage<boolean>('show_thumbnail', true)
|
||||||
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
||||||
const separator = useStorage<string>('url_separator', separators[0]?.value ?? ',')
|
const separator = useStorage<string>('url_separator', separators[0]?.value ?? ',')
|
||||||
const simpleMode = useStorage<boolean>('simple_mode', config.app.simple_mode || false)
|
const simpleMode = useStorage<boolean>('simple_mode', config.app.simple_mode || false)
|
||||||
|
const isSecureContext = ref<boolean>(false)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
isSecureContext.value = window.isSecureContext
|
||||||
|
await nextTick()
|
||||||
|
if ('browser' === toast_target.value && !isSecureContext.value) {
|
||||||
|
toast_target.value = 'toast'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onNotificationTargetChange = async (): Promise<void> => {
|
||||||
|
if ('browser' === toast_target.value) {
|
||||||
|
const permission = await notification.requestBrowserPermission()
|
||||||
|
if ('granted' !== permission) {
|
||||||
|
toast_target.value = 'toast'
|
||||||
|
notification.warning('Browser notification permission denied. Reverting to toast notifications.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue