fix: DI nonsense (#4174)

* fix: DI nonsense

* fix: lint

* fix: client try di issue

* fix: injects outside of context

* fix: use .catch

* refactor: convert projects.vue to composition API.

* fix: moderation checklist notif pos change watcher

* fix: lint issues
This commit is contained in:
Cal H.
2025-08-15 19:02:55 +01:00
committed by GitHub
parent 9b5f172170
commit 4ad6daa45c
32 changed files with 318 additions and 323 deletions

View File

@@ -165,7 +165,14 @@ const handleOptionsClick = async (args) => {
await navigator.clipboard.writeText(args.item.path) await navigator.clipboard.writeText(args.item.path)
break break
case 'install': { case 'install': {
await installVersion(args.item.project_id, null, null, 'ProjectCardContextMenu') await installVersion(
args.item.project_id,
null,
null,
'ProjectCardContextMenu',
() => {},
() => {},
).catch(handleError)
break break
} }

View File

@@ -94,7 +94,7 @@ const stop = async (e, context) => {
const repair = async (e) => { const repair = async (e) => {
e?.stopPropagation() e?.stopPropagation()
await finish_install(props.instance) await finish_install(props.instance).catch(handleError)
} }
const openFolder = async () => { const openFolder = async () => {

View File

@@ -118,7 +118,7 @@
<script setup> <script setup>
import { CheckIcon, DownloadIcon, HeartIcon, PlusIcon, TagsIcon } from '@modrinth/assets' import { CheckIcon, DownloadIcon, HeartIcon, PlusIcon, TagsIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled } from '@modrinth/ui' import { Avatar, ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import { formatCategory, formatNumber } from '@modrinth/utils' import { formatCategory, formatNumber } from '@modrinth/utils'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
@@ -128,6 +128,8 @@ import { useRouter } from 'vue-router'
import { install as installVersion } from '@/store/install.js' import { install as installVersion } from '@/store/install.js'
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
const { handleError } = injectNotificationManager()
const router = useRouter() const router = useRouter()
const props = defineProps({ const props = defineProps({
@@ -175,7 +177,7 @@ async function install() {
(profile) => { (profile) => {
router.push(`/instance/${profile}`) router.push(`/instance/${profile}`)
}, },
) ).catch(handleError)
} }
const modpack = computed(() => props.project.project_type === 'modpack') const modpack = computed(() => props.project.project_type === 'modpack')

View File

@@ -39,7 +39,14 @@ defineExpose({
async function install() { async function install() {
confirmModal.value.hide() confirmModal.value.hide()
await installVersion(project.value.id, version.value.id, null, 'URLConfirmModal') await installVersion(
project.value.id,
version.value.id,
null,
'URLConfirmModal',
() => {},
() => {},
).catch(handleError)
} }
</script> </script>

View File

@@ -110,7 +110,7 @@ async function install(instance) {
} }
await installMod(instance.path, version.id).catch(handleError) await installMod(instance.path, version.id).catch(handleError)
await installVersionDependencies(instance, version) await installVersionDependencies(instance, version).catch(handleError)
instance.installedMod = true instance.installedMod = true
instance.installing = false instance.installing = false
@@ -185,7 +185,7 @@ const createInstance = async () => {
await router.push(`/instance/${encodeURIComponent(id)}/`) await router.push(`/instance/${encodeURIComponent(id)}/`)
const instance = await get(id, true) const instance = await get(id, true)
await installVersionDependencies(instance, versions.value[0]) await installVersionDependencies(instance, versions.value[0]).catch(handleError)
trackEvent('InstanceCreate', { trackEvent('InstanceCreate', {
profile_name: name.value, profile_name: name.value,

View File

@@ -16,7 +16,7 @@ const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>() const props = defineProps<InstanceSettingsTabProps>()
const globalSettings = (await get().catch(handleError)) as AppSettings const globalSettings = (await get().catch(handleError)) as unknown as AppSettings
const overrideJavaInstall = ref(!!props.instance.java_path) const overrideJavaInstall = ref(!!props.instance.java_path)
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError)) const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
@@ -36,7 +36,10 @@ const envVars = ref(
const overrideMemorySettings = ref(!!props.instance.memory) const overrideMemorySettings = ref(!!props.instance.memory)
const memory = ref(props.instance.memory ?? globalSettings.memory) const memory = ref(props.instance.memory ?? globalSettings.memory)
const { maxMemory, snapPoints } = await useMemorySlider() const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as {
maxMemory: number
snapPoints: number[]
}
const editProfileObject = computed(() => { const editProfileObject = computed(() => {
const editProfile: { const editProfile: {

View File

@@ -1,17 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { Slider, Toggle } from '@modrinth/ui' import { injectNotificationManager, Slider, Toggle } from '@modrinth/ui'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import useMemorySlider from '@/composables/useMemorySlider' import useMemorySlider from '@/composables/useMemorySlider'
import { get, set } from '@/helpers/settings.ts' import { get, set } from '@/helpers/settings.ts'
const { handleError } = injectNotificationManager()
const fetchSettings = await get() const fetchSettings = await get()
fetchSettings.launchArgs = fetchSettings.extra_launch_args.join(' ') fetchSettings.launchArgs = fetchSettings.extra_launch_args.join(' ')
fetchSettings.envVars = fetchSettings.custom_env_vars.map((x) => x.join('=')).join(' ') fetchSettings.envVars = fetchSettings.custom_env_vars.map((x) => x.join('=')).join(' ')
const settings = ref(fetchSettings) const settings = ref(fetchSettings)
const { maxMemory, snapPoints } = await useMemorySlider() const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as {
maxMemory: number
snapPoints: number[]
}
watch( watch(
settings, settings,

View File

@@ -1,11 +1,9 @@
import { injectNotificationManager } from '@modrinth/ui'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { get_max_memory } from '@/helpers/jre.js' import { get_max_memory } from '@/helpers/jre.js'
export default async function () { export default async function () {
const { handleError } = injectNotificationManager() const maxMemory = ref(Math.floor((await get_max_memory()) / 1024))
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
const snapPoints = computed(() => { const snapPoints = computed(() => {
let points = [] let points = []

View File

@@ -1,4 +1,3 @@
import { injectNotificationManager } from '@modrinth/ui'
import { getVersion } from '@tauri-apps/api/app' import { getVersion } from '@tauri-apps/api/app'
import { fetch } from '@tauri-apps/plugin-http' import { fetch } from '@tauri-apps/plugin-http'
@@ -11,9 +10,9 @@ export const useFetch = async (url, item, isSilent) => {
}) })
} catch (err) { } catch (err) {
if (!isSilent) { if (!isSilent) {
const { handleError } = injectNotificationManager() throw err
handleError({ message: `Error fetching ${item}` }) } else {
console.error(err)
} }
console.error(err)
} }
} }

View File

@@ -3,7 +3,6 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { injectNotificationManager } from '@modrinth/ui'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { install_to_existing_profile } from '@/helpers/pack.js' import { install_to_existing_profile } from '@/helpers/pack.js'
@@ -194,7 +193,6 @@ export async function edit_icon(path, iconPath) {
} }
export async function finish_install(instance) { export async function finish_install(instance) {
const { handleError } = injectNotificationManager()
if (instance.install_stage !== 'pack_installed') { if (instance.install_stage !== 'pack_installed') {
let linkedData = instance.linked_data let linkedData = instance.linked_data
await install_to_existing_profile( await install_to_existing_profile(
@@ -202,8 +200,8 @@ export async function finish_install(instance) {
linkedData.version_id, linkedData.version_id,
instance.name, instance.name,
instance.path, instance.path,
).catch(handleError) )
} else { } else {
await install(instance.path, false).catch(handleError) await install(instance.path, false)
} }
} }

View File

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

View File

@@ -66,7 +66,14 @@ const defaultCape = ref<Cape>()
const originalSelectedSkin = ref<Skin | null>(null) const originalSelectedSkin = ref<Skin | null>(null)
const originalDefaultCape = ref<Cape>() const originalDefaultCape = ref<Cape>()
const savedSkins = computed(() => filterSavedSkins(skins.value)) const savedSkins = computed(() => {
try {
return filterSavedSkins(skins.value)
} catch (error) {
handleError(error as Error)
return []
}
})
const defaultSkins = computed(() => filterDefaultSkins(skins.value)) const defaultSkins = computed(() => filterDefaultSkins(skins.value))
const currentCape = computed(() => { const currentCape = computed(() => {

View File

@@ -331,7 +331,7 @@ const stopInstance = async (context) => {
} }
const repairInstance = async () => { const repairInstance = async () => {
await finish_install(instance.value) await finish_install(instance.value).catch(handleError)
} }
const handleRightClick = (event) => { const handleRightClick = (event) => {

View File

@@ -252,7 +252,7 @@ async function install(version) {
(profile) => { (profile) => {
router.push(`/instance/${profile}`) router.push(`/instance/${profile}`)
}, },
) ).catch(handleError)
} }
const options = ref(null) const options = ref(null)

View File

@@ -1,4 +1,3 @@
import { injectNotificationManager } from '@modrinth/ui'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
@@ -50,12 +49,11 @@ export const install = async (
callback = () => {}, callback = () => {},
createInstanceCallback = () => {}, createInstanceCallback = () => {},
) => { ) => {
const { handleError } = injectNotificationManager() const project = await get_project(projectId, 'must_revalidate')
const project = await get_project(projectId, 'must_revalidate').catch(handleError)
if (project.project_type === 'modpack') { if (project.project_type === 'modpack') {
const version = versionId ?? project.versions[project.versions.length - 1] const version = versionId ?? project.versions[project.versions.length - 1]
const packs = await list().catch(handleError) const packs = await list()
if (packs.length === 0 || !packs.find((pack) => pack.linked_data?.project_id === project.id)) { if (packs.length === 0 || !packs.find((pack) => pack.linked_data?.project_id === project.id)) {
await packInstall( await packInstall(
@@ -64,7 +62,7 @@ export const install = async (
project.title, project.title,
project.icon_url, project.icon_url,
createInstanceCallback, createInstanceCallback,
).catch(handleError) )
trackEvent('PackInstall', { trackEvent('PackInstall', {
id: project.id, id: project.id,
@@ -81,8 +79,8 @@ export const install = async (
} else { } else {
if (instancePath) { if (instancePath) {
const [instance, instanceProjects, versions] = await Promise.all([ const [instance, instanceProjects, versions] = await Promise.all([
await get(instancePath).catch(handleError), await get(instancePath),
await get_projects(instancePath).catch(handleError), await get_projects(instancePath),
await get_version_many(project.versions, 'must_revalidate'), await get_version_many(project.versions, 'must_revalidate'),
]) ])
@@ -119,7 +117,7 @@ export const install = async (
} }
} }
await add_project_from_version(instance.path, version.id).catch(handleError) await add_project_from_version(instance.path, version.id)
await installVersionDependencies(instance, version) await installVersionDependencies(instance, version)
trackEvent('ProjectInstall', { trackEvent('ProjectInstall', {
@@ -144,7 +142,7 @@ export const install = async (
) )
} }
} else { } else {
const versions = (await get_version_many(project.versions).catch(handleError)).sort( const versions = (await get_version_many(project.versions)).sort(
(a, b) => dayjs(b.date_published) - dayjs(a.date_published), (a, b) => dayjs(b.date_published) - dayjs(a.date_published),
) )
@@ -168,36 +166,27 @@ export const install = async (
} }
export const installVersionDependencies = async (profile, version) => { export const installVersionDependencies = async (profile, version) => {
const { handleError } = injectNotificationManager()
for (const dep of version.dependencies) { for (const dep of version.dependencies) {
if (dep.dependency_type !== 'required') continue if (dep.dependency_type !== 'required') continue
// disallow fabric api install on quilt // disallow fabric api install on quilt
if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue
if (dep.version_id) { if (dep.version_id) {
if ( if (dep.project_id && (await check_installed(profile.path, dep.project_id))) continue
dep.project_id &&
(await check_installed(profile.path, dep.project_id).catch(handleError))
)
continue
await add_project_from_version(profile.path, dep.version_id) await add_project_from_version(profile.path, dep.version_id)
} else { } else {
if ( if (dep.project_id && (await check_installed(profile.path, dep.project_id))) continue
dep.project_id &&
(await check_installed(profile.path, dep.project_id).catch(handleError)) const depProject = await get_project(dep.project_id, 'must_revalidate')
const depVersions = (await get_version_many(depProject.versions, 'must_revalidate')).sort(
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
) )
continue
const depProject = await get_project(dep.project_id, 'must_revalidate').catch(handleError)
const depVersions = (
await get_version_many(depProject.versions, 'must_revalidate').catch(handleError)
).sort((a, b) => dayjs(b.date_published) - dayjs(a.date_published))
const latest = depVersions.find( const latest = depVersions.find(
(v) => v.game_versions.includes(profile.game_version) && v.loaders.includes(profile.loader), (v) => v.game_versions.includes(profile.game_version) && v.loaders.includes(profile.loader),
) )
if (latest) { if (latest) {
await add_project_from_version(profile.path, latest.id).catch(handleError) await add_project_from_version(profile.path, latest.id)
} }
} }
} }

View File

@@ -397,7 +397,8 @@ import { useModerationStore } from '~/store/moderation.ts'
import KeybindsModal from './ChecklistKeybindsModal.vue' import KeybindsModal from './ChecklistKeybindsModal.vue'
import ModpackPermissionsFlow from './ModpackPermissionsFlow.vue' import ModpackPermissionsFlow from './ModpackPermissionsFlow.vue'
const { addNotification } = injectNotificationManager() const notifications = injectNotificationManager()
const { addNotification } = notifications
const keybindsModal = ref<InstanceType<typeof KeybindsModal>>() const keybindsModal = ref<InstanceType<typeof KeybindsModal>>()
@@ -626,6 +627,11 @@ function handleKeybinds(event: KeyboardEvent) {
onMounted(() => { onMounted(() => {
window.addEventListener('keydown', handleKeybinds) window.addEventListener('keydown', handleKeybinds)
initializeAllStages() initializeAllStages()
notifications.setNotificationLocation('left')
})
onUnmounted(() => {
notifications.setNotificationLocation('right')
}) })
function initializeAllStages() { function initializeAllStages() {

View File

@@ -74,12 +74,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { DownloadIcon, ExternalIcon, SpinnerIcon, XIcon } from '@modrinth/assets' import { DownloadIcon, ExternalIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
import { BackupWarning, ButtonStyled, NewModal } from '@modrinth/ui' import { BackupWarning, ButtonStyled, injectNotificationManager, NewModal } from '@modrinth/ui'
import { ModrinthServersFetchError } from '@modrinth/utils' import { ModrinthServersFetchError } from '@modrinth/utils'
import { computed, nextTick, ref } from 'vue' import { computed, nextTick, ref } from 'vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts' import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import { handleError } from '~/composables/servers/modrinth-servers.ts' import { handleServersError } from '~/composables/servers/modrinth-servers.ts'
const notifications = injectNotificationManager()
const cf = ref(false) const cf = ref(false)
@@ -120,18 +122,19 @@ const handleSubmit = async () => {
hide() hide()
} else { } else {
submitted.value = false submitted.value = false
handleError( handleServersError(
new ModrinthServersFetchError( new ModrinthServersFetchError(
'Could not find CurseForge modpack at that URL.', 'Could not find CurseForge modpack at that URL.',
404, 404,
new Error(`No modpack found at ${url.value}`), new Error(`No modpack found at ${url.value}`),
), ),
notifications,
) )
} }
} catch (error) { } catch (error) {
submitted.value = false submitted.value = false
console.error('Error installing:', error) console.error('Error installing:', error)
handleError(error) handleServersError(error, notifications)
} }
} }
} }

View File

@@ -1,5 +1,3 @@
import { injectNotificationManager } from '@modrinth/ui'
export const useAuth = async (oldToken = null) => { export const useAuth = async (oldToken = null) => {
const auth = useState('auth', () => ({ const auth = useState('auth', () => ({
user: null, user: null,
@@ -119,23 +117,17 @@ export const getAuthUrl = (provider, redirect = '/dashboard') => {
export const removeAuthProvider = async (provider) => { export const removeAuthProvider = async (provider) => {
startLoading() startLoading()
try {
const auth = await useAuth()
await useBaseFetch('auth/provider', { const auth = await useAuth()
method: 'DELETE',
body: { await useBaseFetch('auth/provider', {
provider, method: 'DELETE',
}, body: {
}) provider,
await useAuth(auth.value.token) },
} catch (err) { })
const { addNotification } = injectNotificationManager()
addNotification({ await useAuth(auth.value.token)
title: 'An error occurred',
text: err.data.description,
type: 'error',
})
}
stopLoading() stopLoading()
} }

View File

@@ -1,4 +1,4 @@
import { injectNotificationManager } from '@modrinth/ui' import type { AbstractWebNotificationManager } from '@modrinth/ui'
import type { JWTAuth, ModuleError, ModuleName } from '@modrinth/utils' import type { JWTAuth, ModuleError, ModuleName } from '@modrinth/utils'
import { ModrinthServerError } from '@modrinth/utils' import { ModrinthServerError } from '@modrinth/utils'
@@ -13,17 +13,16 @@ import {
} from './modules/index.ts' } from './modules/index.ts'
import { useServersFetch } from './servers-fetch.ts' import { useServersFetch } from './servers-fetch.ts'
export function handleError(err: any) { export function handleServersError(err: any, notifications: AbstractWebNotificationManager) {
const { addNotification } = injectNotificationManager()
if (err instanceof ModrinthServerError && err.v1Error) { if (err instanceof ModrinthServerError && err.v1Error) {
addNotification({ notifications.addNotification({
title: err.v1Error?.context ?? `An error occurred`, title: err.v1Error?.context ?? `An error occurred`,
type: 'error', type: 'error',
text: err.v1Error.description, text: err.v1Error.description,
errorCode: err.v1Error.error, errorCode: err.v1Error.error,
}) })
} else { } else {
addNotification({ notifications.addNotification({
title: 'An error occurred', title: 'An error occurred',
type: 'error', type: 'error',
text: err.message ?? (err.data ? err.data.description : err), text: err.message ?? (err.data ? err.data.description : err),

View File

@@ -1,7 +1,11 @@
import type { AbstractWebNotificationManager } from '@modrinth/ui'
import { injectNotificationManager } from '@modrinth/ui' import { injectNotificationManager } from '@modrinth/ui'
type AsyncFunction<TArgs extends any[], TResult> = (...args: TArgs) => Promise<TResult> type AsyncFunction<TArgs extends any[], TResult> = (...args: TArgs) => Promise<TResult>
type ErrorFunction = (err: any) => void | Promise<void> type ErrorFunction = (
err: any,
addNotification: typeof AbstractWebNotificationManager.prototype.addNotification,
) => void | Promise<void>
type VoidFunction = () => void | Promise<void> type VoidFunction = () => void | Promise<void>
type useClientTry = <TArgs extends any[], TResult>( type useClientTry = <TArgs extends any[], TResult>(
@@ -10,8 +14,7 @@ type useClientTry = <TArgs extends any[], TResult>(
onFinish?: VoidFunction, onFinish?: VoidFunction,
) => (...args: TArgs) => Promise<TResult | undefined> ) => (...args: TArgs) => Promise<TResult | undefined>
const defaultOnError: ErrorFunction = (error) => { const defaultOnError: ErrorFunction = (error, addNotification) => {
const { addNotification } = injectNotificationManager()
addNotification({ addNotification({
title: 'An error occurred', title: 'An error occurred',
text: error?.data?.description || error.message || error || 'Unknown error', text: error?.data?.description || error.message || error || 'Unknown error',
@@ -19,15 +22,15 @@ const defaultOnError: ErrorFunction = (error) => {
}) })
} }
export const useClientTry: useClientTry = export const useClientTry: useClientTry = (fn, onFail = defaultOnError, onFinish) => {
(fn, onFail = defaultOnError, onFinish) => const { addNotification } = injectNotificationManager()
async (...args) => { return async (...args) => {
startLoading() startLoading()
try { try {
return await fn(...args) return await fn(...args)
} catch (err) { } catch (err) {
if (onFail) { if (onFail) {
await onFail(err) await onFail(err, addNotification)
} else { } else {
console.error('[CLIENT TRY ERROR]', err) console.error('[CLIENT TRY ERROR]', err)
} }
@@ -36,3 +39,4 @@ export const useClientTry: useClientTry =
stopLoading() stopLoading()
} }
} }
}

View File

@@ -137,28 +137,10 @@ export const userFollowProject = async (project) => {
} }
} }
export const resendVerifyEmail = async () => { export const resendVerifyEmail = async () => {
// const { injectNotificationManager } = await import("@modrinth/ui"); await useBaseFetch('auth/email/resend_verify', {
// const { addNotification } = injectNotificationManager(); method: 'POST',
})
startLoading() await useAuth()
try {
await useBaseFetch('auth/email/resend_verify', {
method: 'POST',
})
const auth = await useAuth()
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) {
addNotification({
title: 'An error occurred',
text: err.data.description,
type: 'error',
})
}
stopLoading() stopLoading()
} }

View File

@@ -50,7 +50,7 @@
</span> </span>
</template> </template>
<template #actions> <template #actions>
<button v-if="auth?.user?.email" class="btn" @click="resendVerifyEmail"> <button v-if="auth?.user?.email" class="btn" @click="handleResendEmailVerification">
{{ formatMessage(verifyEmailBannerMessages.action) }} {{ formatMessage(verifyEmailBannerMessages.action) }}
</button> </button>
<nuxt-link v-else class="btn" to="/settings/account"> <nuxt-link v-else class="btn" to="/settings/account">
@@ -854,6 +854,23 @@ const footerMessages = defineMessages({
}, },
}) })
async function handleResendEmailVerification() {
try {
await resendVerifyEmail()
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) {
addNotification({
title: 'An error occurred',
text: err.data.description,
type: 'error',
})
}
}
useHead({ useHead({
link: [ link: [
{ {

View File

@@ -1575,18 +1575,6 @@ const showModerationChecklist = useLocalStorage(
) )
const collapsedModerationChecklist = useLocalStorage('collapsed-moderation-checklist', false) const collapsedModerationChecklist = useLocalStorage('collapsed-moderation-checklist', false)
watch(
showModerationChecklist,
(newValue) => {
notifications.setNotificationLocation(newValue ? 'left' : 'right')
},
{ immediate: true },
)
onUnmounted(() => {
notifications.setNotificationLocation('right')
})
if (import.meta.client && history && history.state && history.state.showChecklist) { if (import.meta.client && history && history.state && history.state.showChecklist) {
showModerationChecklist.value = true showModerationChecklist.value = true
} }

View File

@@ -335,6 +335,13 @@ useSeoMeta({
<script> <script>
export default defineNuxtComponent({ export default defineNuxtComponent({
setup() {
const { addNotification } = injectNotificationManager()
return {
addNotification,
}
},
data() { data() {
return { return {
expandedGalleryItem: null, expandedGalleryItem: null,
@@ -425,8 +432,6 @@ export default defineNuxtComponent({
this.shouldPreventActions = true this.shouldPreventActions = true
startLoading() startLoading()
const { addNotification } = injectNotificationManager()
try { try {
let url = `project/${this.project.id}/gallery?ext=${ let url = `project/${this.project.id}/gallery?ext=${
this.editFile this.editFile
@@ -452,7 +457,7 @@ export default defineNuxtComponent({
this.$refs.modal_edit_item.hide() this.$refs.modal_edit_item.hide()
} catch (err) { } catch (err) {
addNotification({ this.addNotification({
title: 'An error occurred', title: 'An error occurred',
text: err.data ? err.data.description : err, text: err.data ? err.data.description : err,
type: 'error', type: 'error',
@@ -465,9 +470,6 @@ export default defineNuxtComponent({
async editGalleryItem() { async editGalleryItem() {
this.shouldPreventActions = true this.shouldPreventActions = true
startLoading() startLoading()
const { addNotification } = injectNotificationManager()
try { try {
let url = `project/${this.project.id}/gallery?url=${encodeURIComponent( let url = `project/${this.project.id}/gallery?url=${encodeURIComponent(
this.project.gallery[this.editIndex].url, this.project.gallery[this.editIndex].url,
@@ -490,7 +492,7 @@ export default defineNuxtComponent({
await this.resetProject() await this.resetProject()
this.$refs.modal_edit_item.hide() this.$refs.modal_edit_item.hide()
} catch (err) { } catch (err) {
addNotification({ this.addNotification({
title: 'An error occurred', title: 'An error occurred',
text: err.data ? err.data.description : err, text: err.data ? err.data.description : err,
type: 'error', type: 'error',
@@ -503,8 +505,6 @@ export default defineNuxtComponent({
async deleteGalleryImage() { async deleteGalleryImage() {
startLoading() startLoading()
const { addNotification } = injectNotificationManager()
try { try {
await useBaseFetch( await useBaseFetch(
`project/${this.project.id}/gallery?url=${encodeURIComponent( `project/${this.project.id}/gallery?url=${encodeURIComponent(
@@ -517,7 +517,7 @@ export default defineNuxtComponent({
await this.resetProject() await this.resetProject()
} catch (err) { } catch (err) {
addNotification({ this.addNotification({
title: 'An error occurred', title: 'An error occurred',
text: err.data ? err.data.description : err, text: err.data ? err.data.description : err,
type: 'error', type: 'error',

View File

@@ -750,6 +750,8 @@ export default defineNuxtComponent({
const data = useNuxtApp() const data = useNuxtApp()
const route = useNativeRoute() const route = useNativeRoute()
const { addNotification } = injectNotificationManager()
const auth = await useAuth() const auth = await useAuth()
const tags = useTags() const tags = useTags()
const flags = useFeatureFlags() const flags = useFeatureFlags()
@@ -915,6 +917,7 @@ export default defineNuxtComponent({
alternateFile: ref(alternateFile), alternateFile: ref(alternateFile),
replaceFile: ref(replaceFile), replaceFile: ref(replaceFile),
uploadedImageIds: ref([]), uploadedImageIds: ref([]),
addNotification,
} }
}, },
data() { data() {
@@ -996,8 +999,7 @@ export default defineNuxtComponent({
const project = await useBaseFetch(`project/${newDependencyId}`) const project = await useBaseFetch(`project/${newDependencyId}`)
if (this.version.dependencies.some((dep) => project.id === dep.project_id)) { if (this.version.dependencies.some((dep) => project.id === dep.project_id)) {
const { addNotification } = injectNotificationManager() this.addNotification({
addNotification({
title: 'Dependency already added', title: 'Dependency already added',
text: 'You cannot add the same dependency twice.', text: 'You cannot add the same dependency twice.',
type: 'error', type: 'error',
@@ -1021,8 +1023,7 @@ export default defineNuxtComponent({
const project = await useBaseFetch(`project/${version.project_id}`) const project = await useBaseFetch(`project/${version.project_id}`)
if (this.version.dependencies.some((dep) => version.id === dep.version_id)) { if (this.version.dependencies.some((dep) => version.id === dep.version_id)) {
const { addNotification } = injectNotificationManager() this.addNotification({
addNotification({
title: 'Dependency already added', title: 'Dependency already added',
text: 'You cannot add the same dependency twice.', text: 'You cannot add the same dependency twice.',
type: 'error', type: 'error',
@@ -1049,8 +1050,7 @@ export default defineNuxtComponent({
this.newDependencyId = '' this.newDependencyId = ''
} catch { } catch {
if (!hideErrors) { if (!hideErrors) {
const { addNotification } = injectNotificationManager() this.addNotification({
addNotification({
title: 'Invalid Dependency', title: 'Invalid Dependency',
text: 'The specified dependency could not be found', text: 'The specified dependency could not be found',
type: 'error', type: 'error',
@@ -1143,8 +1143,7 @@ export default defineNuxtComponent({
)}`, )}`,
) )
} catch (err) { } catch (err) {
const { addNotification } = injectNotificationManager() this.addNotification({
addNotification({
title: 'An error occurred', title: 'An error occurred',
text: err.data ? err.data.description : err, text: err.data ? err.data.description : err,
type: 'error', type: 'error',
@@ -1168,8 +1167,7 @@ export default defineNuxtComponent({
try { try {
await this.createVersionRaw(this.version) await this.createVersionRaw(this.version)
} catch (err) { } catch (err) {
const { addNotification } = injectNotificationManager() this.addNotification({
addNotification({
title: 'An error occurred', title: 'An error occurred',
text: err.data ? err.data.description : err, text: err.data ? err.data.description : err,
type: 'error', type: 'error',
@@ -1292,15 +1290,13 @@ export default defineNuxtComponent({
this.$refs.modal_package_mod.hide() this.$refs.modal_package_mod.hide()
const { addNotification } = injectNotificationManager() this.addNotification({
addNotification({
title: 'Packaging Success', title: 'Packaging Success',
text: 'Your data pack was successfully packaged as a mod! Make sure to playtest to check for errors.', text: 'Your data pack was successfully packaged as a mod! Make sure to playtest to check for errors.',
type: 'success', type: 'success',
}) })
} catch (err) { } catch (err) {
const { addNotification } = injectNotificationManager() this.addNotification({
addNotification({
title: 'An error occurred', title: 'An error occurred',
text: err.data ? err.data.description : err, text: err.data ? err.data.description : err,
type: 'error', type: 'error',

View File

@@ -40,7 +40,11 @@
</template> </template>
</p> </p>
<button v-if="auth.user" class="btn btn-primary continue-btn" @click="resendVerifyEmail"> <button
v-if="auth.user"
class="btn btn-primary continue-btn"
@click="handleResendEmailVerification"
>
{{ formatMessage(failedVerificationMessages.action) }} <RightArrowIcon /> {{ formatMessage(failedVerificationMessages.action) }} <RightArrowIcon />
</button> </button>
@@ -53,7 +57,9 @@
</template> </template>
<script setup> <script setup>
import { RightArrowIcon, SettingsIcon } from '@modrinth/assets' import { RightArrowIcon, SettingsIcon } from '@modrinth/assets'
import { injectNotificationManager } from '@modrinth/ui'
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const messages = defineMessages({ const messages = defineMessages({
@@ -149,4 +155,21 @@ if (route.query.flow) {
success.value = false success.value = false
} }
} }
async function handleResendEmailVerification() {
try {
await resendVerifyEmail()
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) {
addNotification({
title: 'An error occurred',
text: err.data.description,
type: 'error',
})
}
}
</script> </script>

View File

@@ -300,7 +300,7 @@
</div> </div>
</template> </template>
<script> <script setup>
import { import {
EditIcon, EditIcon,
IssuesIcon, IssuesIcon,
@@ -328,170 +328,116 @@ import Modal from '~/components/ui/Modal.vue'
import ModalCreation from '~/components/ui/ModalCreation.vue' import ModalCreation from '~/components/ui/ModalCreation.vue'
import { getProjectTypeForUrl } from '~/helpers/projects.js' import { getProjectTypeForUrl } from '~/helpers/projects.js'
export default defineNuxtComponent({ useHead({ title: 'Projects - Modrinth' })
components: {
Avatar,
ButtonStyled,
ProjectStatusBadge,
SettingsIcon,
TrashIcon,
Checkbox,
IssuesIcon,
PlusIcon,
XIcon,
EditIcon,
SaveIcon,
Modal,
ModalCreation,
Multiselect,
CopyCode,
SortAscIcon,
SortDescIcon,
},
async setup() {
const { formatMessage } = useVIntl()
const user = await useUser() // const UPLOAD_VERSION = 1 << 0
await initUserProjects() // const DELETE_VERSION = 1 << 1
return { formatMessage, user: ref(user) } const EDIT_DETAILS = 1 << 2
}, // const EDIT_BODY = 1 << 3
data() { // const MANAGE_INVITES = 1 << 4
return { // const REMOVE_MEMBER = 1 << 5
projects: this.updateSort(this.user.projects, 'Name'), // const EDIT_MEMBER = 1 << 6
versions: [], // const DELETE_PROJECT = 1 << 7
selectedProjects: [],
sortBy: 'Name',
descending: false,
editLinks: {
showAffected: false,
source: {
val: '',
clear: false,
},
discord: {
val: '',
clear: false,
},
wiki: {
val: '',
clear: false,
},
issues: {
val: '',
clear: false,
},
},
commonMessages,
}
},
head: {
title: 'Projects - Modrinth',
},
created() {
this.UPLOAD_VERSION = 1 << 0
this.DELETE_VERSION = 1 << 1
this.EDIT_DETAILS = 1 << 2
this.EDIT_BODY = 1 << 3
this.MANAGE_INVITES = 1 << 4
this.REMOVE_MEMBER = 1 << 5
this.EDIT_MEMBER = 1 << 6
this.DELETE_PROJECT = 1 << 7
},
methods: {
getProjectTypeForUrl,
formatProjectType,
updateDescending() {
this.descending = !this.descending
this.projects = this.updateSort(this.projects, this.sortBy, this.descending)
},
updateSort(projects, sort, descending) {
let sortedArray = projects
switch (sort) {
case 'Name':
sortedArray = projects.slice().sort((a, b) => {
return a.title.localeCompare(b.title)
})
break
case 'Status':
sortedArray = projects.slice().sort((a, b) => {
if (a.status < b.status) {
return -1
}
if (a.status > b.status) {
return 1
}
return 0
})
break
case 'Type':
sortedArray = projects.slice().sort((a, b) => {
if (a.project_type < b.project_type) {
return -1
}
if (a.project_type > b.project_type) {
return 1
}
return 0
})
break
default:
break
}
if (descending) { const { addNotification } = injectNotificationManager()
sortedArray = sortedArray.reverse() const { formatMessage } = useVIntl()
}
return sortedArray const user = await useUser()
}, const projects = ref([])
async bulkEditLinks() { const selectedProjects = ref([])
const { addNotification } = injectNotificationManager() const sortBy = ref('Name')
const descending = ref(false)
try { const editLinks = reactive({
const baseData = { showAffected: false,
issues_url: this.editLinks.issues.clear ? null : this.editLinks.issues.val.trim(), source: { val: '', clear: false },
source_url: this.editLinks.source.clear ? null : this.editLinks.source.val.trim(), discord: { val: '', clear: false },
wiki_url: this.editLinks.wiki.clear ? null : this.editLinks.wiki.val.trim(), wiki: { val: '', clear: false },
discord_url: this.editLinks.discord.clear ? null : this.editLinks.discord.val.trim(), issues: { val: '', clear: false },
}
const filteredData = Object.fromEntries(
Object.entries(baseData).filter(([, v]) => v !== ''),
)
await useBaseFetch(
`projects?ids=${JSON.stringify(this.selectedProjects.map((x) => x.id))}`,
{
method: 'PATCH',
body: filteredData,
},
)
this.$refs.editLinksModal.hide()
addNotification({
title: 'Success',
text: "Bulk edited selected project's links.",
type: 'success',
})
this.selectedProjects = []
this.editLinks.issues.val = ''
this.editLinks.source.val = ''
this.editLinks.wiki.val = ''
this.editLinks.discord.val = ''
this.editLinks.issues.clear = false
this.editLinks.source.clear = false
this.editLinks.wiki.clear = false
this.editLinks.discord.clear = false
} catch (e) {
addNotification({
title: 'An error occurred',
text: e,
type: 'error',
})
}
},
},
}) })
const editLinksModal = ref(null)
const modal_creation = ref(null)
function updateSort(list, sort, desc) {
let sortedArray = list
switch (sort) {
case 'Name':
sortedArray = list.slice().sort((a, b) => a.title.localeCompare(b.title))
break
case 'Status':
sortedArray = list.slice().sort((a, b) => {
if (a.status < b.status) return -1
if (a.status > b.status) return 1
return 0
})
break
case 'Type':
sortedArray = list.slice().sort((a, b) => {
if (a.project_type < b.project_type) return -1
if (a.project_type > b.project_type) return 1
return 0
})
break
default:
break
}
if (desc) sortedArray = sortedArray.reverse()
return sortedArray
}
function resort() {
projects.value = updateSort(projects.value, sortBy.value, descending.value)
}
function updateDescending() {
descending.value = !descending.value
resort()
}
async function bulkEditLinks() {
try {
const baseData = {
issues_url: editLinks.issues.clear ? null : editLinks.issues.val.trim(),
source_url: editLinks.source.clear ? null : editLinks.source.val.trim(),
wiki_url: editLinks.wiki.clear ? null : editLinks.wiki.val.trim(),
discord_url: editLinks.discord.clear ? null : editLinks.discord.val.trim(),
}
const filteredData = Object.fromEntries(Object.entries(baseData).filter(([, v]) => v !== ''))
await useBaseFetch(`projects?ids=${JSON.stringify(selectedProjects.value.map((x) => x.id))}`, {
method: 'PATCH',
body: filteredData,
})
editLinksModal.value?.hide()
addNotification({
title: 'Success',
text: "Bulk edited selected project's links.",
type: 'success',
})
selectedProjects.value = []
editLinks.issues.val = ''
editLinks.source.val = ''
editLinks.wiki.val = ''
editLinks.discord.val = ''
editLinks.issues.clear = false
editLinks.source.clear = false
editLinks.wiki.clear = false
editLinks.discord.clear = false
} catch (e) {
addNotification({
title: 'An error occurred',
text: e,
type: 'error',
})
}
}
await initUserProjects()
if (user.value?.projects) {
projects.value = updateSort(user.value.projects, 'Name', false)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.grid-table { .grid-table {

View File

@@ -101,7 +101,7 @@
email email
{{ auth.user.payout_data.paypal_address }} {{ auth.user.payout_data.paypal_address }}
</p> </p>
<button class="btn mt-4" @click="removeAuthProvider('paypal')"> <button class="btn mt-4" @click="handleRemoveAuthProvider('paypal')">
<XIcon /> <XIcon />
Disconnect account Disconnect account
</button> </button>
@@ -154,7 +154,9 @@ import { formatDate } from '@modrinth/utils'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { computed } from 'vue' import { computed } from 'vue'
const { addNotification } = injectNotificationManager() import { removeAuthProvider } from '~/composables/auth.js'
const { addNotification, handleError } = injectNotificationManager()
const auth = await useAuth() const auth = await useAuth()
const minWithdraw = ref(0.01) const minWithdraw = ref(0.01)
@@ -170,6 +172,14 @@ const deadlineEnding = computed(() => {
return deadline return deadline
}) })
async function handleRemoveAuthProvider(provider) {
try {
await removeAuthProvider(provider)
} catch (error) {
handleError(error)
}
}
const availableSoonDates = computed(() => { const availableSoonDates = computed(() => {
// Get the next 3 dates from userBalance.dates that are from now to the deadline + 4 months to make sure we get all the pending ones. // Get the next 3 dates from userBalance.dates that are from now to the deadline + 4 months to make sure we get all the pending ones.
const dates = Object.keys(userBalance.value.dates) const dates = Object.keys(userBalance.value.dates)

View File

@@ -62,10 +62,13 @@ const showPreviewImage = (files) => {
const orgId = useRouteId() const orgId = useRouteId()
const onSaveChanges = useClientTry(async () => { const onSaveChanges = useClientTry(async () => {
if (hasChanges.value) { // Only PATCH organization details if there are actual field changes
await patchOrganization(orgId, patchData.value) const hasOrgFieldChanges = Object.keys(patchData.value).length > 0
if (hasOrgFieldChanges) {
await patchOrganization(patchData.value)
} }
// Handle icon deletion / upload separately
if (deletedIcon.value) { if (deletedIcon.value) {
await deleteIcon() await deleteIcon()
deletedIcon.value = false deletedIcon.value = false
@@ -74,6 +77,7 @@ const onSaveChanges = useClientTry(async () => {
icon.value = null icon.value = null
} }
// Always refresh after any change
await refreshOrganization() await refreshOrganization()
addNotification({ addNotification({

View File

@@ -294,9 +294,10 @@ import FilesUploadDragAndDrop from '~/components/ui/servers/FilesUploadDragAndDr
import FilesUploadDropdown from '~/components/ui/servers/FilesUploadDropdown.vue' import FilesUploadDropdown from '~/components/ui/servers/FilesUploadDropdown.vue'
import FilesUploadZipUrlModal from '~/components/ui/servers/FilesUploadZipUrlModal.vue' import FilesUploadZipUrlModal from '~/components/ui/servers/FilesUploadZipUrlModal.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts' import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import { handleError } from '~/composables/servers/modrinth-servers.ts' import { handleServersError } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager() const notifications = injectNotificationManager()
const { addNotification } = notifications
const flags = useFeatureFlags() const flags = useFeatureFlags()
const baseId = useId() const baseId = useId()
@@ -584,7 +585,7 @@ const extractItem = async (path: string) => {
await props.server.fs?.extractFile(path, true, false) await props.server.fs?.extractFile(path, true, false)
} catch (error) { } catch (error) {
console.error('Error extracting item:', error) console.error('Error extracting item:', error)
handleError(error) handleServersError(error, notifications)
} }
} }
@@ -598,11 +599,11 @@ const handleExtractItem = async (item: { name: string; type: string; path: strin
uploadConflictModal.value.show(item.path, dry.conflicting_files) uploadConflictModal.value.show(item.path, dry.conflicting_files)
} }
} else { } else {
handleError(new Error('Error running dry run')) handleServersError(new Error('Error running dry run'), notifications)
} }
} catch (error) { } catch (error) {
console.error('Error extracting item:', error) console.error('Error extracting item:', error)
handleError(error) handleServersError(error, notifications)
} }
} }

View File

@@ -267,7 +267,7 @@
<button <button
v-if="auth.user.auth_providers.includes(provider.id)" v-if="auth.user.auth_providers.includes(provider.id)"
class="btn" class="btn"
@click="removeAuthProvider(provider.id)" @click="handleRemoveAuthProvider(provider.id)"
> >
<TrashIcon /> Remove <TrashIcon /> Remove
</button> </button>
@@ -432,6 +432,7 @@ import SteamIcon from 'assets/icons/auth/sso-steam.svg'
import QrcodeVue from 'qrcode.vue' import QrcodeVue from 'qrcode.vue'
import Modal from '~/components/ui/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import { removeAuthProvider } from '~/composables/auth.js'
useHead({ useHead({
title: 'Account settings - Modrinth', title: 'Account settings - Modrinth',
@@ -471,6 +472,14 @@ async function saveEmail() {
stopLoading() stopLoading()
} }
async function handleRemoveAuthProvider(provider) {
try {
await removeAuthProvider(provider)
} catch (error) {
handleError(error)
}
}
const managePasswordModal = ref() const managePasswordModal = ref()
const removePasswordMode = ref(false) const removePasswordMode = ref(false)
const oldPassword = ref('') const oldPassword = ref('')

View File

@@ -60,7 +60,7 @@ export class OrganizationContext {
const EDIT_DETAILS = 1 << 2 const EDIT_DETAILS = 1 << 2
return ( return (
this.currentMember.value && this.currentMember.value &&
(this.currentMember.value.permissions & EDIT_DETAILS) === EDIT_DETAILS (this.currentMember.value.permissions! & EDIT_DETAILS) === EDIT_DETAILS
) )
}) })
@@ -89,7 +89,9 @@ export class OrganizationContext {
}) })
} }
public patchOrganization = async (newData: { slug: any }) => { public patchOrganization = async (
newData: Partial<{ slug: string; name: string; description: string }>,
) => {
if (this.organization.value === null) { if (this.organization.value === null) {
throw new Error('Organization is not set.') throw new Error('Organization is not set.')
} }