diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index e2f78fe965..0830a814f9 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -1545,8 +1545,10 @@ provideAppUpdateDownloadProgress(appUpdateDownload) @@ -1570,7 +1572,11 @@ provideAppUpdateDownloadProgress(appUpdateDownload) v-tooltip.right="formatMessage(messages.modrinthHosting)" to="/hosting/manage" :is-primary="(r) => r.path === '/hosting/manage' || r.path === '/hosting/manage/'" - :is-subpage="(r) => r.path.startsWith('/hosting/manage/') && r.path !== '/hosting/manage/'" + :is-subpage=" + (r) => + (r.path.startsWith('/hosting/manage/') && r.path !== '/hosting/manage/') || + ((r.path.startsWith('/browse') || r.path.startsWith('/project')) && r.query.sid) + " > diff --git a/apps/app-frontend/src/pages/Browse.vue b/apps/app-frontend/src/pages/Browse.vue index 8f166430fd..730a69fccb 100644 --- a/apps/app-frontend/src/pages/Browse.vue +++ b/apps/app-frontend/src/pages/Browse.vue @@ -57,11 +57,7 @@ import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata' import { get as getSettings, set as setSettings } from '@/helpers/settings.ts' import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags' import { get_instance_worlds } from '@/helpers/worlds' -import { - type BreadcrumbDefinition, - useBreadcrumb, - useRootBreadcrumb, -} from '@/providers/breadcrumbs' +import { type BreadcrumbDefinition, injectBreadcrumbManager } from '@/providers/breadcrumbs' import { injectContentInstall } from '@/providers/content-install' import { injectServerInstall } from '@/providers/server-install' import { @@ -179,45 +175,46 @@ const hiddenInstanceProjectIds = ref>(new Set()) const hiddenInstanceProjectIdsInitialized = ref(false) const isServerInstance = ref(false) -const instanceBreadcrumb = route.query.i - ? useBreadcrumb({ - slot: 'instance', - id: () => `instance:${String(displayedBrowseRoute.value.query.i ?? '')}`, - label: () => instance.value?.name ?? formatMessage(commonMessages.loadingLabel), - visual: () => ({ - type: 'image', - src: instance.value?.icon_path ? convertFileSrc(instance.value.icon_path) : undefined, - alt: instance.value?.name, - tintBy: String(displayedBrowseRoute.value.query.i ?? ''), - }), - to: () => { - const instancePath = `/instance/${encodeURIComponent( - String(displayedBrowseRoute.value.query.i ?? ''), - )}` - return displayedBrowseRoute.value.query.from === 'worlds' - ? `${instancePath}/worlds` - : instancePath - }, - }) - : undefined +const breadcrumbManager = injectBreadcrumbManager() +const instanceBreadcrumbDefinition = { + slot: 'instance', + id: () => `instance:${String(displayedBrowseRoute.value.query.i ?? '')}`, + label: () => instance.value?.name ?? formatMessage(commonMessages.loadingLabel), + visual: () => ({ + type: 'image' as const, + src: instance.value?.icon_path ? convertFileSrc(instance.value.icon_path) : undefined, + alt: instance.value?.name, + tintBy: String(displayedBrowseRoute.value.query.i ?? ''), + }), + to: () => { + const instancePath = `/instance/${encodeURIComponent( + String(displayedBrowseRoute.value.query.i ?? ''), + )}` + return displayedBrowseRoute.value.query.from === 'worlds' + ? `${instancePath}/worlds` + : instancePath + }, +} satisfies BreadcrumbDefinition +const serversBreadcrumbDefinition = { + slot: 'root', + id: 'servers', + label: () => formatMessage(commonMessages.serversLabel), + to: '/hosting/manage/', + visual: { type: 'icon', component: ServerStackIcon }, +} satisfies BreadcrumbDefinition const serverBreadcrumbTo = ref(serverBackUrl.value) watch(serverBackUrl, (value) => { if (route.path.startsWith('/browse/')) { serverBreadcrumbTo.value = value } }) -const serverBreadcrumb = - !instanceBreadcrumb && serverIdQuery.value - ? useBreadcrumb({ - slot: 'server', - id: () => `server:${String(displayedBrowseRoute.value.query.sid ?? '')}`, - label: () => - serverContextServerData.value?.name ?? formatMessage(commonMessages.loadingLabel), - visual: { type: 'icon', component: ServerStackIcon }, - to: serverBreadcrumbTo, - }) - : undefined -const breadcrumbParent = instanceBreadcrumb ?? serverBreadcrumb +const serverBreadcrumbDefinition = { + slot: 'server', + id: () => `server:${String(displayedBrowseRoute.value.query.sid ?? '')}`, + label: () => serverContextServerData.value?.name ?? formatMessage(commonMessages.loadingLabel), + visual: { type: 'icon', component: ServerStackIcon }, + to: serverBreadcrumbTo, +} satisfies BreadcrumbDefinition const breadcrumbDefinition = { slot: 'browse', id: () => @@ -230,9 +227,27 @@ const breadcrumbDefinition = { to: () => displayedBrowseRoute.value.fullPath, visual: { type: 'icon', component: CompassIcon }, } satisfies BreadcrumbDefinition -const browseBreadcrumb = breadcrumbParent - ? useBreadcrumb(breadcrumbDefinition, { parent: breadcrumbParent }) - : useRootBreadcrumb(breadcrumbDefinition) + +function syncBreadcrumbs() { + if (displayedBrowseRoute.value.query.i) { + const instanceBreadcrumb = breadcrumbManager.reset(instanceBreadcrumbDefinition) + breadcrumbManager.push(breadcrumbDefinition, { parent: instanceBreadcrumb }) + return + } + + if (displayedBrowseRoute.value.query.sid) { + const serversBreadcrumb = breadcrumbManager.reset(serversBreadcrumbDefinition) + const serverBreadcrumb = breadcrumbManager.push(serverBreadcrumbDefinition, { + parent: serversBreadcrumb, + }) + breadcrumbManager.push(breadcrumbDefinition, { parent: serverBreadcrumb }) + return + } + + breadcrumbManager.reset(breadcrumbDefinition) +} + +watch(displayedBrowseRoute, syncBreadcrumbs, { immediate: true, flush: 'sync' }) debugLog('fetching tags (categories, loaders, gameVersions)') const [categories, loaders, availableGameVersions] = await Promise.all([ @@ -587,7 +602,6 @@ function resetInstanceContext() { hiddenInstanceProjectIds.value = new Set() hiddenInstanceProjectIdsInitialized.value = false isServerInstance.value = false - browseBreadcrumb.reset() void refreshInstalledProjectIds() } diff --git a/apps/app-frontend/src/providers/setup/server-install-content.ts b/apps/app-frontend/src/providers/setup/server-install-content.ts index c48b90f2c1..c2556c8937 100644 --- a/apps/app-frontend/src/providers/setup/server-install-content.ts +++ b/apps/app-frontend/src/providers/setup/server-install-content.ts @@ -222,7 +222,29 @@ export function createServerInstallContent(opts: { const isSetupServerContext = computed(() => !!serverIdQuery.value && !!serverFlowFrom.value) const serverContextWorldId = ref(worldIdQuery.value) - const serverContextServerData = ref(null) + + function getCachedServer(serverId: string): Archon.Servers.v0.Server | null { + return ( + queryClient.getQueryData(['servers', 'detail', serverId]) ?? + queryClient + .getQueryData(['servers']) + ?.servers.find((server) => server.server_id === serverId) ?? + null + ) + } + + async function ensureServer(serverId: string): Promise { + return queryClient.ensureQueryData({ + queryKey: ['servers', 'detail', serverId], + queryFn: () => client.archon.servers_v0.get(serverId), + staleTime: 30_000, + }) + } + + const initialServerId = serverIdQuery.value + const serverContextServerData = ref( + initialServerId ? getCachedServer(initialServerId) : null, + ) const serverContentProjectIds = ref>(new Set()) const serverContentInstallKeys = ref>(new Set()) const queuedServerInstalls = ref>>( @@ -277,6 +299,9 @@ export function createServerInstallContent(opts: { async function refreshServerInstalledContent(serverId: string, worldId: string) { try { const content = await client.archon.content_v1.getAddons(serverId, worldId) + if (serverIdQuery.value !== serverId || effectiveServerWorldId.value !== worldId) { + return + } const ids = new Set( (content.addons ?? []) .map((addon) => addon.project_id) @@ -297,14 +322,20 @@ export function createServerInstallContent(opts: { if (!sid) return try { - serverContextServerData.value = await client.archon.servers_v0.get(sid) + const server = await ensureServer(sid) + if (serverIdQuery.value === sid) { + serverContextServerData.value = server + } } catch (err) { handleError(err as Error) } + if (serverIdQuery.value !== sid) return + let resolvedWorldId = effectiveServerWorldId.value if (!resolvedWorldId) { resolvedWorldId = await resolveServerContextWorldId(sid) + if (serverIdQuery.value !== sid) return if (resolvedWorldId) { serverContextWorldId.value = resolvedWorldId } @@ -319,6 +350,7 @@ export function createServerInstallContent(opts: { function watchServerContextChanges() { watch([serverIdQuery, effectiveServerWorldId], async ([sid, wid], [prevSid, prevWid]) => { if (!sid) { + serverContextWorldId.value = null serverContextServerData.value = null serverContentProjectIds.value = new Set() serverContentInstallKeys.value = new Set() @@ -330,8 +362,12 @@ export function createServerInstallContent(opts: { serverContentProjectIds.value = new Set() serverContentInstallKeys.value = new Set() queuedServerInstalls.value = readStoredServerInstallQueue(sid, wid) + serverContextServerData.value = getCachedServer(sid) try { - serverContextServerData.value = await client.archon.servers_v0.get(sid) + const server = await ensureServer(sid) + if (serverIdQuery.value === sid) { + serverContextServerData.value = server + } } catch (err) { handleError(err as Error) }