fix: compile error + notif binding issue

This commit is contained in:
IMB11 2025-07-30 15:59:49 +01:00
parent a11c58274c
commit 1602aa9556
7 changed files with 109 additions and 66 deletions

View File

@ -15,7 +15,7 @@
maxlength="64"
:placeholder="`Enter organization name...`"
autocomplete="off"
@input="updateSlug()"
@input="updateSlug"
/>
</div>
<div class="flex flex-col gap-2">
@ -33,7 +33,7 @@
type="text"
maxlength="64"
autocomplete="off"
@input="manualSlug = true"
@input="setManualSlug"
/>
</div>
</div>
@ -61,7 +61,7 @@
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="modal.hide()">
<button @click="hide">
<XIcon aria-hidden="true" />
Cancel
</button>
@ -70,21 +70,22 @@
</div>
</NewModal>
</template>
<script setup>
<script setup lang="ts">
import { PlusIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { ButtonStyled, NewModal, injectNotificationManager } from "@modrinth/ui";
import { ref } from "vue";
const router = useNativeRouter();
const { addNotification } = injectNotificationManager();
const name = ref("");
const slug = ref("");
const description = ref("");
const manualSlug = ref(false);
const name = ref<string>("");
const slug = ref<string>("");
const description = ref<string>("");
const manualSlug = ref<boolean>(false);
const modal = ref<InstanceType<typeof NewModal>>();
const modal = ref();
async function createOrganization() {
async function createOrganization(): Promise<void> {
startLoading();
try {
const value = {
@ -93,16 +94,16 @@ async function createOrganization() {
slug: slug.value.trim().replace(/ +/g, ""),
};
const result = await useBaseFetch("organization", {
const result: any = await useBaseFetch("organization", {
method: "POST",
body: JSON.stringify(value),
apiVersion: 3,
});
modal.value.hide();
modal.value?.hide();
await router.push(`/organization/${result.slug}`);
} catch (err) {
} catch (err: any) {
console.error(err);
addNotification({
title: "An error occurred",
@ -112,13 +113,18 @@ async function createOrganization() {
}
stopLoading();
}
function show(event) {
function show(event?: MouseEvent): void {
name.value = "";
description.value = "";
modal.value.show(event);
modal.value?.show(event);
}
function updateSlug() {
function hide(): void {
modal.value?.hide();
}
function updateSlug(): void {
if (!manualSlug.value) {
slug.value = name.value
.trim()
@ -129,6 +135,10 @@ function updateSlug() {
}
}
function setManualSlug(): void {
manualSlug.value = true;
}
defineExpose({
show,
});

View File

@ -1,5 +1,3 @@
import { injectNotificationManager } from "@modrinth/ui";
export const useUser = async (force = false) => {
const user = useState("user", () => {});
@ -137,7 +135,8 @@ export const userFollowProject = async (project) => {
}
};
export const resendVerifyEmail = async () => {
const { addNotification } = injectNotificationManager();
// const { injectNotificationManager } = await import("@modrinth/ui");
// const { addNotification } = injectNotificationManager();
startLoading();
try {

View File

@ -1,19 +1,14 @@
import { injectNotificationManager } from "@modrinth/ui";
import type { Organization, Project, Report, User, Version } from "@modrinth/utils";
// TODO: There needs to be a standardized way to get these types, eg; @modrinth/types generated from api schema. Later problem.
type Project = { id: string };
type Version = { id: string; project_id: string };
type Report = { id: string; item_type: "project" | "user" | "version"; item_id: string };
type Thread = { id: string };
type User = { id: string };
type Organization = { id: string };
export type NotificationAction = {
export type PlatformNotificationAction = {
title: string;
action_route: [string, string];
};
export type NotificationBody = {
export type PlatformNotificationBody = {
project_id?: string;
version_id?: string;
report_id?: string;
@ -22,7 +17,7 @@ export type NotificationBody = {
organization_id?: string;
};
export type Notification = {
export type PlatformNotification = {
id: string;
user_id: string;
type: "project_update" | "team_invite" | "status_change" | "moderator_message";
@ -31,10 +26,10 @@ export type Notification = {
link: string;
read: boolean;
created: string;
actions: NotificationAction[];
body?: NotificationBody;
actions: PlatformNotificationAction[];
body?: PlatformNotificationBody;
extra_data?: Record<string, unknown>;
grouped_notifs?: Notification[];
grouped_notifs?: PlatformNotification[];
};
async function getBulk<T extends { id: string }>(
@ -55,8 +50,8 @@ async function getBulk<T extends { id: string }>(
}
export async function fetchExtraNotificationData(
notifications: Notification[],
): Promise<Notification[]> {
notifications: PlatformNotification[],
): Promise<PlatformNotification[]> {
const bulk = {
projects: [] as string[],
reports: [] as string[],
@ -133,8 +128,8 @@ export async function fetchExtraNotificationData(
return notifications;
}
export function groupNotifications(notifications: Notification[]): Notification[] {
const grouped: Notification[] = [];
export function groupNotifications(notifications: PlatformNotification[]): PlatformNotification[] {
const grouped: PlatformNotification[] = [];
for (let i = 0; i < notifications.length; i++) {
const current = notifications[i];
const next = notifications[i + 1];
@ -154,18 +149,18 @@ export function groupNotifications(notifications: Notification[]): Notification[
return grouped;
}
function isSimilar(a: Notification, b: Notification | undefined): boolean {
function isSimilar(a: PlatformNotification, b: PlatformNotification | undefined): boolean {
return !!a?.body?.project_id && a.body!.project_id === b?.body?.project_id;
}
export async function markAsRead(
ids: string[],
): Promise<(notifications: Notification[]) => Notification[]> {
): Promise<(notifications: PlatformNotification[]) => PlatformNotification[]> {
try {
await useBaseFetch(`notifications?ids=${JSON.stringify([...new Set(ids)])}`, {
method: "PATCH",
});
return (notifications: Notification[]) => {
return (notifications: PlatformNotification[]) => {
const newNotifs = notifications ?? [];
newNotifs.forEach((n) => {
if (ids.includes(n.id)) n.read = true;

View File

@ -1552,11 +1552,15 @@ const collapsedModerationChecklist = useLocalStorage("collapsed-moderation-check
watch(
showModerationChecklist,
(newValue) => {
notifications.setNotificationsPanelRightwards(newValue);
notifications.setNotificationLocation(newValue ? "left" : "right");
},
{ immediate: true },
);
onUnmounted(() => {
notifications.setNotificationLocation("right");
});
if (import.meta.client && history && history.state && history.state.showChecklist) {
showModerationChecklist.value = true;
}

View File

@ -1,28 +1,32 @@
import { type WebNotification, AbstractWebNotificationManager } from "@modrinth/ui";
import { useState } from "#app";
import {
type WebNotification,
type WebNotificationLocation,
AbstractWebNotificationManager,
} from "@modrinth/ui";
export class FrontendNotificationManager extends AbstractWebNotificationManager {
private readonly state: Ref<WebNotification[]>;
private readonly isRightwards: Ref<boolean>;
private readonly locationState: Ref<WebNotificationLocation>;
public constructor() {
super();
this.state = useState<WebNotification[]>("notifications", () => []);
this.isRightwards = useState<boolean>("notifications.isRightwards", () => true);
this.locationState = useState<WebNotificationLocation>("notifications.location", () => "right");
}
public getNotificationLocation(): WebNotificationLocation {
return this.locationState.value;
}
public setNotificationLocation(location: WebNotificationLocation): void {
this.locationState.value = location;
}
public getNotifications(): WebNotification[] {
return this.state.value;
}
public isNotificationsPanelRightwards(): boolean {
return this.isRightwards.value;
}
public setNotificationsPanelRightwards(isRightwards: boolean): void {
this.isRightwards.value = isRightwards;
}
protected addNotificationToStorage(notification: WebNotification): void {
this.state.value.push(notification);
}

View File

@ -1,7 +1,11 @@
<template>
<div
class="vue-notification-group experimental-styles-within"
:class="{ 'intercom-present': isIntercomPresent }"
:class="{
'intercom-present': isIntercomPresent,
'location-left': notificationLocation === 'left',
'location-right': notificationLocation === 'right',
}"
>
<transition-group name="notifs">
<div
@ -72,20 +76,21 @@
</template>
<script setup lang="ts">
import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
import { ButtonStyled, injectNotificationManager, type WebNotification } from '@modrinth/ui'
import {
XCircleIcon,
CheckCircleIcon,
CheckIcon,
CopyIcon,
InfoIcon,
IssuesIcon,
XCircleIcon,
XIcon,
CopyIcon,
} from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager, type WebNotification } from '@modrinth/ui'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
const notificationManager = injectNotificationManager()
const notifications = computed<WebNotification[]>(() => notificationManager.getNotifications())
const notificationLocation = computed(() => notificationManager.getNotificationLocation())
const isIntercomPresent = ref<boolean>(false)
const copied = ref<Record<string, boolean>>({})
@ -134,15 +139,31 @@ onMounted(() => {
<style lang="scss" scoped>
.vue-notification-group {
position: fixed;
right: 1.5rem;
bottom: 1.5rem;
z-index: 200;
width: 450px;
&.location-right {
right: 1.5rem;
}
&.location-left {
left: 1.5rem;
}
@media screen and (max-width: 500px) {
width: calc(100% - 0.75rem * 2);
right: 0.75rem;
bottom: 0.75rem;
&.location-right {
right: 0.75rem;
left: auto;
}
&.location-left {
left: 0.75rem;
right: auto;
}
}
&.intercom-present {
@ -184,6 +205,12 @@ onMounted(() => {
}
.notifs-leave-to {
transform: translateX(100%) scale(0.8);
.location-right & {
transform: translateX(100%) scale(0.8);
}
.location-left & {
transform: translateX(-100%) scale(0.8);
}
}
</style>

View File

@ -10,17 +10,21 @@ export interface WebNotification {
timer?: NodeJS.Timeout
}
export type WebNotificationLocation = 'left' | 'right'
export abstract class AbstractWebNotificationManager {
protected readonly AUTO_DISMISS_DELAY_MS = 30 * 1000
abstract getNotifications(): WebNotification[]
abstract getNotificationLocation(): WebNotificationLocation
abstract setNotificationLocation(location: WebNotificationLocation): void
protected abstract addNotificationToStorage(notification: WebNotification): void
protected abstract removeNotificationFromStorage(id: string | number): void
protected abstract removeNotificationFromStorageByIndex(index: number): void
protected abstract clearAllNotificationsFromStorage(): void
addNotification(notification: Partial<WebNotification>): void {
addNotification = (notification: Partial<WebNotification>): void => {
const existingNotif = this.findExistingNotification(notification)
if (existingNotif) {
@ -34,7 +38,7 @@ export abstract class AbstractWebNotificationManager {
this.addNotificationToStorage(newNotification)
}
removeNotification(id: string | number): void {
removeNotification = (id: string | number): void => {
const notifications = this.getNotifications()
const notification = notifications.find((n) => n.id === id)
@ -44,7 +48,7 @@ export abstract class AbstractWebNotificationManager {
}
}
removeNotificationByIndex(index: number): void {
removeNotificationByIndex = (index: number): void => {
const notifications = this.getNotifications()
if (index >= 0 && index < notifications.length) {
@ -54,7 +58,7 @@ export abstract class AbstractWebNotificationManager {
}
}
clearAllNotifications(): void {
clearAllNotifications = (): void => {
const notifications = this.getNotifications()
notifications.forEach((notification) => {
this.clearNotificationTimer(notification)
@ -62,7 +66,7 @@ export abstract class AbstractWebNotificationManager {
this.clearAllNotificationsFromStorage()
}
setNotificationTimer(notification: WebNotification): void {
setNotificationTimer = (notification: WebNotification): void => {
if (!notification) return
this.clearNotificationTimer(notification)
@ -72,7 +76,7 @@ export abstract class AbstractWebNotificationManager {
}, this.AUTO_DISMISS_DELAY_MS)
}
stopNotificationTimer(notification: WebNotification): void {
stopNotificationTimer = (notification: WebNotification): void => {
this.clearNotificationTimer(notification)
}