feat: migrate frontend notifications to dependency injection based notificaton manager

This commit is contained in:
IMB11 committed 2025-07-30 12:48:08 +01:00
1 parent 6387fb21c6
commit 3714375c86
81 files changed
+956 -936

No files matched your search

+3 -1
View File
@@ -4,6 +4,8 @@
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"editor.detectIndentation": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
"source.fixAll.eslint": "explicit",
"source.organizeImports": "always",
"source.sortImports": "always"
}
}
+5 -2
View File
@@ -1,11 +1,14 @@
<template>
<NuxtLayout>
<ModrinthLoadingIndicator />
<Notifications />
<NotificationPanel />
<NuxtPage />
</NuxtLayout>
</template>
<script setup lang="ts">
import { NotificationPanel, provideNotificationManager } from "@modrinth/ui";
import ModrinthLoadingIndicator from "~/components/ui/modrinth-loading-indicator.ts";
import Notifications from "~/components/ui/Notifications.vue";
import { FrontendNotificationManager } from "./providers/web-notifications";
provideNotificationManager(new FrontendNotificationManager());
</script>
@@ -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",
@@ -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",
@@ -319,26 +319,17 @@
</template>
<script setup>
import { renderString } from "@modrinth/utils";
import {
UserPlusIcon,
ScaleIcon,
BellIcon,
CheckCircleIcon,
CalendarIcon,
VersionIcon,
CheckCircleIcon,
CheckIcon,
XIcon,
ExternalIcon,
} 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";
ScaleIcon,
UserPlusIcon,
VersionIcon,
XIcon,
} from "@modrinth/ars.js";
const app = useNuxtApp();
const emit = defineEmits(["update:notifications"]);
@@ -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",
@@ -71,10 +71,11 @@
</NewModal>
</template>
<script setup>
import { XIcon, PlusIcon } from "@modrinth/assets";
import { PlusIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal } from "@modrinth/ui";
const router = useNativeRouter();
const { addNotification } = injectNotificationManager();
const name = ref("");
const slug = ref("");
@@ -104,7 +105,6 @@ async function createOrganization() {
} catch (err) {
console.error(err);
addNotification({
group: "main",
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -109,18 +109,21 @@
<script setup>
import {
ChevronRightIcon,
CheckIcon,
XIcon,
AsteriskIcon,
LightBulbIcon,
SendIcon,
ScaleIcon,
CheckIcon,
ChevronRightIcon,
DropdownIcon,
LightBulbIcon,
ScaleIcon,
SendIcon,
XIcon,
} from "@modrinth/assets";
import { injectNotificationManager } from "@modrinth/ui";
import { formatProjectType } from "@modrinth/utils";
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
const { addNotification } = injectNotificationManager();
const props = defineProps({
project: {
type: Object,
@@ -165,7 +168,6 @@ const props = defineProps({
default() {
return () => {
addNotification({
group: "main",
title: "An error occurred",
text: "setProcessing function not found",
type: "error",
@@ -178,7 +180,6 @@ const props = defineProps({
default() {
return () => {
addNotification({
group: "main",
title: "An error occurred",
text: "toggleCollapsed function not found",
type: "error",
@@ -191,7 +192,6 @@ const props = defineProps({
default() {
return () => {
addNotification({
group: "main",
title: "An error occurred",
text: "updateMembers function not found",
type: "error",
@@ -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;
@@ -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;
}>();
@@ -313,64 +313,65 @@
<script lang="ts" setup>
import {
LeftArrowIcon,
RightArrowIcon,
DropdownIcon,
XIcon,
ScaleIcon,
ListBulletedIcon,
FileTextIcon,
BrushCleaningIcon,
CheckIcon,
KeyboardIcon,
DropdownIcon,
EyeOffIcon,
FileTextIcon,
KeyboardIcon,
LeftArrowIcon,
ListBulletedIcon,
RightArrowIcon,
ScaleIcon,
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,
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 * as prettier from "prettier";
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>>();
@@ -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;
}>();
@@ -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;
}>();
@@ -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;
}>();
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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;
+4 -3
View File
@@ -1,3 +1,5 @@
import { injectNotificationManager } from "@modrinth/ui";
export const useAuth = async (oldToken = null) => {
const auth = useState("auth", () => ({
user: null,
@@ -128,9 +130,8 @@ export const removeAuthProvider = async (provider) => {
});
await useAuth(auth.value.token);
} catch (err) {
const data = useNuxtApp();
data.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "An error occurred",
text: err.data.description,
type: "error",
-38
View File
@@ -1,38 +0,0 @@
export const useNotifications = () => useState("notifications", () => []);
export const addNotification = (notification) => {
const notifications = useNotifications();
const existingNotif = notifications.value.find(
(x) =>
x.text === notification.text &&
x.title === notification.title &&
x.type === notification.type,
);
if (existingNotif) {
setNotificationTimer(existingNotif);
existingNotif.count++;
return;
}
notification.id = new Date();
notification.count = 1;
setNotificationTimer(notification);
notifications.value.push(notification);
};
export const setNotificationTimer = (notification) => {
if (!notification) return;
const notifications = useNotifications();
if (notification.timer) {
clearTimeout(notification.timer);
}
notification.timer = setTimeout(() => {
notifications.value.splice(notifications.value.indexOf(notification), 1);
}, 30000);
};
@@ -1,3 +1,5 @@
import { injectNotificationManager } from "@modrinth/ui";
type AsyncFunction<TArgs extends any[], TResult> = (...args: TArgs) => Promise<TResult>;
type ErrorFunction = (err: any) => void | Promise<void>;
type VoidFunction = () => void | Promise<void>;
@@ -9,8 +11,8 @@ type useClientTry = <TArgs extends any[], TResult>(
) => (...args: TArgs) => Promise<TResult | undefined>;
const defaultOnError: ErrorFunction = (error) => {
const { addNotification } = injectNotificationManager();
addNotification({
group: "main",
title: "An error occurred",
text: error?.data?.description || error.message || error || "Unknown error",
type: "error",
+5 -5
View File
@@ -1,3 +1,5 @@
import { injectNotificationManager } from "@modrinth/ui";
export const useUser = async (force = false) => {
const user = useState("user", () => {});
@@ -135,7 +137,7 @@ export const userFollowProject = async (project) => {
}
};
export const resendVerifyEmail = async () => {
const app = useNuxtApp();
const { addNotification } = injectNotificationManager();
startLoading();
try {
@@ -144,15 +146,13 @@ export const resendVerifyEmail = async () => {
});
const auth = await useAuth();
app.$notify({
group: "main",
addNotification({
title: "Email sent",
text: `An email with a link to verify your account has been sent to ${auth.value.user.email}.`,
type: "success",
});
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data.description,
type: "error",
@@ -1,4 +1,4 @@
import { useNuxtApp } from "#imports";
import { injectNotificationManager } from "@modrinth/ui";
// 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 };
@@ -173,9 +173,8 @@ export async function markAsRead(
return newNotifs;
};
} catch (err: any) {
const app: any = useNuxtApp();
app.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "Error marking notification as read",
text: err?.data?.description ?? err,
type: "error",
+40 -42
View File
@@ -666,65 +666,65 @@
</template>
<script setup>
import {
ModrinthIcon,
ArrowBigUpDashIcon,
BookmarkIcon,
ServerIcon,
LogInIcon,
DownloadIcon,
LibraryIcon,
XIcon,
IssuesIcon,
ReportIcon,
CompassIcon,
HamburgerIcon,
SearchIcon,
BellIcon,
SettingsIcon,
BlueskyIcon,
BookmarkIcon,
BoxIcon,
BracesIcon,
ChartIcon,
CollectionIcon,
CompassIcon,
CurrencyIcon,
DiscordIcon,
DownloadIcon,
DropdownIcon,
GithubIcon,
GlassesIcon,
HamburgerIcon,
HomeIcon,
IssuesIcon,
LibraryIcon,
LogInIcon,
LogOutIcon,
MastodonIcon,
ModrinthIcon,
MoonIcon,
SunIcon,
OrganizationIcon,
PackageOpenIcon,
PaintbrushIcon,
PlugIcon,
PlusIcon,
DropdownIcon,
LogOutIcon,
ChartIcon,
BoxIcon,
CollectionIcon,
OrganizationIcon,
UserIcon,
CurrencyIcon,
BracesIcon,
GlassesIcon,
PaintbrushIcon,
PackageOpenIcon,
DiscordIcon,
BlueskyIcon,
TwitterIcon,
MastodonIcon,
GithubIcon,
ReportIcon,
ScaleIcon,
SearchIcon,
ServerIcon,
SettingsIcon,
SunIcon,
TwitterIcon,
UserIcon,
XIcon,
} from "@modrinth/assets";
import {
Avatar,
Button,
ButtonStyled,
commonMessages,
injectNotificationManager,
OverflowMenu,
PagewideBanner,
Avatar,
commonMessages,
} from "@modrinth/ui";
import { isAdmin, isStaff } from "@modrinth/utils";
import { errors as generatedStateErrors } from "~/generated/state.json";
import ModalCreation from "~/components/ui/ModalCreation.vue";
import { getProjectTypeMessage } from "~/utils/i18n-project-type.ts";
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
import ModalCreation from "~/components/ui/ModalCreation.vue";
import OrganizationCreateModal from "~/components/ui/OrganizationCreateModal.vue";
import TeleportOverflowMenu from "~/components/ui/servers/TeleportOverflowMenu.vue";
import { errors as generatedStateErrors } from "~/generated/state.json";
import { getProjectTypeMessage } from "~/utils/i18n-project-type.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const app = useNuxtApp();
const auth = await useAuth();
const user = await useUser();
@@ -1079,15 +1079,13 @@ function developerModeIncrement() {
developerModeCounter.value = 0;
saveFeatureFlags();
if (flags.value.developerMode) {
app.$notify({
group: "main",
addNotification({
title: "Developer mode activated",
text: "Developer mode has been enabled",
type: "success",
});
} else {
app.$notify({
group: "main",
addNotification({
title: "Developer mode deactivated",
text: "Developer mode has been disabled",
type: "success",
+16 -19
View File
@@ -890,6 +890,7 @@
</template>
<script setup>
import { navigateTo } from "#app";
import {
BookmarkIcon,
BookTextIcon,
@@ -906,6 +907,7 @@ import {
HeartIcon,
InfoIcon,
LinkIcon as LinksIcon,
ModrinthIcon,
MoreVerticalIcon,
PlusIcon,
ReportIcon,
@@ -917,13 +919,13 @@ import {
UsersIcon,
VersionIcon,
WrenchIcon,
ModrinthIcon,
XIcon,
} from "@modrinth/assets";
import {
Avatar,
ButtonStyled,
Checkbox,
injectNotificationManager,
NewModal,
OverflowMenu,
PopoutMenu,
@@ -935,36 +937,37 @@ import {
ProjectSidebarLinks,
ProjectStatusBadge,
ScrollablePanel,
TagItem,
ServersPromo,
TagItem,
useRelativeTime,
} from "@modrinth/ui";
import VersionSummary from "@modrinth/ui/src/components/version/VersionSummary.vue";
import { formatCategory, formatProjectType, renderString } from "@modrinth/utils";
import { useLocalStorage } from "@vueuse/core";
import dayjs from "dayjs";
import { Tooltip } from "floating-vue";
import { useLocalStorage } from "@vueuse/core";
import { navigateTo } from "#app";
import Accordion from "~/components/ui/Accordion.vue";
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
import AutomaticAccordion from "~/components/ui/AutomaticAccordion.vue";
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
import MessageBanner from "~/components/ui/MessageBanner.vue";
import ModerationChecklist from "~/components/ui/moderation/checklist/ModerationChecklist.vue";
import NavStack from "~/components/ui/NavStack.vue";
import NavStackItem from "~/components/ui/NavStackItem.vue";
import NavTabs from "~/components/ui/NavTabs.vue";
import ProjectMemberHeader from "~/components/ui/ProjectMemberHeader.vue";
import { userCollectProject } from "~/composables/user.js";
import { reportProject } from "~/utils/report-helpers.ts";
import { saveFeatureFlags } from "~/composables/featureFlags.ts";
import ModerationChecklist from "~/components/ui/moderation/checklist/ModerationChecklist.vue";
import { userCollectProject } from "~/composables/user.js";
import { useModerationStore } from "~/store/moderation.ts";
import { reportProject } from "~/utils/report-helpers.ts";
const data = useNuxtApp();
const route = useNativeRoute();
const config = useRuntimeConfig();
const moderationStore = useModerationStore();
const notifications = injectNotificationManager();
const { addNotification } = notifications;
const auth = await useAuth();
const user = await useUser();
@@ -974,7 +977,6 @@ const flags = useFeatureFlags();
const cosmetics = useCosmetics();
const { formatMessage } = useVIntl();
const { setVisible } = useNotificationRightwards();
const settingsModal = ref();
const downloadModal = ref();
@@ -1425,8 +1427,7 @@ async function setProcessing() {
project.value.status = "processing";
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1459,8 +1460,7 @@ async function patchProject(resData, quiet = false) {
result = true;
if (!quiet) {
data.$notify({
group: "main",
addNotification({
title: "Project updated",
text: "Your project has been updated.",
type: "success",
@@ -1468,8 +1468,7 @@ async function patchProject(resData, quiet = false) {
window.scrollTo({ top: 0, behavior: "smooth" });
}
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1498,15 +1497,13 @@ async function patchIcon(icon) {
);
await resetProject();
result = true;
data.$notify({
group: "main",
addNotification({
title: "Project icon updated",
text: "Your project's icon has been updated.",
type: "success",
});
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1555,7 +1552,7 @@ const collapsedModerationChecklist = useLocalStorage("collapsed-moderation-check
watch(
showModerationChecklist,
(newValue) => {
setVisible(newValue);
notifications.setNotificationsPanelRightwards(newValue);
},
{ immediate: true },
);
+22 -19
View File
@@ -278,26 +278,26 @@
<script setup>
import {
PlusIcon,
CalendarIcon,
ContractIcon,
EditIcon,
TrashIcon,
ExpandIcon,
ExternalIcon,
ImageIcon,
InfoIcon,
LeftArrowIcon,
PlusIcon,
RightArrowIcon,
SaveIcon,
StarIcon,
XIcon,
RightArrowIcon,
LeftArrowIcon,
ExternalIcon,
ExpandIcon,
ContractIcon,
UploadIcon,
InfoIcon,
ImageIcon,
TransferIcon,
TrashIcon,
UploadIcon,
XIcon,
} from "@modrinth/assets";
import { ConfirmModal } from "@modrinth/ui";
import FileInput from "~/components/ui/FileInput.vue";
import { ConfirmModal, injectNotificationManager } from "@modrinth/ui";
import DropArea from "~/components/ui/DropArea.vue";
import FileInput from "~/components/ui/FileInput.vue";
import Modal from "~/components/ui/Modal.vue";
import { isPermission } from "~/utils/permissions.ts";
@@ -425,6 +425,8 @@ export default defineNuxtComponent({
this.shouldPreventActions = true;
startLoading();
const { addNotification } = injectNotificationManager();
try {
let url = `project/${this.project.id}/gallery?ext=${
this.editFile
@@ -450,8 +452,7 @@ export default defineNuxtComponent({
this.$refs.modal_edit_item.hide();
} catch (err) {
this.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -465,6 +466,8 @@ export default defineNuxtComponent({
this.shouldPreventActions = true;
startLoading();
const { addNotification } = injectNotificationManager();
try {
let url = `project/${this.project.id}/gallery?url=${encodeURIComponent(
this.project.gallery[this.editIndex].url,
@@ -487,8 +490,7 @@ export default defineNuxtComponent({
await this.resetProject();
this.$refs.modal_edit_item.hide();
} catch (err) {
this.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -501,6 +503,8 @@ export default defineNuxtComponent({
async deleteGalleryImage() {
startLoading();
const { addNotification } = injectNotificationManager();
try {
await useBaseFetch(
`project/${this.project.id}/gallery?url=${encodeURIComponent(
@@ -513,8 +517,7 @@ export default defineNuxtComponent({
await this.resetProject();
} catch (err) {
this.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -99,8 +99,8 @@
</div>
</template>
<script setup>
import { XIcon, CheckIcon, IssuesIcon } from "@modrinth/assets";
import { Badge } from "@modrinth/ui";
import { CheckIcon, IssuesIcon, XIcon } from "@modrinth/assets";
import { Badge, injectNotificationManager } from "@modrinth/ui";
import ConversationThread from "~/components/ui/thread/ConversationThread.vue";
import {
getProjectLink,
@@ -111,6 +111,7 @@ import {
isUnderReview,
} from "~/helpers/projects.js";
const { addNotification } = injectNotificationManager();
const props = defineProps({
project: {
type: Object,
@@ -131,7 +132,6 @@ const props = defineProps({
},
});
const app = useNuxtApp();
const auth = await useAuth();
const { data: thread } = await useAsyncData(`thread/${props.project.thread_id}`, () =>
@@ -153,8 +153,7 @@ async function setStatus(status) {
await props.resetProject();
thread.value = await useBaseFetch(`thread/${thread.value.id}`);
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -239,12 +239,14 @@
</template>
<script setup>
import { CheckIcon, IssuesIcon, SaveIcon, TrashIcon, UploadIcon, XIcon } from "@modrinth/assets";
import { Avatar, ConfirmModal, injectNotificationManager } from "@modrinth/ui";
import { formatProjectStatus, formatProjectType } from "@modrinth/utils";
import { UploadIcon, SaveIcon, TrashIcon, XIcon, IssuesIcon, CheckIcon } from "@modrinth/assets";
import { Multiselect } from "vue-multiselect";
import { ConfirmModal, Avatar } from "@modrinth/ui";
import FileInput from "~/components/ui/FileInput.vue";
const { addNotification } = injectNotificationManager();
const props = defineProps({
project: {
type: Object,
@@ -374,7 +376,6 @@ const deleteProject = async () => {
await initUserProjects();
await router.push("/dashboard/projects");
addNotification({
group: "main",
title: "Project deleted",
text: "Your project has been deleted.",
type: "success",
@@ -393,7 +394,6 @@ const deleteIcon = async () => {
});
await props.resetProject();
addNotification({
group: "main",
title: "Project icon removed",
text: "Your project's icon has been removed.",
type: "success",
@@ -518,21 +518,30 @@
</template>
<script setup>
import { Multiselect } from "vue-multiselect";
import {
TransferIcon,
CheckIcon,
UsersIcon,
DropdownIcon,
SaveIcon,
UserPlusIcon,
UserXIcon,
OrganizationIcon,
CrownIcon,
DropdownIcon,
OrganizationIcon,
SaveIcon,
TransferIcon,
UserPlusIcon,
UsersIcon,
UserXIcon,
} from "@modrinth/assets";
import { Avatar, Badge, Card, Checkbox, ConfirmModal } from "@modrinth/ui";
import {
Avatar,
Badge,
Card,
Checkbox,
ConfirmModal,
injectNotificationManager,
} from "@modrinth/ui";
import { Multiselect } from "vue-multiselect";
import { removeSelfFromTeam } from "~/helpers/teams.js";
const { addNotification } = injectNotificationManager();
const props = defineProps({
project: {
type: Object,
@@ -654,7 +663,6 @@ const onAddToOrg = useClientTry(async () => {
await updateMembers();
addNotification({
group: "main",
title: "Project transferred",
text: "Your project has been transferred to the organization.",
type: "success",
@@ -675,7 +683,6 @@ const onRemoveFromOrg = useClientTry(async () => {
await updateMembers();
addNotification({
group: "main",
title: "Project removed",
text: "Your project has been removed from the organization.",
type: "success",
@@ -703,7 +710,6 @@ const inviteTeamMember = async () => {
await updateMembers();
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -726,7 +732,6 @@ const removeTeamMember = async (index) => {
await updateMembers();
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -760,14 +765,12 @@ const updateTeamMember = async (index) => {
);
await updateMembers();
addNotification({
group: "main",
title: "Member(s) updated",
text: "Your project's member(s) has been updated.",
type: "success",
});
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -790,7 +793,6 @@ const transferOwnership = async (index) => {
await updateMembers();
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -837,7 +839,6 @@ async function updateOrgMember(index) {
await updateMembers();
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -113,7 +113,8 @@
</template>
<script>
import { StarIcon, SaveIcon } from "@modrinth/assets";
import { SaveIcon, StarIcon } from "@modrinth/assets";
import { injectNotificationManager } from "@modrinth/ui";
import { formatCategory, formatCategoryHeader, formatProjectType } from "@modrinth/utils";
import Checkbox from "~/components/ui/Checkbox.vue";
@@ -146,8 +147,8 @@ export default defineNuxtComponent({
type: Function,
default() {
return () => {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "An error occurred",
text: "Patch project function not found",
type: "error",
@@ -631,45 +631,46 @@
</template>
<script>
import {
Avatar,
Badge,
CopyCode,
Checkbox,
ButtonStyled,
ConfirmModal,
MarkdownEditor,
} from "@modrinth/ui";
import {
FileIcon,
TrashIcon,
EditIcon,
BoxIcon,
ChevronRightIcon,
DownloadIcon,
StarIcon,
ReportIcon,
SaveIcon,
XIcon,
EditIcon,
FileIcon,
HashIcon,
PlusIcon,
TransferIcon,
UploadIcon,
BoxIcon,
ReportIcon,
RightArrowIcon,
ChevronRightIcon,
SaveIcon,
StarIcon,
TransferIcon,
TrashIcon,
UploadIcon,
XIcon,
} from "@modrinth/assets";
import { Multiselect } from "vue-multiselect";
import {
Avatar,
Badge,
ButtonStyled,
Checkbox,
ConfirmModal,
CopyCode,
injectNotificationManager,
MarkdownEditor,
} from "@modrinth/ui";
import { formatBytes, formatCategory } from "@modrinth/utils";
import { Multiselect } from "vue-multiselect";
import { useImageUpload } from "~/composables/image-upload.ts";
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
import { renderHighlightedString } from "~/helpers/highlight.js";
import { inferVersionInfo } from "~/helpers/infer.js";
import { createDataPackVersion } from "~/helpers/package.js";
import { renderHighlightedString } from "~/helpers/highlight.js";
import { reportVersion } from "~/utils/report-helpers.ts";
import { useImageUpload } from "~/composables/image-upload.ts";
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
import Categories from "~/components/ui/search/Categories.vue";
import FileInput from "~/components/ui/FileInput.vue";
import Modal from "~/components/ui/Modal.vue";
import Categories from "~/components/ui/search/Categories.vue";
export default defineNuxtComponent({
components: {
@@ -997,8 +998,8 @@ export default defineNuxtComponent({
const project = await useBaseFetch(`project/${newDependencyId}`);
if (this.version.dependencies.some((dep) => project.id === dep.project_id)) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "Dependency already added",
text: "You cannot add the same dependency twice.",
type: "error",
@@ -1022,8 +1023,8 @@ export default defineNuxtComponent({
const project = await useBaseFetch(`project/${version.project_id}`);
if (this.version.dependencies.some((dep) => version.id === dep.version_id)) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "Dependency already added",
text: "You cannot add the same dependency twice.",
type: "error",
@@ -1050,8 +1051,8 @@ export default defineNuxtComponent({
this.newDependencyId = "";
} catch {
if (!hideErrors) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "Invalid Dependency",
text: "The specified dependency could not be found",
type: "error",
@@ -1144,8 +1145,8 @@ export default defineNuxtComponent({
)}`,
);
} catch (err) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1169,8 +1170,8 @@ export default defineNuxtComponent({
try {
await this.createVersionRaw(this.version);
} catch (err) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1293,15 +1294,15 @@ export default defineNuxtComponent({
this.$refs.modal_package_mod.hide();
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "Packaging Success",
text: "Your data pack was successfully packaged as a mod! Make sure to playtest to check for errors.",
type: "success",
});
} catch (err) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
+13 -10
View File
@@ -251,28 +251,31 @@
</div>
</template>
<script setup>
import {
CheckIcon,
CurrencyIcon,
ExternalIcon,
ModrinthPlusIcon,
ServerIcon,
UserIcon,
XIcon,
} from "@modrinth/assets";
import {
Avatar,
ButtonStyled,
CopyCode,
DropdownSelect,
injectNotificationManager,
NewModal,
Toggle,
useRelativeTime,
} from "@modrinth/ui";
import { formatCategory, formatPrice } from "@modrinth/utils";
import {
CheckIcon,
XIcon,
UserIcon,
ModrinthPlusIcon,
ServerIcon,
ExternalIcon,
CurrencyIcon,
} from "@modrinth/assets";
import dayjs from "dayjs";
import { products } from "~/generated/state.json";
import ModrinthServersIcon from "~/components/ui/servers/ModrinthServersIcon.vue";
import { products } from "~/generated/state.json";
const { addNotification } = injectNotificationManager();
const route = useRoute();
const vintl = useVIntl();
@@ -258,31 +258,31 @@
</div>
</template>
<script setup lang="ts">
import { EditIcon, PlusIcon, SaveIcon, SettingsIcon, TrashIcon, XIcon } from "@modrinth/assets";
import {
CopyCode,
TagItem,
ButtonStyled,
ServerNotice,
commonMessages,
CopyCode,
injectNotificationManager,
NewModal,
ServerNotice,
TagItem,
TeleportDropdownMenu,
Toggle,
useRelativeTime,
} from "@modrinth/ui";
import { SettingsIcon, PlusIcon, SaveIcon, TrashIcon, EditIcon, XIcon } from "@modrinth/assets";
import dayjs from "dayjs";
import { useVIntl } from "@vintl/vintl";
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
import { computed } from "vue";
import { NOTICE_LEVELS } from "@modrinth/ui/src/utils/notices.ts";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
import { useVIntl } from "@vintl/vintl";
import dayjs from "dayjs";
import { computed } from "vue";
import AssignNoticeModal from "~/components/ui/servers/notice/AssignNoticeModal.vue";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const formatRelativeTime = useRelativeTime();
const app = useNuxtApp() as unknown as { $notify: any };
const notices = ref<ServerNoticeType[]>([]);
const createNoticeModal = ref<InstanceType<typeof NewModal>>();
const assignNoticeModal = ref<InstanceType<typeof AssignNoticeModal>>();
@@ -351,15 +351,13 @@ async function deleteNotice(notice: ServerNoticeType) {
method: "DELETE",
})
.then(() => {
app.$notify({
group: "main",
addNotification({
title: `Successfully deleted notice #${notice.id}`,
type: "success",
});
})
.catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error deleting notice",
text: err,
type: "error",
@@ -386,7 +384,6 @@ const noticeSubmitError = computed(() => {
function validateSubmission(message: string) {
if (noticeSubmitError.value) {
addNotification({
group: "main",
title: message,
text: noticeSubmitError.value,
type: "error",
@@ -416,8 +413,7 @@ async function saveChanges() {
: undefined,
},
}).catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error saving changes to notice",
text: err,
type: "error",
@@ -447,8 +443,7 @@ async function createNotice() {
: undefined,
},
}).catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error creating notice",
text: err,
type: "error",
+3 -2
View File
@@ -32,8 +32,10 @@
</div>
</template>
<script setup lang="ts">
import { ButtonStyled } from "@modrinth/ui";
import { MailIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const userEmail = ref("");
@@ -50,7 +52,6 @@ async function getUserFromEmail() {
} catch (err) {
console.error(err);
addNotification({
group: "main",
title: "An error occurred",
text: err.data.description,
type: "error",
+6 -7
View File
@@ -80,13 +80,14 @@
</template>
<script setup>
import { Button, Avatar, commonMessages } from "@modrinth/ui";
import { XIcon, CheckIcon } from "@modrinth/assets";
import { useBaseFetch } from "@/composables/fetch.js";
import { useAuth } from "@/composables/auth.js";
import { useBaseFetch } from "@/composables/fetch.js";
import { CheckIcon, XIcon } from "@modrinth/assets";
import { Avatar, Button, commonMessages, injectNotificationManager } from "@modrinth/ui";
import { useScopes } from "@/composables/auth/scopes.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const messages = defineMessages({
@@ -196,8 +197,7 @@ const onAuthorize = async () => {
throw new Error(formatMessage(messages.noRedirectUrlError));
} catch {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -223,8 +223,7 @@ const onReject = async () => {
throw new Error(formatMessage(messages.noRedirectUrlError));
} catch {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -67,10 +67,11 @@
</div>
</template>
<script setup>
import { SendIcon, MailIcon, KeyIcon } from "@modrinth/assets";
import { commonMessages } from "@modrinth/ui";
import HCaptcha from "@/components/ui/HCaptcha.vue";
import { KeyIcon, MailIcon, SendIcon } from "@modrinth/assets";
import { commonMessages, injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const methodChoiceMessages = defineMessages({
@@ -179,14 +180,12 @@ async function recovery() {
});
addNotification({
group: "main",
title: formatMessage(emailSentNotificationMessages.title),
text: formatMessage(emailSentNotificationMessages.text),
type: "success",
});
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -211,7 +210,6 @@ async function changePassword() {
});
addNotification({
group: "main",
title: formatMessage(passwordResetNotificationMessages.title),
text: formatMessage(passwordResetNotificationMessages.text),
type: "success",
@@ -219,7 +217,6 @@ async function changePassword() {
await navigateTo("/auth/sign-in");
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
+10 -11
View File
@@ -129,20 +129,21 @@
</template>
<script setup>
import HCaptcha from "@/components/ui/HCaptcha.vue";
import {
RightArrowIcon,
SSOGitHubIcon,
SSOMicrosoftIcon,
SSOSteamIcon,
SSOGoogleIcon,
SSODiscordIcon,
SSOGitLabIcon,
KeyIcon,
MailIcon,
RightArrowIcon,
SSODiscordIcon,
SSOGitHubIcon,
SSOGitLabIcon,
SSOGoogleIcon,
SSOMicrosoftIcon,
SSOSteamIcon,
} from "@modrinth/assets";
import { commonMessages } from "@modrinth/ui";
import HCaptcha from "@/components/ui/HCaptcha.vue";
import { commonMessages, injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const messages = defineMessages({
@@ -232,7 +233,6 @@ async function beginPasswordSignIn() {
}
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -257,7 +257,6 @@ async function begin2FASignIn() {
await finishSignIn(res.session);
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
+10 -11
View File
@@ -133,21 +133,22 @@
</template>
<script setup>
import HCaptcha from "@/components/ui/HCaptcha.vue";
import {
RightArrowIcon,
UserIcon,
SSOGitHubIcon,
SSOMicrosoftIcon,
SSOGoogleIcon,
SSOSteamIcon,
SSODiscordIcon,
KeyIcon,
MailIcon,
RightArrowIcon,
SSODiscordIcon,
SSOGitHubIcon,
SSOGitLabIcon,
SSOGoogleIcon,
SSOMicrosoftIcon,
SSOSteamIcon,
UserIcon,
} from "@modrinth/assets";
import { Checkbox, commonMessages } from "@modrinth/ui";
import HCaptcha from "@/components/ui/HCaptcha.vue";
import { Checkbox, commonMessages, injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const messages = defineMessages({
@@ -225,7 +226,6 @@ async function createAccount() {
try {
if (confirmPassword.value !== password.value) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: formatMessage({
id: "auth.sign-up.notification.password-mismatch.text",
@@ -262,7 +262,6 @@ async function createAccount() {
}
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
+4 -6
View File
@@ -367,6 +367,7 @@ import {
BoxIcon,
CalendarIcon,
EditIcon,
GlobeIcon,
GridIcon,
ImageIcon,
LibraryIcon,
@@ -378,7 +379,6 @@ import {
UpdatedIcon,
UploadIcon,
XIcon,
GlobeIcon,
} from "@modrinth/assets";
import {
Avatar,
@@ -387,17 +387,17 @@ import {
ConfirmModal,
DropdownSelect,
FileInput,
injectNotificationManager,
PopoutMenu,
useRelativeTime,
} from "@modrinth/ui";
import { isAdmin } from "@modrinth/utils";
import UpToDate from "assets/images/illustrations/up_to_date.svg";
import { addNotification } from "~/composables/notifs.js";
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
import NavRow from "~/components/ui/NavRow.vue";
import ProjectCard from "~/components/ui/ProjectCard.vue";
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
const { addNotification } = injectNotificationManager();
const vintl = useVIntl();
const { formatMessage } = vintl;
const formatRelativeTime = useRelativeTime();
@@ -664,7 +664,6 @@ async function saveChanges() {
isEditing.value = false;
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err,
type: "error",
@@ -688,7 +687,6 @@ async function deleteCollection() {
}
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
+1 -1
View File
@@ -99,7 +99,7 @@
import { ChevronRightIcon, HistoryIcon } from "@modrinth/assets";
import { Avatar } from "@modrinth/ui";
import NotificationItem from "~/components/ui/NotificationItem.vue";
import { fetchExtraNotificationData, groupNotifications } from "~/helpers/notifications.ts";
import { fetchExtraNotificationData, groupNotifications } from "~/helpers/platform-notifications";
useHead({
title: "Dashboard - Modrinth",
@@ -63,7 +63,7 @@ import {
fetchExtraNotificationData,
groupNotifications,
markAsRead,
} from "~/helpers/notifications.ts";
} from "~/helpers/platform-notifications";
import NotificationItem from "~/components/ui/NotificationItem.vue";
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
+11 -10
View File
@@ -301,17 +301,16 @@
</template>
<script>
import { Multiselect } from "vue-multiselect";
import {
SettingsIcon,
TrashIcon,
PlusIcon,
XIcon,
IssuesIcon,
EditIcon,
IssuesIcon,
PlusIcon,
SaveIcon,
SettingsIcon,
SortAscIcon,
SortDescIcon,
TrashIcon,
XIcon,
} from "@modrinth/assets";
import {
Avatar,
@@ -320,8 +319,10 @@ import {
CopyCode,
ProjectStatusBadge,
commonMessages,
injectNotificationManager,
} from "@modrinth/ui";
import { formatProjectType } from "@modrinth/utils";
import { Multiselect } from "vue-multiselect";
import Modal from "~/components/ui/Modal.vue";
import ModalCreation from "~/components/ui/ModalCreation.vue";
@@ -444,6 +445,8 @@ export default defineNuxtComponent({
return sortedArray;
},
async bulkEditLinks() {
const { addNotification } = injectNotificationManager();
try {
const baseData = {
issues_url: this.editLinks.issues.clear ? null : this.editLinks.issues.val.trim(),
@@ -477,8 +480,7 @@ export default defineNuxtComponent({
);
this.$refs.editLinksModal.hide();
this.$notify({
group: "main",
addNotification({
title: "Success",
text: "Bulk edited selected project's links.",
type: "success",
@@ -494,8 +496,7 @@ export default defineNuxtComponent({
this.editLinks.wiki.clear = false;
this.editLinks.discord.clear = false;
} catch (e) {
this.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: e,
type: "error",
@@ -149,10 +149,12 @@ import {
UnknownIcon,
XIcon,
} from "@modrinth/assets";
import { injectNotificationManager } from "@modrinth/ui";
import { formatDate } from "@modrinth/utils";
import dayjs from "dayjs";
import { computed } from "vue";
const { addNotification } = injectNotificationManager();
const auth = await useAuth();
const minWithdraw = ref(0.01);
@@ -201,9 +203,7 @@ async function updateVenmo() {
});
await useAuth(auth.value.token);
} catch (err) {
const data = useNuxtApp();
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -94,13 +94,14 @@
</div>
</template>
<script setup>
import { XIcon, PayPalIcon, UnknownIcon } from "@modrinth/assets";
import { PayPalIcon, UnknownIcon, XIcon } from "@modrinth/assets";
import { Badge, Breadcrumbs, DropdownSelect, injectNotificationManager } from "@modrinth/ui";
import { capitalizeString, formatWallet } from "@modrinth/utils";
import { Badge, Breadcrumbs, DropdownSelect } from "@modrinth/ui";
import dayjs from "dayjs";
import TremendousIcon from "~/assets/images/external/tremendous.svg?component";
import VenmoIcon from "~/assets/images/external/venmo-small.svg?component";
const { addNotification } = injectNotificationManager();
const vintl = useVIntl();
const { formatMessage } = vintl;
@@ -155,8 +156,7 @@ async function cancelPayout(id) {
await refresh();
await useAuth(auth.value.token);
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -187,20 +187,21 @@
</template>
<script setup>
import { Multiselect } from "vue-multiselect";
import {
PayPalIcon,
SearchIcon,
RadioButtonIcon,
RadioButtonCheckedIcon,
XIcon,
RadioButtonIcon,
SearchIcon,
TransferIcon,
XIcon,
} from "@modrinth/assets";
import { Chips, Checkbox, Breadcrumbs } from "@modrinth/ui";
import { all } from "iso-3166-1";
import { Breadcrumbs, Checkbox, Chips, injectNotificationManager } from "@modrinth/ui";
import { formatMoney, formatWallet } from "@modrinth/utils";
import { all } from "iso-3166-1";
import { Multiselect } from "vue-multiselect";
import VenmoIcon from "~/assets/images/external/venmo.svg?component";
const { addNotification } = injectNotificationManager();
const auth = await useAuth();
const data = useNuxtApp();
@@ -355,8 +356,7 @@ async function withdraw() {
});
await useAuth(auth.value.token);
await navigateTo("/dashboard/revenue");
data.$notify({
group: "main",
addNotification({
title: "Withdrawal complete",
text:
selectedMethod.value.type === "tremendous"
@@ -365,8 +365,7 @@ async function withdraw() {
type: "success",
});
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1,7 +1,8 @@
<script setup>
import { Button, FileInput, Avatar, ConfirmModal } from "@modrinth/ui";
import { UploadIcon, SaveIcon, TrashIcon } from "@modrinth/assets";
import { SaveIcon, TrashIcon, UploadIcon } from "@modrinth/assets";
import { Avatar, Button, ConfirmModal, FileInput, injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const {
organization,
refresh: refreshOrganization,
@@ -74,7 +75,6 @@ const onSaveChanges = useClientTry(async () => {
await refreshOrganization();
addNotification({
group: "main",
title: "Organization updated",
text: "Your organization has been updated.",
type: "success",
@@ -88,7 +88,6 @@ const onDeleteOrganization = useClientTry(async () => {
});
addNotification({
group: "main",
title: "Organization deleted",
text: "Your organization has been deleted.",
type: "success",
@@ -220,18 +220,19 @@
<script setup>
import {
CrownIcon,
DropdownIcon,
SaveIcon,
TransferIcon,
UserPlusIcon,
UserXIcon as UserRemoveIcon,
DropdownIcon,
CrownIcon,
} from "@modrinth/assets";
import { Button, Badge, Avatar, Checkbox } from "@modrinth/ui";
import { Avatar, Badge, Button, Checkbox, injectNotificationManager } from "@modrinth/ui";
import { ref } from "vue";
import { removeTeamMember } from "~/helpers/teams.js";
import { isPermission } from "~/utils/permissions.ts";
const { addNotification } = injectNotificationManager();
const { organization, refresh: refreshOrganization, currentMember } = inject("organizationContext");
const auth = await useAuth();
@@ -296,7 +297,6 @@ const onInviteTeamMember = useClientTry(async (teamId, username) => {
await refreshOrganization();
currentUsername.value = "";
addNotification({
group: "main",
title: "Member invited",
text: `${user.username} has been invited to the organization.`,
type: "success",
@@ -307,7 +307,6 @@ const onRemoveMember = useClientTry(async (teamId, member) => {
await removeTeamMember(teamId, member.user.id);
await refreshOrganization();
addNotification({
group: "main",
title: "Member removed",
text: `${member.user.username} has been removed from the organization.`,
type: "success",
@@ -332,7 +331,6 @@ const onUpdateTeamMember = useClientTry(async (teamId, member) => {
});
await refreshOrganization();
addNotification({
group: "main",
title: "Member updated",
text: `${member.user.username} has been updated.`,
type: "success",
@@ -349,7 +347,6 @@ const onTransferOwnership = useClientTry(async (teamId, uid) => {
});
await refreshOrganization();
addNotification({
group: "main",
title: "Ownership transferred",
text: `The ownership of ${organization.value.name} has been successfully transferred.`,
type: "success",
@@ -298,25 +298,34 @@
</template>
<script setup>
import { Multiselect } from "vue-multiselect";
import {
BoxIcon,
SettingsIcon,
TrashIcon,
EditIcon,
IssuesIcon,
PlusIcon,
XIcon,
EditIcon,
SaveIcon,
SettingsIcon,
SortAscIcon,
SortDescIcon,
TrashIcon,
XIcon,
} from "@modrinth/assets";
import { Button, Modal, Avatar, CopyCode, Badge, Checkbox, commonMessages } from "@modrinth/ui";
import {
Avatar,
Badge,
Button,
Checkbox,
commonMessages,
CopyCode,
injectNotificationManager,
Modal,
} from "@modrinth/ui";
import { formatProjectType } from "@modrinth/utils";
import { Multiselect } from "vue-multiselect";
import ModalCreation from "~/components/ui/ModalCreation.vue";
import OrganizationProjectTransferModal from "~/components/ui/OrganizationProjectTransferModal.vue";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const { organization, projects, refresh } = inject("organizationContext");
@@ -375,14 +384,12 @@ const onProjectTransferSubmit = async (projects) => {
await refreshUserProjects();
addNotification({
group: "main",
title: "Success",
text: "Transferred selected projects to organization.",
type: "success",
});
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -511,7 +518,6 @@ const onBulkEditLinks = useClientTry(async () => {
editLinksModal.value.hide();
addNotification({
group: "main",
title: "Success",
text: "Bulk edited selected project's links.",
type: "success",
+6 -5
View File
@@ -11,8 +11,7 @@
:fetch-payment-data="fetchPaymentData"
:on-error="
(err) =>
data.$notify({
group: 'main',
addNotification({
title: 'An error occurred',
type: 'error',
text: err.message ?? (err.data ? err.data.description : err),
@@ -87,16 +86,18 @@
</template>
<script setup>
import {
ModrinthPlusIcon,
HeartIcon,
ModrinthPlusIcon,
SettingsIcon,
SparklesIcon,
StarIcon,
SettingsIcon,
} from "@modrinth/assets";
import { PurchaseModal } from "@modrinth/ui";
import { injectNotificationManager, PurchaseModal } from "@modrinth/ui";
import { calculateSavings, formatPrice, getCurrency } from "@modrinth/utils";
import { products } from "~/generated/state.json";
const { addNotification } = injectNotificationManager();
const title = "Subscribe to Modrinth Plus!";
const description =
"Subscribe to Modrinth Plus to go ad-free, support Modrinth's development, and get an exclusive profile badge! Half your subscription goes directly to Modrinth creators.";
+21 -20
View File
@@ -243,30 +243,33 @@
<script setup lang="ts">
import {
CheckCircleIcon,
CheckIcon,
ExternalIcon,
IssuesIcon,
LeftArrowIcon,
RightArrowIcon,
ScaleIcon,
SendIcon,
SpinnerIcon,
VersionIcon,
XCircleIcon,
} from "@modrinth/assets";
import {
AutoLink,
Avatar,
ButtonStyled,
injectNotificationManager,
MarkdownEditor,
RadialHeader,
RadioButtons,
ButtonStyled,
Avatar,
AutoLink,
} from "@modrinth/ui";
import {
ExternalIcon,
LeftArrowIcon,
RightArrowIcon,
CheckIcon,
SpinnerIcon,
SendIcon,
IssuesIcon,
CheckCircleIcon,
XCircleIcon,
ScaleIcon,
VersionIcon,
} from "@modrinth/assets";
import type { User, Version, Report } from "@modrinth/utils";
import { useVIntl, defineMessages, type MessageDescriptor } from "@vintl/vintl";
import type { Project, Report, User, Version } from "@modrinth/utils";
import { defineMessages, useVIntl, type MessageDescriptor } from "@vintl/vintl";
import { useImageUpload } from "~/composables/image-upload.ts";
const { addNotification } = injectNotificationManager();
const tags = useTags();
const route = useNativeRoute();
const router = useRouter();
@@ -439,7 +442,6 @@ const submitReport = async () => {
if (error instanceof Error) {
addNotification({
group: "main",
title: "An error occurred",
text: error.message,
type: "error",
@@ -465,7 +467,6 @@ const submitReport = async () => {
if (error instanceof Error) {
addNotification({
group: "main",
title: "An error occurred",
text: error.message,
type: "error",
+11 -10
View File
@@ -632,26 +632,31 @@
</template>
<script setup>
import { ButtonStyled, ModrinthServersPurchaseModal } from "@modrinth/ui";
import {
BoxIcon,
GameIcon,
RightArrowIcon,
ServerIcon,
TerminalSquareIcon,
TransferIcon,
VersionIcon,
ServerIcon,
} from "@modrinth/assets";
import { computed } from "vue";
import {
ButtonStyled,
injectNotificationManager,
ModrinthServersPurchaseModal,
} from "@modrinth/ui";
import { monthsInInterval } from "@modrinth/ui/src/utils/billing.ts";
import { formatPrice } from "@modrinth/utils";
import { useVIntl } from "@vintl/vintl";
import { products } from "~/generated/state.json";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import { computed } from "vue";
import OptionGroup from "~/components/ui/OptionGroup.vue";
import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue";
import ServerPlanSelector from "~/components/ui/servers/marketing/ServerPlanSelector.vue";
import OptionGroup from "~/components/ui/OptionGroup.vue";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import { products } from "~/generated/state.json";
const { addNotification } = injectNotificationManager();
const { locale } = useVIntl();
const billingPeriods = ref(["monthly", "quarterly"]);
@@ -812,7 +817,6 @@ const startTyping = () => {
const handleError = (err) => {
addNotification({
group: "main",
title: "An error occurred",
type: "error",
text: err.message ?? (err.data ? err.data.description : err),
@@ -831,7 +835,6 @@ async function fetchPaymentData() {
} catch (error) {
console.error("Error fetching payment data:", error);
addNotification({
group: "main",
title: "Error fetching payment data",
type: "error",
text: error.message || "An unexpected error occurred",
@@ -886,7 +889,6 @@ const selectProduct = async (product) => {
if ((product === "custom" && isCustomAtCapacity.value) || isAtCapacity.value) {
addNotification({
group: "main",
title: "Server Capacity Full",
type: "error",
text: "We are currently at capacity. Please try again later.",
@@ -902,7 +904,6 @@ const selectProduct = async (product) => {
(product !== "custom" && !selectedPlan.metadata)
) {
addNotification({
group: "main",
title: "Invalid product",
type: "error",
text: "The selected product was found but lacks necessary data. Please contact support.",
+23 -20
View File
@@ -350,38 +350,43 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, type Reactive } from "vue";
import { reloadNuxtApp } from "#app";
import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
import {
SettingsIcon,
CheckIcon,
CopyIcon,
FileIcon,
IssuesIcon,
LeftArrowIcon,
RightArrowIcon,
CheckIcon,
FileIcon,
TransferIcon,
LockIcon,
RightArrowIcon,
SettingsIcon,
TransferIcon,
} from "@modrinth/assets";
import DOMPurify from "dompurify";
import { ButtonStyled, ErrorInformationCard, ServerNotice } from "@modrinth/ui";
import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
import type { MessageDescriptor } from "@vintl/vintl";
import {
ButtonStyled,
ErrorInformationCard,
injectNotificationManager,
ServerNotice,
} from "@modrinth/ui";
import {
type Backup,
type PowerAction,
type ServerState,
type Stats,
type WSEvent,
type WSInstallationResultEvent,
type Backup,
type PowerAction,
} from "@modrinth/utils";
import { reloadNuxtApp } from "#app";
import { useModrinthServersConsole } from "~/store/console.ts";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import { ModrinthServer, useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
import type { MessageDescriptor } from "@vintl/vintl";
import DOMPurify from "dompurify";
import { computed, onMounted, onUnmounted, ref, type Reactive } from "vue";
import ServerInstallation from "~/components/ui/servers/ServerInstallation.vue";
import PanelErrorIcon from "~/components/ui/servers/icons/PanelErrorIcon.vue";
import { ModrinthServer, useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import { useModrinthServersConsole } from "~/store/console.ts";
const app = useNuxtApp() as unknown as { $notify: any };
const { addNotification } = injectNotificationManager();
const socket = ref<WebSocket | null>(null);
const isReconnecting = ref(false);
@@ -964,7 +969,6 @@ const sendPowerAction = async (action: PowerAction) => {
const notifyError = (title: string, text: string) => {
addNotification({
group: "server",
title,
text,
type: "error",
@@ -1149,8 +1153,7 @@ async function dismissNotice(noticeId: number) {
await useServersFetch(`servers/${serverId}/notices/${noticeId}/dismiss`, {
method: "POST",
}).catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error dismissing notice",
text: err,
type: "error",
@@ -150,19 +150,20 @@
</template>
<script setup lang="ts">
import { ButtonStyled, TagItem } from "@modrinth/ui";
import { useStorage } from "@vueuse/core";
import { SpinnerIcon, PlusIcon, DownloadIcon, SettingsIcon, IssuesIcon } from "@modrinth/assets";
import { ref, computed } from "vue";
import { DownloadIcon, IssuesIcon, PlusIcon, SettingsIcon, SpinnerIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager, TagItem } from "@modrinth/ui";
import type { Backup } from "@modrinth/utils";
import { useStorage } from "@vueuse/core";
import { computed, ref } from "vue";
import BackupCreateModal from "~/components/ui/servers/BackupCreateModal.vue";
import BackupDeleteModal from "~/components/ui/servers/BackupDeleteModal.vue";
import BackupItem from "~/components/ui/servers/BackupItem.vue";
import BackupRenameModal from "~/components/ui/servers/BackupRenameModal.vue";
import BackupCreateModal from "~/components/ui/servers/BackupCreateModal.vue";
import BackupRestoreModal from "~/components/ui/servers/BackupRestoreModal.vue";
import BackupDeleteModal from "~/components/ui/servers/BackupDeleteModal.vue";
import BackupSettingsModal from "~/components/ui/servers/BackupSettingsModal.vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
isServerRunning: boolean;
@@ -236,7 +237,11 @@ const prepareDownload = async (backupId: string) => {
await props.server.backups?.prepare(backupId);
} catch (error) {
console.error("Failed to prepare download:", error);
addNotification({ type: "error", title: "Failed to prepare backup for download", text: error });
addNotification({
type: "error",
title: "Failed to prepare backup for download",
text: error as string,
});
}
};
@@ -335,28 +335,29 @@
<script setup lang="ts">
import {
SearchIcon,
EditIcon,
TrashIcon,
PackageClosedIcon,
FilterIcon,
DropdownIcon,
PlusIcon,
MoreVerticalIcon,
CompassIcon,
WrenchIcon,
ListIcon,
DropdownIcon,
EditIcon,
FileIcon,
FilterIcon,
IssuesIcon,
ListIcon,
MoreVerticalIcon,
PackageClosedIcon,
PlusIcon,
SearchIcon,
TrashIcon,
WrenchIcon,
} from "@modrinth/assets";
import { Avatar, ButtonStyled } from "@modrinth/ui";
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
import { Avatar, ButtonStyled, injectNotificationManager } from "@modrinth/ui";
import type { Mod } from "@modrinth/utils";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import FilesUploadDragAndDrop from "~/components/ui/servers/FilesUploadDragAndDrop.vue";
import FilesUploadDropdown from "~/components/ui/servers/FilesUploadDropdown.vue";
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -266,26 +266,27 @@
</template>
<script setup lang="ts">
import { useInfiniteScroll } from "@vueuse/core";
import {
UnknownIcon,
XIcon,
SpinnerIcon,
PackageOpenIcon,
CheckIcon,
UploadIcon,
FolderOpenIcon,
PackageOpenIcon,
SpinnerIcon,
UnknownIcon,
UploadIcon,
XIcon,
} from "@modrinth/assets";
import { computed } from "vue";
import { ButtonStyled, ProgressBar } from "@modrinth/ui";
import { ButtonStyled, injectNotificationManager, ProgressBar } from "@modrinth/ui";
import type { DirectoryItem, DirectoryResponse, FilesystemOp, FSQueuedOp } from "@modrinth/utils";
import { formatBytes, ModrinthServersFetchError } from "@modrinth/utils";
import type { FilesystemOp, FSQueuedOp, DirectoryItem, DirectoryResponse } from "@modrinth/utils";
import { handleError, ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
import { useInfiniteScroll } from "@vueuse/core";
import { computed } from "vue";
import FilesUploadConflictModal from "~/components/ui/servers/FilesUploadConflictModal.vue";
import FilesUploadDragAndDrop from "~/components/ui/servers/FilesUploadDragAndDrop.vue";
import FilesUploadDropdown from "~/components/ui/servers/FilesUploadDropdown.vue";
import FilesUploadZipUrlModal from "~/components/ui/servers/FilesUploadZipUrlModal.vue";
import FilesUploadConflictModal from "~/components/ui/servers/FilesUploadConflictModal.vue";
import { handleError, ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const flags = useFeatureFlags();
const baseId = useId();
@@ -449,7 +450,6 @@ const undoLastOperation = async () => {
refreshList();
addNotification({
group: "files",
title: `${lastOperation.type === "move" ? "Move" : "Rename"} undone`,
text: `${lastOperation.fileName} has been restored to its original ${lastOperation.type === "move" ? "location" : "name"}`,
type: "success",
@@ -457,7 +457,6 @@ const undoLastOperation = async () => {
} catch (error) {
console.error(`Error undoing ${lastOperation.type}:`, error);
addNotification({
group: "files",
title: "Undo failed",
text: `Failed to undo the last ${lastOperation.type} operation`,
type: "error",
@@ -489,7 +488,6 @@ const redoLastOperation = async () => {
refreshList();
addNotification({
group: "files",
title: `${lastOperation.type === "move" ? "Move" : "Rename"} redone`,
text: `${lastOperation.fileName} has been ${lastOperation.type === "move" ? "moved" : "renamed"} again`,
type: "success",
@@ -497,7 +495,6 @@ const redoLastOperation = async () => {
} catch (error) {
console.error(`Error redoing ${lastOperation.type}:`, error);
addNotification({
group: "files",
title: "Redo failed",
text: `Failed to redo the last ${lastOperation.type} operation`,
type: "error",
@@ -513,7 +510,6 @@ const handleCreateNewItem = async (name: string) => {
refreshList();
addNotification({
group: "files",
title: `${newItemType.value === "directory" ? "Folder" : "File"} created`,
text: `New ${newItemType.value === "directory" ? "folder" : "file"} ${name} has been created.`,
type: "success",
@@ -549,7 +545,6 @@ const handleRenameItem = async (newName: string) => {
}
addNotification({
group: "files",
title: `${selectedItem.value.type === "directory" ? "Folder" : "File"} renamed`,
text: `${selectedItem.value.name} has been renamed to ${newName}`,
type: "success",
@@ -559,7 +554,6 @@ const handleRenameItem = async (newName: string) => {
if (error instanceof ModrinthServersFetchError) {
if (error.statusCode === 400) {
addNotification({
group: "files",
title: "Could not rename",
text: `An item named "${newName}" already exists in this location`,
type: "error",
@@ -567,7 +561,6 @@ const handleRenameItem = async (newName: string) => {
return;
}
addNotification({
group: "files",
title: "Could not rename item",
text: "An unexpected error occurred",
type: "error",
@@ -625,7 +618,6 @@ const handleMoveItem = async (destination: string) => {
refreshList();
addNotification({
group: "files",
title: `${itemType === "directory" ? "Folder" : "File"} moved`,
text: `${selectedItem.value.name} has been moved to ${newPath}`,
type: "success",
@@ -658,7 +650,6 @@ const handleDirectMove = async (moveData: {
refreshList();
addNotification({
group: "files",
title: `${moveData.type === "directory" ? "Folder" : "File"} moved`,
text: `${moveData.name} has been moved to ${newPath}`,
type: "success",
@@ -675,7 +666,6 @@ const handleDeleteItem = async () => {
refreshList();
addNotification({
group: "files",
title: "File deleted",
text: "Your file has been deleted.",
type: "success",
@@ -717,14 +707,12 @@ const handleCreateError = (error: any) => {
if (error instanceof ModrinthServersFetchError) {
if (error.statusCode === 400) {
addNotification({
group: "files",
title: "Error creating item",
text: "Invalid file",
type: "error",
});
} else if (error.statusCode === 500) {
addNotification({
group: "files",
title: "Error creating item",
text: "Something went wrong. The file may already exist.",
type: "error",
@@ -1010,7 +998,6 @@ const requestShareLink = async () => {
if (response.success) {
await navigator.clipboard.writeText(response.url);
addNotification({
group: "files",
title: "Log URL copied",
text: "Your log file URL has been copied to your clipboard.",
type: "success",
@@ -1080,7 +1067,6 @@ const saveFileContent = async (exit: boolean = true) => {
}
addNotification({
group: "files",
title: "File saved",
text: "Your file has been saved.",
type: "success",
@@ -1095,7 +1081,6 @@ const saveFileContentRestart = async () => {
await props.server.general?.power("Restart");
addNotification({
group: "files",
title: "Server restarted",
text: "Your server has been restarted.",
type: "success",
@@ -113,9 +113,11 @@
<script setup lang="ts">
import { EditIcon, TransferIcon } from "@modrinth/assets";
import { injectNotificationManager } from "@modrinth/ui";
import ButtonStyled from "@modrinth/ui/src/components/base/ButtonStyled.vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -161,7 +163,6 @@ const saveGeneral = async () => {
if (!available) {
addNotification({
group: "serverOptions",
type: "error",
title: "Subdomain not available",
text: "The subdomain you entered is already in use.",
@@ -173,7 +174,6 @@ const saveGeneral = async () => {
} catch (error) {
console.error("Error checking subdomain availability:", error);
addNotification({
group: "serverOptions",
type: "error",
title: "Error checking availability",
text: "Failed to verify if the subdomain is available.",
@@ -184,7 +184,6 @@ const saveGeneral = async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
await props.server.refresh();
addNotification({
group: "serverOptions",
type: "success",
title: "Server settings updated",
text: "Your server settings were successfully changed.",
@@ -192,7 +191,6 @@ const saveGeneral = async () => {
} catch (error) {
console.error(error);
addNotification({
group: "serverOptions",
type: "error",
title: "Failed to update server settings",
text: "An error occurred while attempting to update your server settings.",
@@ -211,7 +209,6 @@ const uploadFile = async (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (!file) {
addNotification({
group: "serverOptions",
type: "error",
title: "No file selected",
text: "Please select a file to upload.",
@@ -267,7 +264,6 @@ const uploadFile = async (e: Event) => {
});
addNotification({
group: "serverOptions",
type: "success",
title: "Server icon updated",
text: "Your server icon was successfully changed.",
@@ -275,7 +271,6 @@ const uploadFile = async (e: Event) => {
} catch (error) {
console.error("Error uploading icon:", error);
addNotification({
group: "serverOptions",
type: "error",
title: "Upload failed",
text: "Failed to upload server icon.",
@@ -295,7 +290,6 @@ const resetIcon = async () => {
await props.server.refresh(["general"]);
addNotification({
group: "serverOptions",
type: "success",
title: "Server icon reset",
text: "Your server icon was successfully reset.",
@@ -303,7 +297,6 @@ const resetIcon = async () => {
} catch (error) {
console.error("Error resetting icon:", error);
addNotification({
group: "serverOptions",
type: "error",
title: "Reset failed",
text: "Failed to reset server icon.",
@@ -115,10 +115,11 @@
</template>
<script setup lang="ts">
import { ButtonStyled, CopyCode } from "@modrinth/ui";
import { CopyIcon, ExternalIcon, EyeIcon, EyeOffIcon } from "@modrinth/assets";
import { ButtonStyled, CopyCode, injectNotificationManager } from "@modrinth/ui";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -264,19 +264,26 @@
<script setup lang="ts">
import {
PlusIcon,
TrashIcon,
EditIcon,
VersionIcon,
SaveIcon,
InfoIcon,
UploadIcon,
IssuesIcon,
PlusIcon,
SaveIcon,
TrashIcon,
UploadIcon,
VersionIcon,
} from "@modrinth/assets";
import { ButtonStyled, NewModal, ConfirmModal, CopyCode } from "@modrinth/ui";
import { ref, computed, nextTick } from "vue";
import {
ButtonStyled,
ConfirmModal,
CopyCode,
injectNotificationManager,
NewModal,
} from "@modrinth/ui";
import { computed, nextTick, ref } from "vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -317,7 +324,6 @@ const addNewAllocation = async () => {
newAllocationName.value = "";
addNotification({
group: "serverOptions",
type: "success",
title: "Allocation reserved",
text: "Your allocation has been reserved.",
@@ -359,7 +365,6 @@ const confirmDeleteAllocation = async () => {
await props.server.refresh(["network"]);
addNotification({
group: "serverOptions",
type: "success",
title: "Allocation removed",
text: "Your allocation has been removed.",
@@ -379,7 +384,6 @@ const editAllocation = async () => {
newAllocationName.value = "";
addNotification({
group: "serverOptions",
type: "success",
title: "Allocation updated",
text: "Your allocation has been updated.",
@@ -397,7 +401,6 @@ const saveNetwork = async () => {
const available = await props.server.network?.checkSubdomainAvailability(serverSubdomain.value);
if (!available) {
addNotification({
group: "serverOptions",
type: "error",
title: "Subdomain not available",
text: "The subdomain you entered is already in use.",
@@ -416,7 +419,6 @@ const saveNetwork = async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
await props.server.refresh();
addNotification({
group: "serverOptions",
type: "success",
title: "Server settings updated",
text: "Your server settings were successfully changed.",
@@ -424,7 +426,6 @@ const saveNetwork = async () => {
} catch (error) {
console.error(error);
addNotification({
group: "serverOptions",
type: "error",
title: "Failed to update server settings",
text: "An error occurred while attempting to update your server settings.",
@@ -483,7 +484,6 @@ const exportDnsRecords = () => {
const copyText = (text: string) => {
navigator.clipboard.writeText(text);
addNotification({
group: "serverOptions",
type: "success",
title: "Text copied",
text: `${text} has been copied to your clipboard`,
@@ -42,9 +42,11 @@
</template>
<script setup lang="ts">
import { injectNotificationManager } from "@modrinth/ui";
import { useStorage } from "@vueuse/core";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const route = useNativeRoute();
const serverId = route.params.id as string;
@@ -109,7 +111,6 @@ const hasUnsavedChanges = computed(() => {
const savePreferences = () => {
userPreferences.value = { ...newUserPreferences.value };
addNotification({
group: "serverOptions",
type: "success",
title: "Preferences saved",
text: "Your preferences have been saved.",
@@ -143,11 +143,13 @@
</template>
<script setup lang="ts">
import { ref, watch, computed, inject } from "vue";
import { EyeIcon, SearchIcon, IssuesIcon } from "@modrinth/assets";
import { EyeIcon, IssuesIcon, SearchIcon } from "@modrinth/assets";
import { injectNotificationManager } from "@modrinth/ui";
import Fuse from "fuse.js";
import { computed, inject, ref, watch } from "vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -281,7 +283,6 @@ const saveProperties = async () => {
originalProperties.value = JSON.parse(JSON.stringify(liveProperties.value));
await props.server.refresh();
addNotification({
group: "serverOptions",
type: "success",
title: "Server properties updated",
text: "Your server properties were successfully changed.",
@@ -289,7 +290,6 @@ const saveProperties = async () => {
} catch (error) {
console.error("Error updating server properties:", error);
addNotification({
group: "serverOptions",
type: "error",
title: "Failed to update server properties",
text: "An error occurred while attempting to update your server properties.",
@@ -112,10 +112,11 @@
</template>
<script setup lang="ts">
import { UpdatedIcon, IssuesIcon } from "@modrinth/assets";
import { ButtonStyled } from "@modrinth/ui";
import { IssuesIcon, UpdatedIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager } from "@modrinth/ui";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -216,7 +217,6 @@ const saveStartup = async () => {
}
addNotification({
group: "serverOptions",
type: "success",
title: "Server settings updated",
text: "Your server settings were successfully changed.",
@@ -224,7 +224,6 @@ const saveStartup = async () => {
} catch (error) {
console.error(error);
addNotification({
group: "serverOptions",
type: "error",
title: "Failed to update server arguments",
text: "Please try again later.",
+14 -19
View File
@@ -408,6 +408,7 @@
<script setup>
import {
CheckIcon,
DownloadIcon,
EditIcon,
ExternalIcon,
LeftArrowIcon,
@@ -418,17 +419,16 @@ import {
TrashIcon,
UpdatedIcon,
XIcon,
DownloadIcon,
} from "@modrinth/assets";
import QrcodeVue from "qrcode.vue";
import { ConfirmModal } from "@modrinth/ui";
import GithubIcon from "assets/icons/auth/sso-github.svg";
import MicrosoftIcon from "assets/icons/auth/sso-microsoft.svg";
import GoogleIcon from "assets/icons/auth/sso-google.svg";
import SteamIcon from "assets/icons/auth/sso-steam.svg";
import DiscordIcon from "assets/icons/auth/sso-discord.svg";
import { ConfirmModal, injectNotificationManager } from "@modrinth/ui";
import KeyIcon from "assets/icons/auth/key.svg";
import DiscordIcon from "assets/icons/auth/sso-discord.svg";
import GithubIcon from "assets/icons/auth/sso-github.svg";
import GitLabIcon from "assets/icons/auth/sso-gitlab.svg";
import GoogleIcon from "assets/icons/auth/sso-google.svg";
import MicrosoftIcon from "assets/icons/auth/sso-microsoft.svg";
import SteamIcon from "assets/icons/auth/sso-steam.svg";
import QrcodeVue from "qrcode.vue";
import Modal from "~/components/ui/Modal.vue";
useHead({
@@ -439,7 +439,7 @@ definePageMeta({
middleware: "auth",
});
const data = useNuxtApp();
const { addNotification } = injectNotificationManager();
const auth = await useAuth();
const changeEmailModal = ref();
@@ -460,8 +460,7 @@ async function saveEmail() {
changeEmailModal.value.hide();
await useAuth(auth.value.token);
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -492,8 +491,7 @@ async function savePassword() {
managePasswordModal.value.hide();
await useAuth(auth.value.token);
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -529,8 +527,7 @@ async function showTwoFactorModal() {
twoFactorSecret.value = res.secret;
twoFactorFlow.value = res.flow;
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -619,8 +616,7 @@ async function deleteAccount() {
method: "DELETE",
});
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -649,8 +645,7 @@ async function exportData() {
const blob = new Blob([jsonString], { type: "application/json" });
generated.value = URL.createObjectURL(blob);
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -215,26 +215,27 @@
</div>
</template>
<script setup>
import { UploadIcon, PlusIcon, XIcon, TrashIcon, EditIcon, SaveIcon } from "@modrinth/assets";
import { EditIcon, PlusIcon, SaveIcon, TrashIcon, UploadIcon, XIcon } from "@modrinth/assets";
import {
CopyCode,
ConfirmModal,
Avatar,
Button,
Checkbox,
Avatar,
ConfirmModal,
CopyCode,
FileInput,
commonSettingsMessages,
injectNotificationManager,
} from "@modrinth/ui";
import Modal from "~/components/ui/Modal.vue";
import {
scopeList,
getScopeValue,
hasScope,
scopeList,
toggleScope,
useScopes,
getScopeValue,
} from "~/composables/auth/scopes.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
definePageMeta({
@@ -343,8 +344,7 @@ async function onImageSelection(files) {
setForm(app);
}
data.$notify({
group: "main",
addNotification({
title: "Icon updated",
text: "Your application icon has been updated.",
type: "success",
@@ -374,8 +374,7 @@ async function createApp() {
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -443,8 +442,7 @@ async function editApp() {
appModal.value.hide();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -467,8 +465,7 @@ async function removeApp() {
await refresh();
editingId.value = null;
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -88,10 +88,17 @@
</div>
</template>
<script setup>
import { Button, ConfirmModal, Avatar, commonSettingsMessages } from "@modrinth/ui";
import { TrashIcon, CheckIcon } from "@modrinth/assets";
import { CheckIcon, TrashIcon } from "@modrinth/assets";
import {
Avatar,
Button,
commonSettingsMessages,
ConfirmModal,
injectNotificationManager,
} from "@modrinth/ui";
import { useScopes } from "~/composables/auth/scopes.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const { scopesToDefinitions } = useScopes();
@@ -163,8 +170,7 @@ async function revokeApp(id) {
revokingId.value = null;
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -392,8 +392,7 @@
"
:on-error="
(err) =>
data.$notify({
group: 'main',
addNotification({
title: 'An error occurred',
type: 'error',
text: err.message ?? (err.data ? err.data.description : err),
@@ -422,8 +421,7 @@
:renewal-date="currentSubRenewalDate"
:on-error="
(err) =>
data.$notify({
group: 'main',
addNotification({
title: 'An error occurred',
type: 'error',
text: err.message ?? (err.data ? err.data.description : err),
@@ -554,43 +552,44 @@
<script setup>
import {
ConfirmModal,
ArrowBigUpDashIcon,
CardIcon,
CheckCircleIcon,
CurrencyIcon,
EditIcon,
HistoryIcon,
ModrinthPlusIcon,
MoreVerticalIcon,
PayPalIcon,
PlusIcon,
RightArrowIcon,
SpinnerIcon,
StarIcon,
TransferIcon,
TrashIcon,
UpdatedIcon,
XIcon,
} from "@modrinth/assets";
import {
AddPaymentMethodModal,
ButtonStyled,
ConfirmModal,
CopyCode,
OverflowMenu,
PurchaseModal,
ButtonStyled,
CopyCode,
commonMessages,
injectNotificationManager,
} from "@modrinth/ui";
import {
PlusIcon,
TransferIcon,
SpinnerIcon,
ArrowBigUpDashIcon,
XIcon,
CardIcon,
MoreVerticalIcon,
TrashIcon,
EditIcon,
StarIcon,
PayPalIcon,
CurrencyIcon,
CheckCircleIcon,
RightArrowIcon,
ModrinthPlusIcon,
UpdatedIcon,
HistoryIcon,
} from "@modrinth/assets";
import { calculateSavings, formatPrice, getCurrency } from "@modrinth/utils";
import { ref, computed } from "vue";
import { computed, ref } from "vue";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import { products } from "~/generated/state.json";
const { addNotification } = injectNotificationManager();
definePageMeta({
middleware: "auth",
});
const app = useNuxtApp();
const auth = await useAuth();
const baseId = useId();
@@ -845,8 +844,7 @@ async function editPaymentMethod(index, primary) {
});
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -864,8 +862,7 @@ async function removePaymentMethod(index) {
});
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -887,8 +884,7 @@ async function cancelSubscription(id, cancelled) {
});
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -955,8 +951,7 @@ const showPyroUpgradeModal = async (subscription) => {
if (!currentProduct.value) {
console.error("Could not find product for current subscription");
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: "Could not find product for current subscription",
type: "error",
@@ -988,8 +983,7 @@ async function fetchCapacityStatuses(serverId, product) {
};
} catch (error) {
console.error("Error checking server capacities:", error);
app.$notify({
group: "main",
addNotification({
title: "Error checking server capacities",
text: error,
type: "error",
@@ -1015,23 +1009,20 @@ const resubscribePyro = async (subscriptionId, wasSuspended) => {
});
await refresh();
if (wasSuspended) {
data.$notify({
group: "main",
addNotification({
title: "Resubscription request submitted",
text: "If the server is currently suspended, it may take up to 10 minutes for another charge attempt to be made.",
type: "success",
});
} else {
data.$notify({
group: "main",
addNotification({
title: "Success",
text: "Server subscription resubscribed successfully",
type: "success",
});
}
} catch {
data.$notify({
group: "main",
addNotification({
title: "Error resubscribing",
text: "An error occurred while resubscribing to your Modrinth server.",
type: "error",
+2 -2
View File
@@ -199,7 +199,7 @@
<script setup lang="ts">
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from "@modrinth/assets";
import { Button, ThemeSelector } from "@modrinth/ui";
import { Button, injectNotificationManager, ThemeSelector } from "@modrinth/ui";
import { formatProjectType } from "@modrinth/utils";
import MessageBanner from "~/components/ui/MessageBanner.vue";
import type { DisplayLocation } from "~/plugins/cosmetics";
@@ -209,6 +209,7 @@ useHead({
title: "Display settings - Modrinth",
});
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const developerModeBanner = defineMessages({
@@ -377,7 +378,6 @@ function disableDeveloperMode() {
flags.value.developerMode = !flags.value.developerMode;
saveFeatureFlags();
addNotification({
group: "main",
title: "Developer mode deactivated",
text: "Developer mode has been disabled",
type: "success",
+10 -13
View File
@@ -202,26 +202,26 @@
</div>
</template>
<script setup>
import { PlusIcon, XIcon, TrashIcon, EditIcon, SaveIcon } from "@modrinth/assets";
import { EditIcon, PlusIcon, SaveIcon, TrashIcon, XIcon } from "@modrinth/assets";
import {
Checkbox,
CopyCode,
ConfirmModal,
commonSettingsMessages,
CopyCode,
commonMessages,
commonSettingsMessages,
injectNotificationManager,
useRelativeTime,
} from "@modrinth/ui";
import Modal from "~/components/ui/Modal.vue";
import {
getScopeValue,
hasScope,
scopeList,
toggleScope,
useScopes,
getScopeValue,
} from "~/composables/auth/scopes.ts";
import Modal from "~/components/ui/Modal.vue";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const formatRelativeTime = useRelativeTime();
@@ -346,8 +346,7 @@ async function createPat() {
pats.value.push(res);
patModal.value.hide();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -372,8 +371,7 @@ async function editPat() {
await refresh();
patModal.value.hide();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -392,8 +390,7 @@ async function removePat(id) {
});
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
+5 -5
View File
@@ -90,8 +90,11 @@
</template>
<script setup>
import { UserIcon, SaveIcon, UploadIcon, UndoIcon, XIcon, TrashIcon } from "@modrinth/assets";
import { Avatar, FileInput, Button, commonMessages } from "@modrinth/ui";
import { SaveIcon, TrashIcon, UndoIcon, UploadIcon, UserIcon, XIcon } from "@modrinth/assets";
import { Avatar, Button, commonMessages, FileInput, injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
useHead({
title: "Profile settings - Modrinth",
@@ -101,8 +104,6 @@ definePageMeta({
middleware: "auth",
});
const { formatMessage } = useVIntl();
const messages = defineMessages({
title: {
id: "settings.profile.profile-info",
@@ -222,7 +223,6 @@ async function saveChanges() {
saved.value = true;
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err
? err.data
@@ -57,12 +57,18 @@
</template>
<script setup>
import { XIcon } from "@modrinth/assets";
import { commonMessages, commonSettingsMessages, useRelativeTime } from "@modrinth/ui";
import {
commonMessages,
commonSettingsMessages,
injectNotificationManager,
useRelativeTime,
} from "@modrinth/ui";
definePageMeta({
middleware: "auth",
});
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const formatRelativeTime = useRelativeTime();
@@ -116,8 +122,7 @@ async function revokeSession(id) {
});
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
-1
View File
@@ -120,7 +120,6 @@ export default defineNuxtPlugin((nuxtApp) => {
return 0;
});
});
nuxtApp.provide("notify", (notif) => addNotification(notif));
});
export const formatNumber = (number, abbreviate = true) => {
const x = +number;
@@ -0,0 +1,44 @@
import { type WebNotification, AbstractWebNotificationManager } from "@modrinth/ui";
import { useState } from "#app";
export class FrontendNotificationManager extends AbstractWebNotificationManager {
private readonly state: Ref<WebNotification[]>;
private readonly isRightwards: Ref<boolean>;
public constructor() {
super();
this.state = useState<WebNotification[]>("notifications", () => []);
this.isRightwards = useState<boolean>("notifications.isRightwards", () => true);
}
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);
}
protected removeNotificationFromStorage(id: string | number): void {
const index = this.state.value.findIndex((n) => n.id === id);
if (index > -1) {
this.state.value.splice(index, 1);
}
}
protected removeNotificationFromStorageByIndex(index: number): void {
this.state.value.splice(index, 1);
}
protected clearAllNotificationsFromStorage(): void {
this.state.value.splice(0);
}
}
+1
View File
@@ -1,3 +1,4 @@
export * from './src/components'
export * from './src/utils'
export * from './src/composables'
export * from './src/providers'
@@ -1,152 +0,0 @@
<template>
<div class="vue-notification-group" :class="{ 'has-sidebar': sidebar }">
<transition-group name="notifs">
<div
v-for="(item, index) in notifications"
:key="item.id"
class="vue-notification-wrapper"
@click="notifications.splice(index, 1)"
@mouseenter="stopTimer(item)"
@mouseleave="setNotificationTimer(item)"
>
<div class="vue-notification-template vue-notification" :class="{ [item.type]: true }">
<div class="notification-title" v-html="item.title"></div>
<div class="notification-content" v-html="item.text"></div>
</div>
</div>
</transition-group>
</div>
</template>
<script setup>
import { ref } from 'vue'
defineProps({
sidebar: {
type: Boolean,
default: false,
},
})
const notifications = ref([])
defineExpose({
addNotification: (notification) => {
const existingNotif = notifications.value.find(
(x) =>
x.text === notification.text &&
x.title === notification.title &&
x.type === notification.type,
)
if (existingNotif) {
setNotificationTimer(existingNotif)
return
}
notification.id = new Date()
setNotificationTimer(notification)
notifications.value.push(notification)
},
})
function setNotificationTimer(notification) {
if (!notification) return
if (notification.timer) {
clearTimeout(notification.timer)
}
notification.timer = setTimeout(() => {
notifications.value.splice(notifications.value.indexOf(notification), 1)
}, 30000)
}
function stopTimer(notif) {
clearTimeout(notif.timer)
}
</script>
<style lang="scss" scoped>
.vue-notification {
background: var(--color-blue) !important;
color: var(--color-accent-contrast) !important;
box-sizing: border-box;
text-align: left;
font-size: 12px;
padding: 10px;
margin: 0 5px 5px;
&.success {
background: var(--color-green) !important;
}
&.warn {
background: var(--color-orange) !important;
}
&.error {
background: var(--color-red) !important;
}
}
.vue-notification-group {
position: fixed;
right: 25px;
bottom: 25px;
z-index: 99999999;
width: 300px;
&.has-sidebar {
right: 325px;
}
.vue-notification-wrapper {
width: 100%;
overflow: hidden;
margin-bottom: 10px;
.vue-notification-template {
border-radius: var(--radius-md);
margin: 0;
.notification-title {
font-size: var(--font-size-lg);
margin-right: auto;
font-weight: 600;
}
.notification-content {
margin-right: auto;
font-size: var(--font-size-md);
}
}
&:last-child {
margin: 0;
}
}
@media screen and (max-width: 750px) {
bottom: calc(var(--size-mobile-navbar-height, 15px) + 10px) !important;
&.browse-menu-open {
bottom: calc(var(--size-mobile-navbar-height-expanded, 15px) + 10px) !important;
}
}
}
.notifs-enter-active,
.notifs-leave-active,
.notifs-move {
transition: all 0.5s;
@media (prefers-reduced-motion) {
transition: none !important;
}
}
.notifs-enter-from,
.notifs-leave-to {
opacity: 0;
}
</style>
+1
View File
@@ -76,6 +76,7 @@ export { default as Breadcrumbs } from './nav/Breadcrumbs.vue'
export { default as NavItem } from './nav/NavItem.vue'
export { default as NavRow } from './nav/NavRow.vue'
export { default as NavStack } from './nav/NavStack.vue'
export { default as NotificationPanel } from './nav/NotificationPanel.vue'
export { default as PagewideBanner } from './nav/PagewideBanner.vue'
// Project
@@ -1,10 +1,7 @@
<template>
<div
class="vue-notification-group experimental-styles-within"
:class="{
'intercom-present': isIntercomPresent,
rightwards: moveNotificationsRight,
}"
:class="{ 'intercom-present': isIntercomPresent }"
>
<transition-group name="notifs">
<div
@@ -53,7 +50,7 @@
</button>
</ButtonStyled>
<ButtonStyled circular size="small">
<button v-tooltip="`Dismiss`" @click="notifications.splice(index, 1)">
<button v-tooltip="`Dismiss`" @click="dismissNotification(index)">
<XIcon />
</button>
</ButtonStyled>
@@ -73,8 +70,10 @@
</transition-group>
</div>
</template>
<script setup>
import { ButtonStyled } from "@modrinth/ui";
<script setup lang="ts">
import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
import { ButtonStyled, injectNotificationManager, type WebNotification } from '@modrinth/ui'
import {
XCircleIcon,
CheckCircleIcon,
@@ -83,69 +82,55 @@ import {
IssuesIcon,
XIcon,
CopyIcon,
} from "@modrinth/assets";
const notifications = useNotifications();
const { isVisible: moveNotificationsRight } = useNotificationRightwards();
} from '@modrinth/assets'
const isIntercomPresent = ref(false);
const notificationManager = injectNotificationManager()
const notifications = computed<WebNotification[]>(() => notificationManager.getNotifications())
function stopTimer(notif) {
clearTimeout(notif.timer);
const isIntercomPresent = ref<boolean>(false)
const copied = ref<Record<string, boolean>>({})
const stopTimer = (n: WebNotification) => notificationManager.stopNotificationTimer(n)
const setNotificationTimer = (n: WebNotification) => notificationManager.setNotificationTimer(n)
const dismissNotification = (n: number) => notificationManager.removeNotificationByIndex(n)
function createNotifText(notif: WebNotification): string {
return [notif.title, notif.text, notif.errorCode].filter(Boolean).join('\n')
}
const copied = ref({});
function checkIntercomPresence(): void {
isIntercomPresent.value = !!document.querySelector('.intercom-lightweight-app')
}
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 copyToClipboard(notif: WebNotification): void {
const text = createNotifText(notif)
function checkIntercomPresence() {
isIntercomPresent.value = !!document.querySelector(".intercom-lightweight-app");
copied.value[text] = true
navigator.clipboard.writeText(text)
setTimeout(() => {
delete copied.value[text]
}, 2000)
}
onMounted(() => {
checkIntercomPresence();
checkIntercomPresence()
const observer = new MutationObserver(() => {
checkIntercomPresence();
});
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);
}
observer.disconnect()
})
})
</script>
<style lang="scss" scoped>
.vue-notification-group {
position: fixed;
@@ -164,15 +149,6 @@ function copyToClipboard(notif) {
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;
+81
View File
@@ -0,0 +1,81 @@
/**
* MIT License
*
* Copyright (c) 2023 UnoVue
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @source https://github.com/unovue/reka-ui/blob/53b4734734f8ebef9a344b1e62db291177c59bfe/packages/core/src/shared/createContext.ts
*/
import type { InjectionKey } from 'vue'
import { inject, provide } from 'vue'
/**
* @param providerComponentName - The name(s) of the component(s) providing the context.
*
* There are situations where context can come from multiple components. In such cases, you might need to give an array of component names to provide your context, instead of just a single string.
*
* @param contextName The description for injection key symbol.
*/
export function createContext<ContextValue>(
providerComponentName: string | string[],
contextName?: string,
) {
const symbolDescription =
typeof providerComponentName === 'string' && !contextName
? `${providerComponentName}Context`
: contextName
const injectionKey: InjectionKey<ContextValue | null> = Symbol(symbolDescription)
/**
* @param fallback The context value to return if the injection fails.
*
* @throws When context injection failed and no fallback is specified.
* This happens when the component injecting the context is not a child of the root component providing the context.
*/
const injectContext = <T extends ContextValue | null | undefined = ContextValue>(
fallback?: T,
): T extends null ? ContextValue | null : ContextValue => {
const context = inject(injectionKey, fallback)
if (context) return context
if (context === null)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return context as any
throw new Error(
`Injection \`${injectionKey.toString()}\` not found. Component must be used within ${
Array.isArray(providerComponentName)
? `one of the following components: ${providerComponentName.join(', ')}`
: `\`${providerComponentName}\``
}`,
)
}
const provideContext = (contextValue: ContextValue) => {
provide(injectionKey, contextValue)
return contextValue
}
return [injectContext, provideContext] as const
}
export * from './web-notifications'
@@ -0,0 +1,111 @@
import { createContext } from '.'
export interface WebNotification {
id: string | number
title?: string
text?: string
type?: 'error' | 'warning' | 'success' | 'info'
errorCode?: string
count?: number
timer?: NodeJS.Timeout
}
export abstract class AbstractWebNotificationManager {
protected readonly AUTO_DISMISS_DELAY_MS = 30 * 1000
abstract getNotifications(): WebNotification[]
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 {
const existingNotif = this.findExistingNotification(notification)
if (existingNotif) {
this.refreshNotificationTimer(existingNotif)
existingNotif.count = (existingNotif.count || 0) + 1
return
}
const newNotification = this.createNotification(notification)
this.setNotificationTimer(newNotification)
this.addNotificationToStorage(newNotification)
}
removeNotification(id: string | number): void {
const notifications = this.getNotifications()
const notification = notifications.find((n) => n.id === id)
if (notification) {
this.clearNotificationTimer(notification)
this.removeNotificationFromStorage(id)
}
}
removeNotificationByIndex(index: number): void {
const notifications = this.getNotifications()
if (index >= 0 && index < notifications.length) {
const notification = notifications[index]
this.clearNotificationTimer(notification)
this.removeNotificationFromStorageByIndex(index)
}
}
clearAllNotifications(): void {
const notifications = this.getNotifications()
notifications.forEach((notification) => {
this.clearNotificationTimer(notification)
})
this.clearAllNotificationsFromStorage()
}
setNotificationTimer(notification: WebNotification): void {
if (!notification) return
this.clearNotificationTimer(notification)
notification.timer = setTimeout(() => {
this.removeNotification(notification.id)
}, this.AUTO_DISMISS_DELAY_MS)
}
stopNotificationTimer(notification: WebNotification): void {
this.clearNotificationTimer(notification)
}
private refreshNotificationTimer(notification: WebNotification): void {
this.setNotificationTimer(notification)
}
private clearNotificationTimer(notification: WebNotification): void {
if (notification.timer) {
clearTimeout(notification.timer)
notification.timer = undefined
}
}
private findExistingNotification(
notification: Partial<WebNotification>,
): WebNotification | undefined {
return this.getNotifications().find(
(existing) =>
existing.text === notification.text &&
existing.title === notification.title &&
existing.type === notification.type,
)
}
private createNotification(notification: Partial<WebNotification>): WebNotification {
return {
...notification,
id: new Date().getTime(),
count: 1,
} as WebNotification
}
}
export const [injectNotificationManager, provideNotificationManager] =
createContext<AbstractWebNotificationManager>('root', 'notificationManager')