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>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {defineEmits, defineProps} from 'vue'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
/** Title text for the notification */
|
||||
title?: string | null
|
||||
|
|
@ -64,4 +62,4 @@ defineEmits<{
|
|||
/** Emitted when the close button is clicked */
|
||||
(e: 'close'): void
|
||||
}>()
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useStorage } from '@vueuse/core'
|
|||
import { POSITION, useToast } from "vue-toastification"
|
||||
|
||||
export type notificationType = 'info' | 'success' | 'warning' | 'error'
|
||||
export type notificationTarget = 'toast' | 'browser'
|
||||
|
||||
export interface notification {
|
||||
id: string;
|
||||
|
|
@ -25,9 +26,77 @@ export interface notificationOptions {
|
|||
const allowToast = useStorage<boolean>('allow_toasts', true)
|
||||
const toastPosition = useStorage<POSITION>('toast_position', POSITION.TOP_RIGHT)
|
||||
const toastDismissOnClick = useStorage<boolean>('toast_dismiss_on_click', true)
|
||||
const toastTarget = useStorage<notificationTarget>('toast_target', 'toast')
|
||||
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()
|
||||
|
||||
if (!opts) {
|
||||
|
|
@ -63,23 +132,7 @@ function notify(type: notificationType, message: string, opts?: notificationOpti
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
sendMessage(type, id, message, opts)
|
||||
}
|
||||
|
||||
export const useNotification = () => {
|
||||
|
|
@ -88,6 +141,7 @@ export const useNotification = () => {
|
|||
success: (message: string, opts?: notificationOptions) => notify('success', message, opts),
|
||||
warning: (message: string, opts?: notificationOptions) => notify('warning', 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="icon-text">
|
||||
<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 class="field is-horizontal" />
|
||||
|
|
@ -222,6 +222,33 @@
|
|||
</div>
|
||||
|
||||
<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">
|
||||
<label class="label">Notifications position</label>
|
||||
</div>
|
||||
|
|
@ -243,7 +270,7 @@
|
|||
</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">
|
||||
<label class="label">Dismiss notification on click</label>
|
||||
</div>
|
||||
|
|
@ -266,13 +293,17 @@
|
|||
<script setup lang="ts">
|
||||
import 'assets/css/bulma-switch.css'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { POSITION } from 'vue-toastification'
|
||||
import { useConfigStore } from '~/stores/ConfigStore'
|
||||
import { useNotification } from '~/composables/useNotification'
|
||||
import type { notificationTarget } from '~/composables/useNotification'
|
||||
|
||||
defineProps<{ isLoading: boolean }>()
|
||||
defineEmits<{ (e: 'reload_bg'): void }>()
|
||||
|
||||
const config = useConfigStore()
|
||||
const notification = useNotification()
|
||||
|
||||
const bg_enable = useStorage<boolean>('random_bg', true)
|
||||
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 toast_position = useStorage<POSITION>('toast_position', POSITION.TOP_RIGHT)
|
||||
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 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)
|
||||
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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue