feat: introduce dependency injection framework (#4091)

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

* fix: lint

* fix: issues

* fix: compile error + notif binding issue

* refactor: move org context to new DI setup

* feat: migrate app notifications to DI + frontend styling

* fix: sidebar issues

* fix: dont use delete in computed

* fix: import and prop issue

* refactor: move handleError to main notification manager class

* fix: lint & build

* fix: merge issues

* fix: lint issues

* fix: lint issues

---------

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

View File

@@ -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.",

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",

View File

@@ -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,
});
}
};

View File

@@ -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;
}>();

View File

@@ -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",

View File

@@ -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.",

View File

@@ -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;
}>();

View File

@@ -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`,

View File

@@ -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.",

View File

@@ -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.",

View File

@@ -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;
}>();
@@ -199,7 +200,6 @@ async function saveStartup() {
}
addNotification({
group: "serverOptions",
type: "success",
title: "Server settings updated",
text: "Your server settings were successfully changed.",
@@ -207,7 +207,6 @@ async function saveStartup() {
} catch (error) {
console.error(error);
addNotification({
group: "serverOptions",
type: "error",
title: "Failed to update server arguments",
text: "Please try again later.",