From 56536abb2454a0a0588556f81159721fd0736511 Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Fri, 19 Jun 2026 21:23:35 +0100 Subject: [PATCH] fix: modal shift on close + readd settings preload (#6448) * fix: NewModal layout shift on close * fix: preload queries in settings modals * fix: any --- .../InstallationSettings.vue | 126 +++++++++--------- .../ui/modal/InstanceSettingsModal.vue | 51 +++++-- apps/app-frontend/src/pages/instance/Mods.vue | 30 ++++- packages/ui/src/components/modal/NewModal.vue | 8 +- .../servers/ServerSettingsModal.vue | 19 +-- .../components/modals/ModpackContentModal.vue | 14 +- .../shared/installation-settings/layout.vue | 9 +- .../providers/installation-settings.ts | 2 +- .../wrapped/hosting/manage/content.vue | 10 +- 9 files changed, 178 insertions(+), 91 deletions(-) diff --git a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue index a9edf12ef3..df4bf75f04 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue @@ -13,7 +13,7 @@ import { } from '@modrinth/ui' import type { GameVersionTag, PlatformTag } from '@modrinth/utils' import { useQuery, useQueryClient } from '@tanstack/vue-query' -import { computed, ref, shallowRef } from 'vue' +import { computed, ref } from 'vue' import { trackEvent } from '@/helpers/analytics' import { get_project_versions, get_version } from '@/helpers/cache' @@ -46,55 +46,62 @@ debug('metadata load: start', { installStage: instance.value.install_stage, }) -const [ - fabric_versions, - forge_versions, - quilt_versions, - neoforge_versions, - all_game_versions, - loaders, -] = await Promise.all([ - get_loader_versions('fabric') - .then((manifest: Manifest) => shallowRef(manifest)) - .catch(handleError), - get_loader_versions('forge') - .then((manifest: Manifest) => shallowRef(manifest)) - .catch(handleError), - get_loader_versions('quilt') - .then((manifest: Manifest) => shallowRef(manifest)) - .catch(handleError), - get_loader_versions('neo') - .then((manifest: Manifest) => shallowRef(manifest)) - .catch(handleError), - get_game_versions() - .then((gameVersions: GameVersionTag[]) => shallowRef(gameVersions)) - .catch(handleError), - get_loaders() - .then((value: PlatformTag[]) => - value - .filter( - (item) => item.supported_project_types.includes('modpack') || item.name === 'vanilla', - ) - .sort((a, b) => (a.name === 'vanilla' ? -1 : b.name === 'vanilla' ? 1 : 0)), - ) - .then((loader: PlatformTag[]) => ref(loader)) - .catch(handleError), -]) +function getSupportedModpackLoaders() { + return get_loaders().then((value: PlatformTag[]) => + value + .filter((item) => item.supported_project_types.includes('modpack') || item.name === 'vanilla') + .sort((a, b) => (a.name === 'vanilla' ? -1 : b.name === 'vanilla' ? 1 : 0)), + ) +} -debug('metadata load: done', { - hasFabricManifest: !!fabric_versions?.value, - hasForgeManifest: !!forge_versions?.value, - hasQuiltManifest: !!quilt_versions?.value, - hasNeoforgeManifest: !!neoforge_versions?.value, - gameVersions: all_game_versions?.value?.length ?? 0, - availablePlatforms: loaders?.value?.map((loader) => loader.name) ?? [], +const fabricVersionsQuery = useQuery({ + queryKey: ['instance-settings', 'loader-versions', 'fabric'], + queryFn: () => get_loader_versions('fabric') as Promise, +}) +const forgeVersionsQuery = useQuery({ + queryKey: ['instance-settings', 'loader-versions', 'forge'], + queryFn: () => get_loader_versions('forge') as Promise, +}) +const quiltVersionsQuery = useQuery({ + queryKey: ['instance-settings', 'loader-versions', 'quilt'], + queryFn: () => get_loader_versions('quilt') as Promise, +}) +const neoforgeVersionsQuery = useQuery({ + queryKey: ['instance-settings', 'loader-versions', 'neo'], + queryFn: () => get_loader_versions('neo') as Promise, +}) +const gameVersionsQuery = useQuery({ + queryKey: ['instance-settings', 'game-versions'], + queryFn: () => get_game_versions() as Promise, +}) +const loadersQuery = useQuery({ + queryKey: ['instance-settings', 'loaders', 'modpack'], + queryFn: getSupportedModpackLoaders, }) -const { data: modpackInfo } = useQuery({ +const metadataLoading = computed(() => + [ + fabricVersionsQuery, + forgeVersionsQuery, + quiltVersionsQuery, + neoforgeVersionsQuery, + gameVersionsQuery, + loadersQuery, + ].some((query) => query.isLoading.value), +) + +debug('metadata queries configured', { + instancePath: instance.value.path, + loader: instance.value.loader, + gameVersion: instance.value.game_version, +}) + +const modpackInfoQuery = useQuery({ queryKey: computed(() => ['linkedModpackInfo', instance.value.path]), queryFn: () => get_linked_modpack_info(instance.value.path, 'must_revalidate'), enabled: computed(() => !!instance.value.linked_data?.project_id && !offline), }) +const modpackInfo = modpackInfoQuery.data const repairing = ref(false) const reinstalling = ref(false) @@ -107,17 +114,17 @@ const messages = defineMessages({ }) function getManifest(loader: string) { - const map: Record = { - fabric: fabric_versions, - forge: forge_versions, - quilt: quilt_versions, - neoforge: neoforge_versions, + const map: Record = { + fabric: fabricVersionsQuery.data.value, + forge: forgeVersionsQuery.data.value, + quilt: quiltVersionsQuery.data.value, + neoforge: neoforgeVersionsQuery.data.value, } const manifest = map[loader] debug('getManifest:', { loader, - hasManifest: !!manifest?.value, - gameVersions: manifest?.value?.gameVersions?.length ?? 0, + hasManifest: !!manifest, + gameVersions: manifest?.gameVersions?.length ?? 0, }) return manifest } @@ -143,7 +150,7 @@ provideAppBackup({ provideInstallationSettings({ closeSettings: closeModal, - loading: ref(false), + loading: computed(() => metadataLoading.value || modpackInfoQuery.isLoading.value), installationInfo: computed(() => { const rows = [ { @@ -185,14 +192,14 @@ provideInstallationSettings({ currentPlatform: computed(() => instance.value.loader), currentGameVersion: computed(() => instance.value.game_version), currentLoaderVersion: computed(() => instance.value.loader_version ?? ''), - availablePlatforms: loaders?.value?.map((x) => x.name) ?? [], + availablePlatforms: computed(() => loadersQuery.data.value?.map((x) => x.name) ?? []), resolveGameVersions(loader, showSnapshots) { - const versions = all_game_versions?.value ?? [] + const versions = gameVersionsQuery.data.value ?? [] const filtered = versions.filter((item) => { if (loader === 'vanilla') return true const manifest = getManifest(loader) - return !!manifest?.value?.gameVersions?.some((x) => item.version === x.id) + return !!manifest?.gameVersions?.some((x) => item.version === x.id) }) const result = ( showSnapshots ? filtered : filtered.filter((x) => x.version_type === 'release') @@ -213,12 +220,12 @@ provideInstallationSettings({ return [] } const manifest = getManifest(loader) - if (!manifest?.value) { + if (!manifest) { debug('resolveLoaderVersions: no manifest', { loader, gameVersion }) return [] } if (loader === 'fabric' || loader === 'quilt') { - const result = manifest.value.gameVersions[0]?.loaders ?? [] + const result = manifest.gameVersions[0]?.loaders ?? [] debug('resolveLoaderVersions: fabric/quilt result', { loader, gameVersion, @@ -226,14 +233,13 @@ provideInstallationSettings({ }) return result } - const result = - manifest.value.gameVersions?.find((item) => item.id === gameVersion)?.loaders ?? [] + const result = manifest.gameVersions?.find((item) => item.id === gameVersion)?.loaders ?? [] debug('resolveLoaderVersions: result', { loader, gameVersion, count: result.length }) return result }, resolveHasSnapshots(loader) { - const versions = all_game_versions?.value ?? [] + const versions = gameVersionsQuery.data.value ?? [] if (loader === 'vanilla') { const result = versions.some((x) => x.version_type !== 'release') debug('resolveHasSnapshots: vanilla', { loader, result }) @@ -241,7 +247,7 @@ provideInstallationSettings({ } const manifest = getManifest(loader) const supported = versions.filter( - (item) => !!manifest?.value?.gameVersions?.some((x) => item.version === x.id), + (item) => !!manifest?.gameVersions?.some((x) => item.version === x.id), ) const result = supported.some((x) => x.version_type !== 'release') debug('resolveHasSnapshots:', { diff --git a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue index e5253948c7..dc749c295b 100644 --- a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue +++ b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue @@ -16,7 +16,8 @@ import { type TabbedModalTab, useVIntl, } from '@modrinth/ui' -import { useQueryClient } from '@tanstack/vue-query' +import type { PlatformTag } from '@modrinth/utils' +import { useQuery } from '@tanstack/vue-query' import { convertFileSrc } from '@tauri-apps/api/core' import { computed, nextTick, ref, watch } from 'vue' @@ -26,7 +27,9 @@ import InstallationSettings from '@/components/ui/instance_settings/Installation import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue' import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue' import { get_project_v3 } from '@/helpers/cache' +import { get_loader_versions } from '@/helpers/metadata' import { get_linked_modpack_info } from '@/helpers/profile' +import { get_game_versions, get_loaders } from '@/helpers/tags' import { provideInstanceSettings } from '@/providers/instance-settings' import type { GameInstance } from '../../../helpers/types' @@ -45,7 +48,6 @@ const isMinecraftServer = ref(false) const handleUnlinked = () => emit('unlinked') const instanceRef = computed(() => props.instance) -const queryClient = useQueryClient() const tabbedModal = ref | null>(null) function hide() { @@ -120,13 +122,46 @@ const tabs = computed(() => [ }, ]) +function getSupportedModpackLoaders() { + return get_loaders().then((value: PlatformTag[]) => + value + .filter((item) => item.supported_project_types.includes('modpack') || item.name === 'vanilla') + .sort((a, b) => (a.name === 'vanilla' ? -1 : b.name === 'vanilla' ? 1 : 0)), + ) +} + +// Preload +useQuery({ + queryKey: ['instance-settings', 'loader-versions', 'fabric'], + queryFn: () => get_loader_versions('fabric'), +}) +useQuery({ + queryKey: ['instance-settings', 'loader-versions', 'forge'], + queryFn: () => get_loader_versions('forge'), +}) +useQuery({ + queryKey: ['instance-settings', 'loader-versions', 'quilt'], + queryFn: () => get_loader_versions('quilt'), +}) +useQuery({ + queryKey: ['instance-settings', 'loader-versions', 'neo'], + queryFn: () => get_loader_versions('neo'), +}) +useQuery({ + queryKey: ['instance-settings', 'game-versions'], + queryFn: get_game_versions, +}) +useQuery({ + queryKey: ['instance-settings', 'loaders', 'modpack'], + queryFn: getSupportedModpackLoaders, +}) +useQuery({ + queryKey: computed(() => ['linkedModpackInfo', props.instance.path]), + queryFn: () => get_linked_modpack_info(props.instance.path, 'stale_while_revalidate'), + enabled: computed(() => !!props.instance.linked_data?.project_id && !props.offline), +}) + function show(tabIndex?: number) { - if (props.instance.linked_data?.project_id) { - queryClient.prefetchQuery({ - queryKey: ['linkedModpackInfo', props.instance.path], - queryFn: () => get_linked_modpack_info(props.instance.path, 'stale_while_revalidate'), - }) - } tabbedModal.value?.show() if (tabIndex !== undefined) { nextTick(() => tabbedModal.value?.setTab(tabIndex)) diff --git a/apps/app-frontend/src/pages/instance/Mods.vue b/apps/app-frontend/src/pages/instance/Mods.vue index 41af9d55be..3e100a417d 100644 --- a/apps/app-frontend/src/pages/instance/Mods.vue +++ b/apps/app-frontend/src/pages/instance/Mods.vue @@ -90,6 +90,7 @@ import { useVIntl, versionChangesGameVersion, } from '@modrinth/ui' +import { useQuery, useQueryClient } from '@tanstack/vue-query' import { getCurrentWebview } from '@tauri-apps/api/webview' import { open } from '@tauri-apps/plugin-dialog' import { openUrl } from '@tauri-apps/plugin-opener' @@ -153,6 +154,7 @@ const { formatMessage } = useVIntl() const { handleError, addNotification } = injectNotificationManager() const { installingItems } = injectContentInstall() const router = useRouter() +const queryClient = useQueryClient() const debug = useDebugLogger('Mods:ContentUpdate') const props = defineProps<{ @@ -212,6 +214,13 @@ const contentUpdaterModal = ref | null> const modpackContentModal = ref | null>() const modpackUpdateConfirmModal = ref | null>() +const modpackContentQueryKey = computed(() => ['linkedModpackContent', props.instance.path]) +const modpackContentQuery = useQuery({ + queryKey: modpackContentQueryKey, + queryFn: () => get_linked_modpack_content(props.instance.path), + enabled: computed(() => !!props.instance?.path && !!props.instance?.linked_data), +}) + // TODO: Extract content operation and updater modal state into composables; this page currently owns file mutations, dependency installs, busy flags, and version selection flow. const updatingProject = ref(null) const updatingProjectVersions = ref([]) @@ -740,13 +749,19 @@ async function handleModpackContentBulkToggle(items: ContentItem[]) { async function handleModpackContent() { if (!props.instance?.path) return + if (modpackContentQuery.data.value !== undefined) { + modpackContentModal.value?.show(modpackContentQuery.data.value) + return + } + modpackContentModal.value?.showLoading() - const contentItems = await get_linked_modpack_content(props.instance.path).catch(handleError) + const { data, error } = await modpackContentQuery.refetch() - if (contentItems) { - modpackContentModal.value?.show(contentItems) + if (data !== undefined) { + modpackContentModal.value?.show(data) } else { + if (error) handleError(error) modpackContentModal.value?.hide() } } @@ -754,9 +769,12 @@ async function handleModpackContent() { async function refreshModpackContentItems(cacheBehaviour?: CacheBehaviour) { if (!props.instance?.path) return - const contentItems = await get_linked_modpack_content(props.instance.path, cacheBehaviour).catch( - handleError, - ) + const contentItems = await queryClient + .fetchQuery({ + queryKey: modpackContentQueryKey.value, + queryFn: () => get_linked_modpack_content(props.instance.path, cacheBehaviour), + }) + .catch(handleError) if (contentItems) { modpackContentModal.value?.setItems(contentItems) diff --git a/packages/ui/src/components/modal/NewModal.vue b/packages/ui/src/components/modal/NewModal.vue index 40d873b333..04be73d0f2 100644 --- a/packages/ui/src/components/modal/NewModal.vue +++ b/packages/ui/src/components/modal/NewModal.vue @@ -268,6 +268,7 @@ function hide() { return } props.onHide?.() + resetMousePosition() visible.value = false popModal() if (modalStackSize() === 0) { @@ -309,6 +310,11 @@ function updateMousePosition(event: { clientX: number; clientY: number }) { mouseY.value = event.clientY } +function resetMousePosition() { + mouseX.value = Math.round(window.innerWidth / 2) + mouseY.value = Math.round(window.innerHeight / 2) +} + onUnmounted(() => { if (open.value) { popModal() @@ -325,8 +331,6 @@ function handleWindowKeyDown(event: KeyboardEvent) { if (props.closeOnEsc && event.key === 'Escape' && props.closable) { if (!isTopmostModal()) return hide() - mouseX.value = Math.round(window.innerWidth / 2) - mouseY.value = Math.round(window.innerHeight / 2) } } diff --git a/packages/ui/src/components/servers/ServerSettingsModal.vue b/packages/ui/src/components/servers/ServerSettingsModal.vue index a72001c73b..d63706a963 100644 --- a/packages/ui/src/components/servers/ServerSettingsModal.vue +++ b/packages/ui/src/components/servers/ServerSettingsModal.vue @@ -1,7 +1,7 @@