diff --git a/web/components/Notifications/Indicator.vue b/web/components/Notifications/Indicator.vue index 9012b8917..da64bb268 100644 --- a/web/components/Notifications/Indicator.vue +++ b/web/components/Notifications/Indicator.vue @@ -3,7 +3,7 @@ import { BellIcon, ExclamationTriangleIcon, ShieldExclamationIcon } from '@heroi import { cn } from '~/components/shadcn/utils'; import { Importance, type OverviewQuery } from '~/composables/gql/graphql'; -const props = defineProps<{ overview?: OverviewQuery['notifications']['overview'], seen?: boolean }>(); +const props = defineProps<{ overview?: OverviewQuery['notifications']['overview']; seen?: boolean }>(); const indicatorLevel = computed(() => { if (!props.overview?.unread) { @@ -51,4 +51,4 @@ const icon = computed<{ component: Component; color: string } | null>(() => { :class="cn('absolute -top-1 -right-1 size-4 rounded-full', icon.color)" /> - \ No newline at end of file + diff --git a/web/components/Notifications/List.vue b/web/components/Notifications/List.vue index 153a3a9f2..b63df6d00 100644 --- a/web/components/Notifications/List.vue +++ b/web/components/Notifications/List.vue @@ -2,6 +2,7 @@ import { CheckIcon } from '@heroicons/vue/24/solid'; import { useQuery } from '@vue/apollo-composable'; import { vInfiniteScroll } from '@vueuse/components'; +import { useHaveSeenNotifications } from '~/composables/api/use-notifications'; import { useFragment } from '~/composables/gql/fragment-masking'; import type { Importance, NotificationType } from '~/composables/gql/graphql'; import { useUnraidApiStore } from '~/store/unraidApi'; @@ -47,6 +48,21 @@ const notifications = computed(() => { return list.filter((n) => n.type === props.type); }); +// saves timestamp of latest visible notification to local storage +const { latestSeenTimestamp } = useHaveSeenNotifications(); +watch( + notifications, + () => { + const [latest] = notifications.value; + if (!latest?.timestamp) return; + if (new Date(latest.timestamp) > new Date(latestSeenTimestamp.value)) { + console.log('[notif list] setting last seen timestamp', latest.timestamp); + latestSeenTimestamp.value = latest.timestamp; + } + }, + { immediate: true } +); + async function onLoadMore() { console.log('[getNotifications] onLoadMore'); const incoming = await fetchMore({ diff --git a/web/components/Notifications/Sidebar.vue b/web/components/Notifications/Sidebar.vue index 0aa1443f0..4c5d713a6 100644 --- a/web/components/Notifications/Sidebar.vue +++ b/web/components/Notifications/Sidebar.vue @@ -2,6 +2,7 @@ import { Button } from '@/components/shadcn/button'; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/shadcn/sheet'; import { useMutation, useQuery, useSubscription } from '@vue/apollo-composable'; +import { useTrackLatestSeenNotification } from '~/composables/api/use-notifications'; import { useFragment } from '~/composables/gql'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports -- false positive :( import { Importance, NotificationType } from '~/composables/gql/graphql'; @@ -37,12 +38,17 @@ const confirmAndDeleteArchives = async () => { const { result } = useQuery(notificationsOverview, null, { pollInterval: 2_000, // 2 seconds }); - +const { latestNotificationTimestamp, haveSeenNotifications } = useTrackLatestSeenNotification(); const { onResult: onNotificationAdded } = useSubscription(notificationAddedSubscription); + onNotificationAdded(({ data }) => { if (!data) return; const notif = useFragment(NOTIFICATION_FRAGMENT, data.notificationAdded); if (notif.type !== NotificationType.Unread) return; + + if (notif.timestamp) { + latestNotificationTimestamp.value = notif.timestamp; + } // probably smart to leave this log outside the if-block for the initial release console.log('incoming notification', notif); if (!globalThis.toast) { @@ -78,32 +84,13 @@ const readArchivedCount = computed(() => { const { archive, unread } = overview.value; return Math.max(0, archive.total - unread.total); }); - -/** whether user has viewed their notifications */ -const hasSeenNotifications = ref(false); - -// renews unseen state when new notifications arrive -watch( - () => overview.value?.unread, - (newVal, oldVal) => { - if (!newVal || !oldVal) return; - if (newVal.total > oldVal.total) { - hasSeenNotifications.value = false; - } - } -); - -const prepareToViewNotifications = () => { - determineTeleportTarget(); - hasSeenNotifications.value = true; -};