refactor: move handleError to main notification manager class

This commit is contained in:
IMB11 2025-08-02 12:40:40 +01:00
parent 444ace8e54
commit c0f9bc0f48
22 changed files with 57 additions and 63 deletions

View File

@ -1,12 +1,11 @@
<script setup lang="ts">
import { add_project_from_path } from '@/helpers/profile.js'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { DropdownIcon, FolderOpenIcon, PlusIcon } from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager, OverflowMenu } from '@modrinth/ui'
import { open } from '@tauri-apps/plugin-dialog'
import { useRouter } from 'vue-router'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const props = defineProps({
instance: {

View File

@ -4,7 +4,6 @@ import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { get_user_many } from '@/helpers/cache'
import { friend_listener } from '@/helpers/events'
import { add_friend, friend_statuses, friends, remove_friend } from '@/helpers/friends'
import type { AppNotificationManager } from '@/providers/app-notifications'
import {
MailIcon,
MoreVerticalIcon,
@ -24,7 +23,7 @@ import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import { computed, onUnmounted, ref, watch } from 'vue'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const formatRelativeTime = useRelativeTime()
const props = defineProps<{

View File

@ -2,7 +2,6 @@
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { CopyIcon, EditIcon, PlusIcon, SpinnerIcon, TrashIcon, UploadIcon } from '@modrinth/assets'
import {
Avatar,
@ -18,7 +17,7 @@ import { computed, ref, type Ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import type { GameInstance, InstanceSettingsTabProps } from '../../../helpers/types'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const router = useRouter()

View File

@ -1,13 +1,12 @@
<script setup lang="ts">
import { edit } from '@/helpers/profile'
import { get } from '@/helpers/settings.ts'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { Checkbox, injectNotificationManager } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, ref, watch } from 'vue'
import type { AppSettings, Hooks, InstanceSettingsTabProps } from '../../../helpers/types'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>()

View File

@ -6,7 +6,6 @@ import { get_project, get_version_many } from '@/helpers/cache'
import { get_loader_versions } from '@/helpers/metadata'
import { edit, install, update_repair_modrinth } from '@/helpers/profile'
import { get_game_versions, get_loaders } from '@/helpers/tags'
import type { AppNotificationManager } from '@/providers/app-notifications'
import {
DownloadIcon,
HammerIcon,
@ -42,7 +41,7 @@ import type {
ManifestLoaderVersion,
} from '../../../helpers/types'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const repairConfirmModal = ref()

View File

@ -3,14 +3,13 @@ import JavaSelector from '@/components/ui/JavaSelector.vue'
import useMemorySlider from '@/composables/useMemorySlider'
import { edit, get_optimal_jre_key } from '@/helpers/profile'
import { get } from '@/helpers/settings.ts'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { CheckCircleIcon, XCircleIcon } from '@modrinth/assets'
import { Checkbox, injectNotificationManager, Slider } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, readonly, ref, watch } from 'vue'
import type { AppSettings, InstanceSettingsTabProps, MemorySettings } from '../../../helpers/types'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>()

View File

@ -1,13 +1,12 @@
<script setup lang="ts">
import { edit } from '@/helpers/profile'
import { get } from '@/helpers/settings.ts'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { Checkbox, injectNotificationManager, Toggle } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, ref, type Ref, watch } from 'vue'
import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>()

View File

@ -113,7 +113,6 @@ import {
type Skin,
type SkinModel,
} from '@/helpers/skins.ts'
import type { AppNotificationManager } from '@/providers/app-notifications'
import {
CheckIcon,
ChevronRightIcon,
@ -133,7 +132,7 @@ import {
} from '@modrinth/ui'
import { computed, ref, useTemplateRef, watch } from 'vue'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const modal = useTemplateRef('modal')
const selectCapeModal = useTemplateRef('selectCapeModal')

View File

@ -6,7 +6,6 @@ import { get_by_profile_path } from '@/helpers/process'
import { kill, run } from '@/helpers/profile'
import type { GameInstance } from '@/helpers/types'
import { showProfileInFolder } from '@/helpers/utils'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { handleSevereError } from '@/store/error'
import {
EyeIcon,
@ -33,7 +32,7 @@ import dayjs from 'dayjs'
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const formatRelativeTime = useRelativeTime()

View File

@ -18,7 +18,6 @@ import {
start_join_server,
start_join_singleplayer_world,
} from '@/helpers/worlds.ts'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { handleSevereError } from '@/store/error'
import { useTheming } from '@/store/theme.ts'
import { GAME_MODES, HeadingLink, injectNotificationManager } from '@modrinth/ui'
@ -26,7 +25,7 @@ import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const props = defineProps<{
recentInstances: GameInstance[]

View File

@ -4,13 +4,12 @@ import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
import type { GameInstance } from '@/helpers/types'
import { add_server_to_profile, type ServerPackStatus, type ServerWorld } from '@/helpers/worlds.ts'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { PlayIcon, PlusIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { ref } from 'vue'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const emit = defineEmits<{

View File

@ -10,13 +10,12 @@ import {
type ServerPackStatus,
type ServerWorld,
} from '@/helpers/worlds.ts'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { SaveIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { defineMessage, useVIntl } from '@vintl/vintl'
import { computed, ref } from 'vue'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const emit = defineEmits<{

View File

@ -4,13 +4,12 @@ import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.v
import type { GameInstance } from '@/helpers/types'
import type { DisplayStatus, SingleplayerWorld } from '@/helpers/worlds.ts'
import { rename_world, reset_world_icon, set_world_display_status } from '@/helpers/worlds.ts'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { ChevronRightIcon, SaveIcon, UndoIcon, XIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, ref } from 'vue'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const emit = defineEmits<{

View File

@ -1,4 +1,3 @@
import type { AppNotificationManager } from '@/providers/app-notifications'
import { injectNotificationManager } from '@modrinth/ui'
import { arrayBufferToBase64 } from '@modrinth/utils'
import { invoke } from '@tauri-apps/api/core'
@ -40,7 +39,7 @@ export const DEFAULT_MODELS: Record<string, SkinModel> = {
export function filterSavedSkins(list: Skin[]) {
const customSkins = list.filter((s) => s.source !== 'default')
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
fixUnknownSkins(customSkins).catch(handleError)
return customSkins
}

View File

@ -7,7 +7,6 @@ import SearchCard from '@/components/ui/SearchCard.vue'
import { get_search_results } from '@/helpers/cache.js'
import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile.js'
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { ClipboardCopyIcon, ExternalIcon, GlobeIcon, SearchIcon, XIcon } from '@modrinth/assets'
import type { Category, GameVersion, Platform, ProjectType, SortType, Tags } from '@modrinth/ui'
@ -29,7 +28,7 @@ import { computed, nextTick, ref, shallowRef, watch } from 'vue'
import type { LocationQuery } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const router = useRouter()

View File

@ -5,7 +5,6 @@ import { get_search_results } from '@/helpers/cache.js'
import { profile_listener } from '@/helpers/events'
import { list } from '@/helpers/profile.js'
import type { GameInstance } from '@/helpers/types'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { injectNotificationManager } from '@modrinth/ui'
import type { SearchResult } from '@modrinth/utils'
@ -13,7 +12,7 @@ import dayjs from 'dayjs'
import { computed, onUnmounted, ref } from 'vue'
import { useRoute } from 'vue-router'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const route = useRoute()
const breadcrumbs = useBreadcrumbs()

View File

@ -20,7 +20,6 @@ import {
remove_custom_skin,
set_default_cape,
} from '@/helpers/skins.ts'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { handleSevereError } from '@/store/error'
import {
EditIcon,
@ -48,7 +47,7 @@ const editSkinModal = useTemplateRef('editSkinModal')
const selectCapeModal = useTemplateRef('selectCapeModal')
const uploadSkinModal = useTemplateRef('uploadSkinModal')
const notifications = injectNotificationManager() as AppNotificationManager
const notifications = injectNotificationManager()
const { handleError } = notifications
const settings = ref(await getSettings())

View File

@ -273,7 +273,6 @@ import {
} from '@/helpers/profile.js'
import type { CacheBehaviour, ContentFile, GameInstance } from '@/helpers/types'
import { highlightModInProfile } from '@/helpers/utils.js'
import type { AppNotificationManager } from '@/providers/app-notifications'
import {
CheckCircleIcon,
ClipboardCopyIcon,
@ -311,7 +310,7 @@ import dayjs from 'dayjs'
import type { ComputedRef } from 'vue'
import { computed, onUnmounted, ref, watch } from 'vue'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const props = defineProps<{
instance: GameInstance

View File

@ -151,7 +151,6 @@ import {
start_join_server,
start_join_singleplayer_world,
} from '@/helpers/worlds.ts'
import type { AppNotificationManager } from '@/providers/app-notifications'
import { PlusIcon, SearchIcon, SpinnerIcon, UpdatedIcon, XIcon } from '@modrinth/assets'
import {
Button,
@ -168,7 +167,7 @@ import { defineMessages } from '@vintl/vintl'
import { computed, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const { handleError } = injectNotificationManager() as AppNotificationManager
const { handleError } = injectNotificationManager()
const route = useRoute()
const addServerModal = ref<InstanceType<typeof AddServerModal>>()

View File

@ -1,33 +1,25 @@
import {
AbstractWebNotificationManager,
type NotificationPanelLocation,
type WebNotification,
type WebNotificationLocation,
} from '@modrinth/ui'
import { ref, type Ref } from 'vue'
export class AppNotificationManager extends AbstractWebNotificationManager {
private readonly state: Ref<WebNotification[]>
private readonly locationState: Ref<WebNotificationLocation>
private readonly locationState: Ref<NotificationPanelLocation>
public constructor() {
super()
this.state = ref<WebNotification[]>([])
this.locationState = ref<WebNotificationLocation>('right')
this.locationState = ref<NotificationPanelLocation>('right')
}
public handleError = (error: Error): void => {
this.addNotification({
title: 'An error occurred',
text: error.message ?? error,
type: 'error',
})
}
public getNotificationLocation(): WebNotificationLocation {
public getNotificationLocation(): NotificationPanelLocation {
return this.locationState.value
}
public setNotificationLocation(location: WebNotificationLocation): void {
public setNotificationLocation(location: NotificationPanelLocation): void {
this.locationState.value = location
}

View File

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

View File

@ -10,35 +10,47 @@ export interface WebNotification {
timer?: NodeJS.Timeout
}
export type WebNotificationLocation = 'left' | 'right'
export type NotificationPanelLocation = 'left' | 'right'
export abstract class AbstractWebNotificationManager {
protected readonly AUTO_DISMISS_DELAY_MS = 30 * 1000
abstract getNotifications(): WebNotification[]
abstract getNotificationLocation(): WebNotificationLocation
abstract setNotificationLocation(location: WebNotificationLocation): void
abstract getNotificationLocation(): NotificationPanelLocation
abstract setNotificationLocation(location: NotificationPanelLocation): void
protected abstract addNotificationToStorage(notification: WebNotification): void
protected abstract removeNotificationFromStorage(id: string | number): void
protected abstract removeNotificationFromStorageByIndex(index: number): void
protected abstract clearAllNotificationsFromStorage(): void
addNotification = (notification: Partial<WebNotification>): void => {
addNotification = (notification: Partial<WebNotification>): WebNotification => {
const existingNotif = this.findExistingNotification(notification)
if (existingNotif) {
this.refreshNotificationTimer(existingNotif)
existingNotif.count = (existingNotif.count || 0) + 1
return
return existingNotif
}
const newNotification = this.createNotification(notification)
this.setNotificationTimer(newNotification)
this.addNotificationToStorage(newNotification)
return newNotification
}
removeNotification = (id: string | number): void => {
/**
* @deprecated You should use `addNotification` instead to provide a more human-readable error message to the user.
*/
handleError = (error: Error): void => {
this.addNotification({
title: 'An error occurred',
text: error.message ?? error,
type: 'error',
})
}
removeNotification = (id: string | number): WebNotification | undefined => {
const notifications = this.getNotifications()
const notification = notifications.find((n) => n.id === id)
@ -46,16 +58,22 @@ export abstract class AbstractWebNotificationManager {
this.clearNotificationTimer(notification)
this.removeNotificationFromStorage(id)
}
return notification
}
removeNotificationByIndex = (index: number): void => {
removeNotificationByIndex = (index: number): WebNotification | null => {
const notifications = this.getNotifications()
if (index >= 0 && index < notifications.length) {
const notification = notifications[index]
this.clearNotificationTimer(notification)
this.removeNotificationFromStorageByIndex(index)
return notification
}
return null
}
clearAllNotifications = (): void => {