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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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