More work on notification dropdown

This commit is contained in:
Nassim Jahnke 2022-05-29 14:43:51 +02:00
parent 2bd3ba466b
commit 15e2e1813d
No known key found for this signature in database
GPG Key ID: 6BE3B555EBC5982B
6 changed files with 46 additions and 25 deletions

View File

@ -22,12 +22,14 @@ import IconMdiBellOutline from "~icons/mdi/bell-outline";
import IconMdiBellBadge from "~icons/mdi/bell-badge";
import IconMdiAlertOutline from "~icons/mdi/alert-outline";
import IconMdiInformationOutline from "~icons/mdi/information-outline";
import IconMdiMessageOutline from "~icons/mdi/message-outline";
import IconMdiCheck from "~icons/mdi/check";
import { useAuthStore } from "~/store/auth";
import { useAuth } from "~/composables/useAuth";
import { useBackendDataStore } from "~/store/backendData";
import { authLog } from "~/composables/useLog";
import { lastUpdated } from "~/composables/useTime";
import { hasPerms } from "~/composables/usePerm";
import { NamedPermission } from "~/types/enums";
import UserAvatar from "~/components/UserAvatar.vue";
@ -43,7 +45,6 @@ import { useInternalApi } from "~/composables/useApi";
const settings = useSettingsStore();
const { t } = useI18n();
const backendData = useBackendDataStore();
const ctx = useContext();
const i18n = useI18n();
const authStore = useAuthStore();
@ -96,15 +97,19 @@ const auth = useAuth;
const authHost = import.meta.env.HANGAR_AUTH_HOST;
authLog("render with user " + authStore.user?.name);
async function markNotificationRead() {
function markNotificationsRead() {
for (const notification of notifications.value) {
if (!notification.read) {
useInternalApi(`notifications/${notification.id}`, true, "post").catch((e) => handleRequestError(e, ctx, i18n));
notification.read = true;
}
markNotificationRead(notification);
}
unreadNotifications.value = false;
}
async function markNotificationRead(notification: HangarNotification) {
if (!notification.read) {
useInternalApi(`notifications/${notification.id}`, true, "post").catch((e) => handleRequestError(e, ctx, i18n));
notification.read = true;
}
}
</script>
<template>
@ -195,7 +200,6 @@ async function markNotificationRead() {
<icon-mdi-white-balance-sunny v-else class="text-[1.2em]"></icon-mdi-white-balance-sunny>
</button>
<div v-if="authStore.user">
<!-- todo: make prettier (show all unread and not just recent 20 unread/read, actually use action field) -->
<Menu>
<MenuButton>
<div class="flex items-center gap-2 rounded-md p-2" hover="text-primary-400 bg-primary-0">
@ -205,25 +209,34 @@ async function markNotificationRead() {
</MenuButton>
<MenuItems class="absolute flex flex-col mt-1 z-10 rounded border-t-2 border-primary-400 background-default drop-shadow-xl overflow-auto shadow-md">
<div v-if="notifications.length === 0">
<span class="flex shadow-0 p-2 mt-1 ml-3 mr-2">{{ i18n.t("notifications.empty.recent") }}</span>
<span class="flex shadow-0 p-2 mt-2 ml-3 mr-2">{{ i18n.t("notifications.empty.recent") }}</span>
</div>
<div
v-for="notification in notifications"
:key="notification.id"
:class="'text-sm flex shadow-0 p-3 pr-4 inline-flex items-center ' + (!notification.read ? 'bg-blue-100 dark:bg-slate-700' : '')"
:class="'text-sm flex shadow-0 p-3 pt-2 pr-4 inline-flex items-center ' + (!notification.read ? 'bg-blue-100 dark:bg-slate-700' : '')"
>
<div class="text-lg mr-2">
<IconMdiInformationOutline v-if="notification.type === 'info'" class="text-lightBlue-600" />
<IconMdiCheck v-else-if="notification.type === 'success'" class="text-lime-600" />
<IconMdiAlertOutline v-else-if="notification.type === 'warning'" class="text-red-600" />
<IconMdiMessageOutline v-else-if="notification.type === 'neutral'" />
</div>
<router-link v-if="notification.action" :to="'/' + notification.action" active-class="">
{{ i18n.t(notification.message[0], notification.message.slice(1)) }}
<div class="text-xs mt-1">{{ lastUpdated(new Date(notification.createdAt)) }}</div>
</router-link>
<div v-else>
{{ i18n.t(notification.message[0], notification.message.slice(1)) }}
<div class="text-xs mt-1">{{ lastUpdated(new Date(notification.createdAt)) }}</div>
</div>
{{ i18n.t(notification.message[0], notification.message.slice(1)) }}
</div>
<div class="p-2 mb-1 ml-2 space-x-3 text-sm">
<Link to="/notifications"
><span>{{ i18n.t("notifications.viewAll") }}</span></Link
>
<span v-if="unreadNotifications" class="color-primary font-bold hover:(underline)" @click="markNotificationRead">Mark as read</span>
<span v-if="unreadNotifications" class="color-primary font-bold hover:(underline)" @click="markNotificationsRead">Mark as read</span>
</div>
</MenuItems>
</Menu>

View File

@ -4,6 +4,7 @@ declare module "hangar-internal" {
import type { RoleCategory } from "~/types/enums";
interface HangarNotification {
createdAt: string;
id: number;
action: string;
message: string[];

View File

@ -12,7 +12,7 @@ import java.util.List;
public interface HangarNotificationsDAO {
@RegisterConstructorMapper(HangarNotification.class)
@SqlQuery("SELECT n.id, n.type, n.action, n.message_args message, n.read, u.name as origin_user_name" +
@SqlQuery("SELECT n.created_at, n.id, n.type, n.action, n.message_args message, n.read, u.name as origin_user_name" +
" FROM notifications n" +
" LEFT OUTER JOIN users u ON u.id = n.origin_id" +
" WHERE n.user_id = :userId" +

View File

@ -2,10 +2,12 @@ package io.papermc.hangar.model.internal.user.notifications;
import org.jdbi.v3.core.enums.EnumByOrdinal;
import java.time.OffsetDateTime;
import java.util.List;
public class HangarNotification {
private final OffsetDateTime createdAt;
private final long id;
private final String action;
private final List<String> message;
@ -13,7 +15,8 @@ public class HangarNotification {
private final String originUserName;
private final NotificationType type;
public HangarNotification(long id, String action, List<String> message, boolean read, String originUserName, @EnumByOrdinal NotificationType type) {
public HangarNotification(OffsetDateTime createdAt, long id, String action, List<String> message, boolean read, String originUserName, @EnumByOrdinal NotificationType type) {
this.createdAt = createdAt;
this.id = id;
this.action = action;
this.message = message;
@ -22,6 +25,10 @@ public class HangarNotification {
this.type = type;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public long getId() {
return id;
}

View File

@ -44,10 +44,10 @@ public class NotificationService extends HangarComponent {
List<NotificationTable> notificationTables = new ArrayList<>();
for (UserTable projectWatcher : projectWatchers) {
notificationTables.add(new NotificationTable(
projectWatcher.getId(),
projectTable.getOwnerName() + "/" + projectTable.getSlug(),
projectTable.getId(),
new String[]{"notifications.project.newVersion", projectTable.getName(), projectVersionTable.getVersionString()}, NotificationType.NEUTRAL)
projectWatcher.getId(),
projectTable.getOwnerName() + "/" + projectTable.getSlug() + "/versions/" + projectVersionTable.getVersionString(),
projectTable.getId(),
new String[]{"notifications.project.newVersion", projectTable.getName(), projectVersionTable.getVersionString()}, NotificationType.NEUTRAL)
);
}
notificationsDAO.insert(notificationTables);
@ -58,13 +58,13 @@ public class NotificationService extends HangarComponent {
ProjectTable projectTable = projectsDAO.getById(projectVersionTable.getProjectId());
permissionService.getProjectMemberPermissions(projectVersionTable.getProjectId()).forEach((user, perm) -> {
if (perm.has(Permission.EditVersion)) {
if (partial) {
notificationTables.add(new NotificationTable(user.getId(), null, null,
new String[]{"notifications.project.reviewedPartial", projectTable.getSlug(), projectVersionTable.getVersionString()}, NotificationType.SUCCESS));
} else {
notificationTables.add(new NotificationTable(user.getId(), null, null,
new String[]{"notifications.project.reviewed", projectTable.getSlug(), projectVersionTable.getVersionString()}, NotificationType.SUCCESS));
}
notificationTables.add(new NotificationTable(
user.getId(),
projectTable.getOwnerName() + "/" + projectTable.getSlug() + "/versions/" + projectVersionTable.getVersionString(),
projectTable.getId(),
new String[]{partial ? "notifications.project.reviewedPartial" : "notifications.project.reviewed", projectTable.getSlug(), projectVersionTable.getVersionString()},
NotificationType.SUCCESS)
);
}
});
notificationsDAO.insert(notificationTables);

View File

@ -31,7 +31,7 @@ public abstract class JoinableNotificationService<RT extends ExtendedRoleTable<?
public void invited(Collection<RT> inviteeRoleTables, J joinable) {
Collection<NotificationTable> notificationTables = new HashSet<>();
for (RT rt : inviteeRoleTables) {
notificationTables.add(new NotificationTable(rt.getUserId(), null, joinable.getId(), new String[]{this.msgPrefix + "invite", rt.getRole().getTitle(), joinable.getName()}, NotificationType.INFO));
notificationTables.add(new NotificationTable(rt.getUserId(), "notifications", joinable.getId(), new String[]{this.msgPrefix + "invite", rt.getRole().getTitle(), joinable.getName()}, NotificationType.INFO));
}
notificationsDAO.insert(notificationTables);
}