157 lines
4.9 KiB
Svelte
157 lines
4.9 KiB
Svelte
<script lang="ts">
|
|
import { goto } from "$app/navigation";
|
|
import { page } from "$app/stores";
|
|
import type { Notification } from "$lib/models/notification";
|
|
import {
|
|
notifications_index,
|
|
notifications_mark_as_seen,
|
|
} from "$lib/stores/notification_store";
|
|
import { currentUser } from "$lib/stores/user_store";
|
|
import { onMount } from "svelte";
|
|
import { fly } from "svelte/transition";
|
|
import SkeletonNotificationCard from "../base/skeleton_notification_card.svelte";
|
|
import NotificationCard from "./notification_card.svelte";
|
|
import { _ } from "svelte-i18n";
|
|
import emptyStateNotificationDark from "$lib/assets/svgs/empty_states/empty_state_notification_dark.svg";
|
|
import emptyStateNotificationLight from "$lib/assets/svgs/empty_states/empty_state_notification_light.svg";
|
|
import { theme } from "$lib/stores/theme_store";
|
|
|
|
let notifications: Notification[] = [];
|
|
|
|
const pagination = {
|
|
page: $page.data.notifications.page,
|
|
totalPages: $page.data.notifications.totalPages,
|
|
};
|
|
|
|
let loadingNextPage: boolean = false;
|
|
let isOpen = false;
|
|
|
|
$: unreadCount = notifications.reduce(
|
|
(value, n) => (value += n.seen ? 0 : 1),
|
|
0,
|
|
);
|
|
|
|
onMount(() => {
|
|
if (!notifications.length && $page.data.notifications?.items?.length) {
|
|
notifications = $page.data.notifications.items;
|
|
}
|
|
});
|
|
|
|
async function toggleMenu(e: MouseEvent) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
isOpen = !isOpen;
|
|
pagination.page = 0;
|
|
await loadNextPage();
|
|
}
|
|
|
|
function handleWindowClick(e: MouseEvent) {
|
|
if (
|
|
(e.target as HTMLElement).parentElement?.classList.contains(
|
|
"dropdown-toggle",
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
isOpen = false;
|
|
}
|
|
|
|
async function onListScroll(e: Event) {
|
|
const container = e.target as HTMLDivElement;
|
|
const scrollTop = container.scrollTop;
|
|
const scrollHeight = container.scrollHeight;
|
|
const clientHeight = container.clientHeight;
|
|
|
|
if (
|
|
scrollTop + clientHeight >= scrollHeight * 0.8 &&
|
|
pagination.page !== pagination.totalPages &&
|
|
!loadingNextPage
|
|
) {
|
|
await loadNextPage();
|
|
}
|
|
}
|
|
|
|
async function loadNextPage() {
|
|
loadingNextPage = true;
|
|
|
|
if (!$currentUser) {
|
|
return;
|
|
}
|
|
pagination.page += 1;
|
|
const result = await notifications_index(
|
|
{ recipient: $currentUser.id },
|
|
pagination.page,
|
|
);
|
|
|
|
notifications = result.items;
|
|
loadingNextPage = false;
|
|
}
|
|
|
|
async function handleNotificationClick(e: CustomEvent) {
|
|
await notifications_mark_as_seen(e.detail.notification);
|
|
e.detail.notification.seen = true;
|
|
notifications = notifications;
|
|
|
|
if (e.detail.link) {
|
|
goto(e.detail.link);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<svelte:window on:mouseup={handleWindowClick} />
|
|
|
|
<div class="dropdown relative">
|
|
{#if unreadCount > 0}
|
|
<div
|
|
class="absolute pointer-events-none -top-1 left-4 text-sm rounded-full bg-content text-content-inverse px-1 text-center"
|
|
>
|
|
{unreadCount}{unreadCount >= 10 ? "+" : ""}
|
|
</div>
|
|
{/if}
|
|
<div class="dropdown-toggle">
|
|
<button on:click={toggleMenu} class="btn-icon">
|
|
<i class="fa fa-bell"></i>
|
|
</button>
|
|
</div>
|
|
|
|
{#if isOpen}
|
|
<ul
|
|
class="menu absolute bg-menu-background border border-input-border rounded-l-xl rounded-b-xl shadow-md right-0 overflow-scroll mt-4 max-h-96 w-72"
|
|
class:none={isOpen}
|
|
on:scroll={onListScroll}
|
|
style="z-index: 1001"
|
|
in:fly={{ y: -10, duration: 150 }}
|
|
out:fly={{ y: -10, duration: 150 }}
|
|
>
|
|
{#if loadingNextPage}
|
|
{#each { length: 5 } as _, index}
|
|
<SkeletonNotificationCard></SkeletonNotificationCard>
|
|
{/each}
|
|
{:else if notifications.length}
|
|
{#each notifications as notification}
|
|
<NotificationCard
|
|
on:click={handleNotificationClick}
|
|
{notification}
|
|
></NotificationCard>
|
|
{/each}
|
|
{:else}
|
|
<div class="text-center p-8">
|
|
<img class="mx-auto"
|
|
src={$theme === "light"
|
|
? emptyStateNotificationLight
|
|
: emptyStateNotificationDark}
|
|
alt="notification empty state"
|
|
/>
|
|
<p class="text-gray-500 text-sm text-center mt-2">
|
|
{$_("no-notifications")}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
</ul>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
</style>
|