feat: introduce dependency injection framework (#4091)

* feat: migrate frontend notifications to dependency injection based notificaton manager

* fix: lint

* fix: issues

* fix: compile error + notif binding issue

* refactor: move org context to new DI setup

* feat: migrate app notifications to DI + frontend styling

* fix: sidebar issues

* fix: dont use delete in computed

* fix: import and prop issue

* refactor: move handleError to main notification manager class

* fix: lint & build

* fix: merge issues

* fix: lint issues

* fix: lint issues

---------

Signed-off-by: IMB11 <hendersoncal117@gmail.com>
Signed-off-by: Cal H. <hendersoncal117@gmail.com>
This commit is contained in:
Cal H.
2025-08-13 21:48:52 +01:00
committed by GitHub
parent 9ea43a12fd
commit b81e727204
136 changed files with 2024 additions and 1719 deletions

View File

@@ -51,7 +51,9 @@
<script setup>
import { PlusIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const router = useNativeRouter();
const name = ref("");
@@ -87,7 +89,6 @@ async function create() {
await router.push(`/collection/${result.id}`);
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err,
type: "error",

View File

@@ -84,8 +84,11 @@
</template>
<script setup>
import { NewModal, ButtonStyled, DropdownSelect } from "@modrinth/ui";
import { XIcon, PlusIcon } from "@modrinth/assets";
import { PlusIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, DropdownSelect, NewModal } from "@modrinth/ui";
import { injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const router = useRouter();
const app = useNuxtApp();
@@ -180,8 +183,7 @@ async function createProject() {
},
});
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -319,30 +319,21 @@
</template>
<script setup>
import { renderString } from "@modrinth/utils";
import {
UserPlusIcon,
ScaleIcon,
BellIcon,
CheckCircleIcon,
CalendarIcon,
VersionIcon,
CheckCircleIcon,
CheckIcon,
XIcon,
ExternalIcon,
ScaleIcon,
UserPlusIcon,
VersionIcon,
XIcon,
} from "@modrinth/assets";
import { Avatar, ProjectStatusBadge, CopyCode, useRelativeTime } from "@modrinth/ui";
import ThreadSummary from "~/components/ui/thread/ThreadSummary.vue";
import { getProjectLink, getVersionLink } from "~/helpers/projects.js";
import { getUserLink } from "~/helpers/users.js";
import { acceptTeamInvite, removeSelfFromTeam } from "~/helpers/teams.js";
import { markAsRead } from "~/helpers/notifications.ts";
import DoubleIcon from "~/components/ui/DoubleIcon.vue";
import Categories from "~/components/ui/search/Categories.vue";
import { injectNotificationManager } from "@modrinth/ui";
const app = useNuxtApp();
const { addNotification } = injectNotificationManager();
const emit = defineEmits(["update:notifications"]);
const formatRelativeTime = useRelativeTime();
const props = defineProps({
@@ -407,8 +398,7 @@ async function read() {
const newNotifs = updateNotifs(props.notifications);
emit("update:notifications", newNotifs);
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "Error marking notification as read",
text: err.data ? err.data.description : err,
type: "error",
@@ -427,8 +417,7 @@ async function performAction(notification, actionIndex) {
});
}
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -1,213 +0,0 @@
<template>
<div
class="vue-notification-group experimental-styles-within"
:class="{
'intercom-present': isIntercomPresent,
rightwards: moveNotificationsRight,
}"
>
<transition-group name="notifs">
<div
v-for="(item, index) in notifications"
:key="item.id"
class="vue-notification-wrapper"
@mouseenter="stopTimer(item)"
@mouseleave="setNotificationTimer(item)"
>
<div class="flex w-full gap-2 overflow-hidden rounded-lg bg-bg-raised shadow-xl">
<div
class="w-2"
:class="{
'bg-red': item.type === 'error',
'bg-orange': item.type === 'warning',
'bg-green': item.type === 'success',
'bg-blue': !item.type || !['error', 'warning', 'success'].includes(item.type),
}"
></div>
<div
class="grid w-full grid-cols-[auto_1fr_auto] items-center gap-x-2 gap-y-1 py-2 pl-1 pr-3"
>
<div
class="flex items-center"
:class="{
'text-red': item.type === 'error',
'text-orange': item.type === 'warning',
'text-green': item.type === 'success',
'text-blue': !item.type || !['error', 'warning', 'success'].includes(item.type),
}"
>
<IssuesIcon v-if="item.type === 'warning'" class="h-6 w-6" />
<CheckCircleIcon v-else-if="item.type === 'success'" class="h-6 w-6" />
<XCircleIcon v-else-if="item.type === 'error'" class="h-6 w-6" />
<InfoIcon v-else class="h-6 w-6" />
</div>
<div class="m-0 text-wrap font-bold text-contrast" v-html="item.title"></div>
<div class="flex items-center gap-1">
<div v-if="item.count && item.count > 1" class="text-xs font-bold text-contrast">
x{{ item.count }}
</div>
<ButtonStyled circular size="small">
<button v-tooltip="'Copy to clipboard'" @click="copyToClipboard(item)">
<CheckIcon v-if="copied[createNotifText(item)]" />
<CopyIcon v-else />
</button>
</ButtonStyled>
<ButtonStyled circular size="small">
<button v-tooltip="`Dismiss`" @click="notifications.splice(index, 1)">
<XIcon />
</button>
</ButtonStyled>
</div>
<div></div>
<div class="col-span-2 text-sm text-primary" v-html="item.text"></div>
<template v-if="item.errorCode">
<div></div>
<div
class="m-0 text-wrap text-xs font-medium text-secondary"
v-html="item.errorCode"
></div>
</template>
</div>
</div>
</div>
</transition-group>
</div>
</template>
<script setup>
import { ButtonStyled } from "@modrinth/ui";
import {
XCircleIcon,
CheckCircleIcon,
CheckIcon,
InfoIcon,
IssuesIcon,
XIcon,
CopyIcon,
} from "@modrinth/assets";
const notifications = useNotifications();
const { isVisible: moveNotificationsRight } = useNotificationRightwards();
const isIntercomPresent = ref(false);
function stopTimer(notif) {
clearTimeout(notif.timer);
}
const copied = ref({});
const createNotifText = (notif) => {
let text = "";
if (notif.title) {
text += notif.title;
}
if (notif.text) {
if (text.length > 0) {
text += "\n";
}
text += notif.text;
}
if (notif.errorCode) {
if (text.length > 0) {
text += "\n";
}
text += notif.errorCode;
}
return text;
};
function checkIntercomPresence() {
isIntercomPresent.value = !!document.querySelector(".intercom-lightweight-app");
}
onMounted(() => {
checkIntercomPresence();
const observer = new MutationObserver(() => {
checkIntercomPresence();
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
onBeforeUnmount(() => {
observer.disconnect();
});
});
function copyToClipboard(notif) {
const text = createNotifText(notif);
copied.value[text] = true;
navigator.clipboard.writeText(text);
setTimeout(() => {
delete copied.value[text];
}, 2000);
}
</script>
<style lang="scss" scoped>
.vue-notification-group {
position: fixed;
right: 1.5rem;
bottom: 1.5rem;
z-index: 200;
width: 450px;
@media screen and (max-width: 500px) {
width: calc(100% - 0.75rem * 2);
right: 0.75rem;
bottom: 0.75rem;
}
&.intercom-present {
bottom: 5rem;
}
&.rightwards {
right: unset !important;
left: 1.5rem;
@media screen and (max-width: 500px) {
left: 0.75rem;
}
}
.vue-notification-wrapper {
width: 100%;
overflow: hidden;
margin-bottom: 10px;
&:last-child {
margin: 0;
}
}
@media screen and (max-width: 750px) {
transition: bottom 0.25s ease-in-out;
bottom: calc(var(--size-mobile-navbar-height) + 10px) !important;
&.browse-menu-open {
bottom: calc(var(--size-mobile-navbar-height-expanded) + 10px) !important;
}
}
}
.notifs-enter-active,
.notifs-leave-active,
.notifs-move {
transition: all 0.25s ease-in-out;
}
.notifs-enter-from,
.notifs-leave-to {
opacity: 0;
}
.notifs-enter-from {
transform: translateY(100%) scale(0.8);
}
.notifs-leave-to {
transform: translateX(100%) scale(0.8);
}
</style>

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,20 +70,22 @@
</div>
</NewModal>
</template>
<script setup>
import { XIcon, PlusIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal } from "@modrinth/ui";
<script setup lang="ts">
import { PlusIcon, XIcon } from "@modrinth/assets";
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 = {
@@ -92,19 +94,18 @@ 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({
group: "main",
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -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

@@ -105,24 +105,25 @@
<script setup lang="ts">
import {
ChevronRightIcon,
CheckIcon,
XIcon,
AsteriskIcon,
LightBulbIcon,
TriangleAlertIcon,
CheckIcon,
ChevronRightIcon,
DropdownIcon,
SendIcon,
LightBulbIcon,
ScaleIcon,
InfoIcon,
SendIcon,
TriangleAlertIcon,
XIcon,
} from "@modrinth/assets";
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
import { nags } from "@modrinth/moderation";
import { ButtonStyled } from "@modrinth/ui";
import { useVIntl, defineMessages, type MessageDescriptor } from "@vintl/vintl";
import type { Nag, NagContext, NagStatus } from "@modrinth/moderation";
import { nags } from "@modrinth/moderation";
import { ButtonStyled, injectNotificationManager } from "@modrinth/ui";
import type { Project, User, Version } from "@modrinth/utils";
import { defineMessages, useVIntl, type MessageDescriptor } from "@vintl/vintl";
import type { Component } from "vue";
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
const { addNotification } = injectNotificationManager();
interface Tags {
rejectedStatuses: string[];
@@ -433,14 +434,12 @@ async function acceptInvite(): Promise<void> {
await acceptTeamInvite(props.project.team);
await updateMembers();
addNotification({
group: "main",
title: formatMessage(messages.success),
text: formatMessage(messages.successJoin),
type: "success",
});
} catch (error) {
addNotification({
group: "main",
title: formatMessage(messages.error),
text: formatMessage(messages.errorJoin),
type: "error",
@@ -456,14 +455,12 @@ async function declineInvite(): Promise<void> {
await removeTeamMember(props.project.team, props.auth.user.id);
await updateMembers();
addNotification({
group: "main",
title: formatMessage(messages.success),
text: formatMessage(messages.successDecline),
type: "success",
});
} catch (error) {
addNotification({
group: "main",
title: formatMessage(messages.error),
text: formatMessage(messages.errorDecline),
type: "error",

View File

@@ -118,22 +118,25 @@
</template>
<script setup lang="ts">
import dayjs from "dayjs";
import {
Avatar,
useRelativeTime,
OverflowMenu,
type OverflowMenuOption,
ButtonStyled,
} from "@modrinth/ui";
import {
EllipsisVerticalIcon,
OrganizationIcon,
EyeIcon,
ClipboardCopyIcon,
EllipsisVerticalIcon,
EyeIcon,
LinkIcon,
OrganizationIcon,
} from "@modrinth/assets";
import type { ExtendedDelphiReport } from "@modrinth/moderation";
import {
Avatar,
ButtonStyled,
injectNotificationManager,
OverflowMenu,
useRelativeTime,
type OverflowMenuOption,
} from "@modrinth/ui";
import dayjs from "dayjs";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
report: ExtendedDelphiReport;

View File

@@ -135,28 +135,31 @@
</template>
<script setup lang="ts">
import {
Avatar,
useRelativeTime,
OverflowMenu,
type OverflowMenuOption,
CollapsibleRegion,
ButtonStyled,
} from "@modrinth/ui";
import {
EllipsisVerticalIcon,
OrganizationIcon,
EyeIcon,
ClipboardCopyIcon,
EllipsisVerticalIcon,
EyeIcon,
LinkIcon,
OrganizationIcon,
} from "@modrinth/assets";
import {
type ExtendedReport,
reportQuickReplies,
type ReportQuickReply,
} from "@modrinth/moderation";
import {
Avatar,
ButtonStyled,
CollapsibleRegion,
injectNotificationManager,
OverflowMenu,
type OverflowMenuOption,
useRelativeTime,
} from "@modrinth/ui";
import ChevronDownIcon from "../servers/icons/ChevronDownIcon.vue";
import ReportThread from "../thread/ReportThread.vue";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
report: ExtendedReport;
}>();

View File

@@ -335,66 +335,67 @@
<script lang="ts" setup>
import {
LeftArrowIcon,
RightArrowIcon,
DropdownIcon,
XIcon,
ScaleIcon,
ListBulletedIcon,
FileTextIcon,
BrushCleaningIcon,
CheckIcon,
KeyboardIcon,
DropdownIcon,
EyeOffIcon,
FileTextIcon,
KeyboardIcon,
LeftArrowIcon,
ListBulletedIcon,
RightArrowIcon,
ScaleIcon,
ToggleLeftIcon,
ToggleRightIcon,
XIcon,
} from "@modrinth/assets";
import {
type Action,
type ButtonAction,
type ConditionalButtonAction,
type DropdownAction,
type MultiSelectChipsAction,
type Stage,
type ToggleAction,
checklist,
getActionIdForStage,
initializeActionState,
getActionMessage,
findMatchingVariant,
processMessage,
getVisibleInputs,
serializeActionStates,
deserializeActionStates,
kebabToTitleCase,
flattenProjectVariables,
expandVariables,
finalPermissionMessages,
findMatchingVariant,
flattenProjectVariables,
getActionIdForStage,
getActionMessage,
getVisibleInputs,
handleKeybind,
initializeActionState,
kebabToTitleCase,
keybinds,
processMessage,
serializeActionStates,
} from "@modrinth/moderation";
import {
ButtonStyled,
Collapsible,
OverflowMenu,
type OverflowMenuOption,
Checkbox,
Collapsible,
DropdownSelect,
MarkdownEditor,
OverflowMenu,
type OverflowMenuOption,
injectNotificationManager,
} from "@modrinth/ui";
import {
type Project,
renderHighlightedString,
type ModerationJudgements,
type ModerationModpackItem,
type Project,
type ProjectStatus,
renderHighlightedString,
} from "@modrinth/utils";
import { computedAsync, useLocalStorage } from "@vueuse/core";
import {
type Action,
type MultiSelectChipsAction,
type DropdownAction,
type ButtonAction,
type ToggleAction,
type ConditionalButtonAction,
type Stage,
finalPermissionMessages,
} from "@modrinth/moderation";
import ModpackPermissionsFlow from "./ModpackPermissionsFlow.vue";
import KeybindsModal from "./ChecklistKeybindsModal.vue";
import { useModerationStore } from "~/store/moderation.ts";
import KeybindsModal from "./ChecklistKeybindsModal.vue";
import ModpackPermissionsFlow from "./ModpackPermissionsFlow.vue";
const { addNotification } = injectNotificationManager();
const keybindsModal = ref<InstanceType<typeof KeybindsModal>>();

View File

@@ -42,12 +42,14 @@
</template>
<script setup lang="ts">
import { ref, nextTick, computed } from "vue";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { IssuesIcon, PlusIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager, NewModal } from "@modrinth/ui";
import { ModrinthServersFetchError, type ServerBackup } from "@modrinth/utils";
import { computed, nextTick, ref } from "vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();

View File

@@ -45,12 +45,14 @@
</template>
<script setup lang="ts">
import { ref, nextTick, computed } from "vue";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { SpinnerIcon, SaveIcon, XIcon, IssuesIcon } from "@modrinth/assets";
import { IssuesIcon, SaveIcon, SpinnerIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager, NewModal } from "@modrinth/ui";
import type { Backup } from "@modrinth/utils";
import { computed, nextTick, ref } from "vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();

View File

@@ -17,12 +17,14 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ConfirmModal, NewModal } from "@modrinth/ui";
import { ConfirmModal, injectNotificationManager, NewModal } from "@modrinth/ui";
import type { Backup } from "@modrinth/utils";
import { ref } from "vue";
import BackupItem from "~/components/ui/servers/BackupItem.vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();

View File

@@ -56,11 +56,13 @@
</template>
<script setup lang="ts">
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { XIcon, SaveIcon } from "@modrinth/assets";
import { ref, computed } from "vue";
import { SaveIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager, NewModal } from "@modrinth/ui";
import { computed, ref } from "vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -110,7 +112,6 @@ const fetchSettings = async () => {
} catch (error) {
console.error("Error fetching backup settings:", error);
addNotification({
group: "server",
title: "Error",
text: "Failed to load backup settings",
type: "error",
@@ -135,7 +136,6 @@ const saveSettings = async () => {
};
addNotification({
group: "server",
title: "Success",
text: "Backup settings updated successfully",
type: "success",
@@ -145,7 +145,6 @@ const saveSettings = async () => {
} catch (error) {
console.error("Error saving backup settings:", error);
addNotification({
group: "server",
title: "Error",
text: "Failed to save backup settings",
type: "error",

View File

@@ -101,11 +101,13 @@
</template>
<script setup lang="ts">
import { FolderOpenIcon, CheckCircleIcon, XCircleIcon } from "@modrinth/assets";
import { ButtonStyled } from "@modrinth/ui";
import { ref, computed, watch, nextTick } from "vue";
import { CheckCircleIcon, FolderOpenIcon, XCircleIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager } from "@modrinth/ui";
import { computed, nextTick, ref, watch } from "vue";
import { FSModule } from "~/composables/servers/modules/fs.ts";
const { addNotification } = injectNotificationManager();
interface UploadItem {
file: File;
progress: number;
@@ -282,7 +284,6 @@ const uploadFile = async (file: File) => {
if (error instanceof Error && error.message !== "Upload cancelled") {
addNotification({
group: "files",
title: "Upload failed",
text: `Failed to upload ${file.name}`,
type: "error",

View File

@@ -67,11 +67,13 @@
</template>
<script setup lang="ts">
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { DownloadIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager, NewModal } from "@modrinth/ui";
import { ModrinthServersFetchError } from "@modrinth/utils";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
project: any;
@@ -112,14 +114,12 @@ const handleReinstall = async () => {
} catch (error) {
if (error instanceof ModrinthServersFetchError && error.statusCode === 429) {
addNotification({
group: "server",
title: "Cannot reinstall server",
text: "You are being rate limited. Please try again later.",
type: "error",
});
} else {
addNotification({
group: "server",
title: "Reinstall Failed",
text: "An unexpected error occurred while reinstalling. Please try again later.",
type: "error",

View File

@@ -144,18 +144,20 @@
</template>
<script setup lang="ts">
import { BackupWarning, ButtonStyled, NewModal } from "@modrinth/ui";
import {
UploadIcon,
RightArrowIcon,
XIcon,
ServerIcon,
ArrowBigRightDashIcon,
RightArrowIcon,
ServerIcon,
UploadIcon,
XIcon,
} from "@modrinth/assets";
import { BackupWarning, ButtonStyled, injectNotificationManager, NewModal } from "@modrinth/ui";
import { formatBytes, ModrinthServersFetchError } from "@modrinth/utils";
import { onMounted, onUnmounted } from "vue";
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
import type { ModrinthServer } from "~/composables/servers/modrinth-servers";
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
const { addNotification } = injectNotificationManager();
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
if (isLoading.value) {
@@ -250,7 +252,6 @@ const handleReinstall = async () => {
if (!mrpackFile.value) {
addNotification({
group: "server",
title: "No file selected",
text: "Choose a .mrpack file before installing.",
type: "error",
@@ -301,14 +302,12 @@ const handleReinstall = async () => {
} catch (error) {
if (error instanceof ModrinthServersFetchError && error.statusCode === 429) {
addNotification({
group: "server",
title: "Cannot upload and install modpack to server",
text: "You are being rate limited. Please try again later.",
type: "error",
});
} else {
addNotification({
group: "server",
title: "Modpack upload and install failed",
text: "An unexpected error occurred while uploading/installing. Please try again later.",
type: "error",

View File

@@ -197,13 +197,20 @@
</template>
<script setup lang="ts">
import { BackupWarning, ButtonStyled, NewModal, Toggle } from "@modrinth/ui";
import { DropdownIcon, RightArrowIcon, ServerIcon, XIcon } from "@modrinth/assets";
import { $fetch } from "ofetch";
import {
BackupWarning,
ButtonStyled,
injectNotificationManager,
NewModal,
Toggle,
} from "@modrinth/ui";
import { type Loaders, ModrinthServersFetchError } from "@modrinth/utils";
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
import { $fetch } from "ofetch";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
interface LoaderVersion {
@@ -475,14 +482,12 @@ const handleReinstall = async () => {
} catch (error) {
if (error instanceof ModrinthServersFetchError && (error as any)?.statusCode === 429) {
addNotification({
group: "server",
title: "Cannot reinstall server",
text: "You are being rate limited. Please try again later.",
type: "error",
});
} else {
addNotification({
group: "server",
title: "Reinstall Failed",
text: "An unexpected error occurred while reinstalling. Please try again later.",
type: "error",

View File

@@ -20,8 +20,11 @@
<script setup lang="ts">
import { LinkIcon } from "@modrinth/assets";
import { injectNotificationManager } from "@modrinth/ui";
import { useStorage } from "@vueuse/core";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
subdomain: string;
noSeparator?: boolean;
@@ -30,7 +33,6 @@ const props = defineProps<{
const copySubdomain = () => {
navigator.clipboard.writeText(props.subdomain + ".modrinth.gg");
addNotification({
group: "servers",
title: "Custom URL copied",
text: "Your server's URL has been copied to your clipboard.",
type: "success",

View File

@@ -1,11 +1,18 @@
<script setup lang="ts">
import { Accordion, ButtonStyled, NewModal, ServerNotice, TagItem } from "@modrinth/ui";
import { PlusIcon, XIcon } from "@modrinth/assets";
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
import {
Accordion,
ButtonStyled,
injectNotificationManager,
NewModal,
ServerNotice,
TagItem,
} from "@modrinth/ui";
import { type ServerNotice as ServerNoticeType } from "@modrinth/utils";
import { ref } from "vue";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
const app = useNuxtApp() as unknown as { $notify: any };
const { addNotification } = injectNotificationManager();
const modal = ref<InstanceType<typeof NewModal>>();
@@ -39,16 +46,14 @@ async function assign(server: boolean = true) {
method: "PUT",
},
).catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error assigning notice",
text: err,
type: "error",
});
});
} else {
app.$notify({
group: "main",
addNotification({
title: "Error assigning notice",
text: "No server or node specified",
type: "error",
@@ -64,8 +69,7 @@ async function unassignDetect() {
const node = assignedNodes.value.some((assigned) => assigned.id === input);
if (!server && !node) {
app.$notify({
group: "main",
addNotification({
title: "Error unassigning notice",
text: "ID is not an assigned server or node",
type: "error",
@@ -84,8 +88,7 @@ async function unassign(id: string, server: boolean = true) {
method: "PUT",
},
).catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error unassigning notice",
text: err,
type: "error",

View File

@@ -251,23 +251,25 @@
</template>
<script setup>
import { CopyCode, OverflowMenu, MarkdownEditor } from "@modrinth/ui";
import {
DropdownIcon,
ReplyIcon,
SendIcon,
CheckCircleIcon,
XIcon,
EyeOffIcon,
CheckIcon,
DropdownIcon,
EyeOffIcon,
ReplyIcon,
ScaleIcon,
SendIcon,
XIcon,
} from "@modrinth/assets";
import { useImageUpload } from "~/composables/image-upload.ts";
import ThreadMessage from "~/components/ui/thread/ThreadMessage.vue";
import { isStaff } from "~/helpers/users.js";
import { isApproved, isRejected } from "~/helpers/projects.js";
import Modal from "~/components/ui/Modal.vue";
import { CopyCode, MarkdownEditor, OverflowMenu, injectNotificationManager } from "@modrinth/ui";
import Checkbox from "~/components/ui/Checkbox.vue";
import Modal from "~/components/ui/Modal.vue";
import ThreadMessage from "~/components/ui/thread/ThreadMessage.vue";
import { useImageUpload } from "~/composables/image-upload.ts";
import { isApproved, isRejected } from "~/helpers/projects.js";
import { isStaff } from "~/helpers/users.js";
const { addNotification } = injectNotificationManager();
const props = defineProps({
thread: {
@@ -388,8 +390,7 @@ async function sendReply(status = null, privateMessage = false) {
props.setStatus(status);
}
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "Error sending message",
text: err.data ? err.data.description : err,
type: "error",
@@ -411,8 +412,7 @@ async function closeReport(reply) {
});
await updateThreadLocal();
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "Error closing report",
text: err.data ? err.data.description : err,
type: "error",
@@ -430,8 +430,7 @@ async function reopenReport() {
});
await updateThreadLocal();
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "Error reopening report",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -110,13 +110,15 @@
</template>
<script setup lang="ts">
import { CopyCode, MarkdownEditor, ButtonStyled } from "@modrinth/ui";
import { ReplyIcon, SendIcon, CheckCircleIcon, ScaleIcon } from "@modrinth/assets";
import type { Thread, Report, User, ThreadMessage as TypeThreadMessage } from "@modrinth/utils";
import { CheckCircleIcon, ReplyIcon, ScaleIcon, SendIcon } from "@modrinth/assets";
import { ButtonStyled, CopyCode, injectNotificationManager, MarkdownEditor } from "@modrinth/ui";
import type { Report, Thread, ThreadMessage as TypeThreadMessage, User } from "@modrinth/utils";
import dayjs from "dayjs";
import ThreadMessage from "./ThreadMessage.vue";
import { useImageUpload } from "~/composables/image-upload.ts";
import { isStaff } from "~/helpers/users.js";
import ThreadMessage from "./ThreadMessage.vue";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
thread: Thread;