fix: breadcrumb issue on browse content via server panel (#6971)

fix: breadcrumb issue on browse/navigation bar

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
Calum H.
2026-08-04 21:32:52 +00:00
committed by GitHub
co-authored by Prospector
parent 36857a167b
commit 540b3269df
3 changed files with 105 additions and 47 deletions
+9 -3
View File
@@ -1547,8 +1547,10 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
<NavButton <NavButton
v-tooltip.right="formatMessage(commonMessages.discoverContentLabel)" v-tooltip.right="formatMessage(commonMessages.discoverContentLabel)"
to="/browse/modpack" to="/browse/modpack"
:is-primary="() => route.path.startsWith('/browse') && !route.query.i" :is-primary="() => route.path.startsWith('/browse') && !route.query.i && !route.query.sid"
:is-subpage="(route) => route.path.startsWith('/project') && !route.query.i" :is-subpage="
(route) => route.path.startsWith('/project') && !route.query.i && !route.query.sid
"
> >
<CompassIcon /> <CompassIcon />
</NavButton> </NavButton>
@@ -1572,7 +1574,11 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
v-tooltip.right="formatMessage(messages.modrinthHosting)" v-tooltip.right="formatMessage(messages.modrinthHosting)"
to="/hosting/manage" to="/hosting/manage"
:is-primary="(r) => r.path === '/hosting/manage' || r.path === '/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)
"
> >
<ServerStackIcon /> <ServerStackIcon />
</NavButton> </NavButton>
+57 -41
View File
@@ -56,11 +56,7 @@ import {
instanceKeys, instanceKeys,
instanceLinkedProjectQueryOptions, instanceLinkedProjectQueryOptions,
} from '@/pages/instance/query-options' } from '@/pages/instance/query-options'
import { import { type BreadcrumbDefinition, injectBreadcrumbManager } from '@/providers/breadcrumbs'
type BreadcrumbDefinition,
useBreadcrumb,
useRootBreadcrumb,
} from '@/providers/breadcrumbs'
import { injectContentInstall } from '@/providers/content-install' import { injectContentInstall } from '@/providers/content-install'
import { injectServerInstall } from '@/providers/server-install' import { injectServerInstall } from '@/providers/server-install'
import { import {
@@ -177,45 +173,46 @@ const isServerInstance = computed(
() => linkedInstanceProjectQuery.data.value?.minecraft_server != null, () => linkedInstanceProjectQuery.data.value?.minecraft_server != null,
) )
const instanceBreadcrumb = route.query.i const breadcrumbManager = injectBreadcrumbManager()
? useBreadcrumb({ const instanceBreadcrumbDefinition = {
slot: 'instance', slot: 'instance',
id: () => `instance:${String(displayedBrowseRoute.value.query.i ?? '')}`, id: () => `instance:${String(displayedBrowseRoute.value.query.i ?? '')}`,
label: () => instance.value?.name ?? formatMessage(commonMessages.loadingLabel), label: () => instance.value?.name ?? formatMessage(commonMessages.loadingLabel),
visual: () => ({ visual: () => ({
type: 'image', type: 'image' as const,
src: instance.value?.icon_path ? convertFileSrc(instance.value.icon_path) : undefined, src: instance.value?.icon_path ? convertFileSrc(instance.value.icon_path) : undefined,
alt: instance.value?.name, alt: instance.value?.name,
tintBy: String(displayedBrowseRoute.value.query.i ?? ''), tintBy: String(displayedBrowseRoute.value.query.i ?? ''),
}), }),
to: () => { to: () => {
const instancePath = `/instance/${encodeURIComponent( const instancePath = `/instance/${encodeURIComponent(
String(displayedBrowseRoute.value.query.i ?? ''), String(displayedBrowseRoute.value.query.i ?? ''),
)}` )}`
return displayedBrowseRoute.value.query.from === 'worlds' return displayedBrowseRoute.value.query.from === 'worlds'
? `${instancePath}/worlds` ? `${instancePath}/worlds`
: instancePath : instancePath
}, },
}) } satisfies BreadcrumbDefinition
: undefined 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) const serverBreadcrumbTo = ref(serverBackUrl.value)
watch(serverBackUrl, (value) => { watch(serverBackUrl, (value) => {
if (route.path.startsWith('/browse/')) { if (route.path.startsWith('/browse/')) {
serverBreadcrumbTo.value = value serverBreadcrumbTo.value = value
} }
}) })
const serverBreadcrumb = const serverBreadcrumbDefinition = {
!instanceBreadcrumb && serverIdQuery.value slot: 'server',
? useBreadcrumb({ id: () => `server:${String(displayedBrowseRoute.value.query.sid ?? '')}`,
slot: 'server', label: () => serverContextServerData.value?.name ?? formatMessage(commonMessages.loadingLabel),
id: () => `server:${String(displayedBrowseRoute.value.query.sid ?? '')}`, visual: { type: 'icon', component: ServerStackIcon },
label: () => to: serverBreadcrumbTo,
serverContextServerData.value?.name ?? formatMessage(commonMessages.loadingLabel), } satisfies BreadcrumbDefinition
visual: { type: 'icon', component: ServerStackIcon },
to: serverBreadcrumbTo,
})
: undefined
const breadcrumbParent = instanceBreadcrumb ?? serverBreadcrumb
const breadcrumbDefinition = { const breadcrumbDefinition = {
slot: 'browse', slot: 'browse',
id: () => id: () =>
@@ -228,9 +225,27 @@ const breadcrumbDefinition = {
to: () => displayedBrowseRoute.value.fullPath, to: () => displayedBrowseRoute.value.fullPath,
visual: { type: 'icon', component: CompassIcon }, visual: { type: 'icon', component: CompassIcon },
} satisfies BreadcrumbDefinition } satisfies BreadcrumbDefinition
const browseBreadcrumb = breadcrumbParent
? useBreadcrumb(breadcrumbDefinition, { parent: breadcrumbParent }) function syncBreadcrumbs() {
: useRootBreadcrumb(breadcrumbDefinition) 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)') debugLog('fetching tags (categories, loaders, gameVersions)')
const [categories, loaders, availableGameVersions] = await Promise.all([ const [categories, loaders, availableGameVersions] = await Promise.all([
@@ -595,6 +610,7 @@ function resetInstanceContext() {
newlyInstalled.value = [] newlyInstalled.value = []
hiddenInstanceProjectIds.value = new Set() hiddenInstanceProjectIds.value = new Set()
hiddenInstanceProjectIdsInitialized.value = false hiddenInstanceProjectIdsInitialized.value = false
isServerInstance.value = false
browseBreadcrumb.reset() browseBreadcrumb.reset()
void refreshInstalledProjectIds() void refreshInstalledProjectIds()
} }
@@ -222,7 +222,29 @@ export function createServerInstallContent(opts: {
const isSetupServerContext = computed(() => !!serverIdQuery.value && !!serverFlowFrom.value) const isSetupServerContext = computed(() => !!serverIdQuery.value && !!serverFlowFrom.value)
const serverContextWorldId = ref<string | null>(worldIdQuery.value) const serverContextWorldId = ref<string | null>(worldIdQuery.value)
const serverContextServerData = ref<Archon.Servers.v0.Server | null>(null)
function getCachedServer(serverId: string): Archon.Servers.v0.Server | null {
return (
queryClient.getQueryData<Archon.Servers.v0.Server>(['servers', 'detail', serverId]) ??
queryClient
.getQueryData<Archon.Servers.v0.ServerGetResponse>(['servers'])
?.servers.find((server) => server.server_id === serverId) ??
null
)
}
async function ensureServer(serverId: string): Promise<Archon.Servers.v0.Server> {
return queryClient.ensureQueryData({
queryKey: ['servers', 'detail', serverId],
queryFn: () => client.archon.servers_v0.get(serverId),
staleTime: 30_000,
})
}
const initialServerId = serverIdQuery.value
const serverContextServerData = ref<Archon.Servers.v0.Server | null>(
initialServerId ? getCachedServer(initialServerId) : null,
)
const serverContentProjectIds = ref<Set<string>>(new Set()) const serverContentProjectIds = ref<Set<string>>(new Set())
const serverContentInstallKeys = ref<Set<string>>(new Set()) const serverContentInstallKeys = ref<Set<string>>(new Set())
const queuedServerInstalls = ref<Map<string, BrowseInstallPlan<InstallableSearchResult>>>( const queuedServerInstalls = ref<Map<string, BrowseInstallPlan<InstallableSearchResult>>>(
@@ -277,6 +299,9 @@ export function createServerInstallContent(opts: {
async function refreshServerInstalledContent(serverId: string, worldId: string) { async function refreshServerInstalledContent(serverId: string, worldId: string) {
try { try {
const content = await client.archon.content_v1.getAddons(serverId, worldId) const content = await client.archon.content_v1.getAddons(serverId, worldId)
if (serverIdQuery.value !== serverId || effectiveServerWorldId.value !== worldId) {
return
}
const ids = new Set( const ids = new Set(
(content.addons ?? []) (content.addons ?? [])
.map((addon) => addon.project_id) .map((addon) => addon.project_id)
@@ -297,14 +322,20 @@ export function createServerInstallContent(opts: {
if (!sid) return if (!sid) return
try { try {
serverContextServerData.value = await client.archon.servers_v0.get(sid) const server = await ensureServer(sid)
if (serverIdQuery.value === sid) {
serverContextServerData.value = server
}
} catch (err) { } catch (err) {
handleError(err as Error) handleError(err as Error)
} }
if (serverIdQuery.value !== sid) return
let resolvedWorldId = effectiveServerWorldId.value let resolvedWorldId = effectiveServerWorldId.value
if (!resolvedWorldId) { if (!resolvedWorldId) {
resolvedWorldId = await resolveServerContextWorldId(sid) resolvedWorldId = await resolveServerContextWorldId(sid)
if (serverIdQuery.value !== sid) return
if (resolvedWorldId) { if (resolvedWorldId) {
serverContextWorldId.value = resolvedWorldId serverContextWorldId.value = resolvedWorldId
} }
@@ -319,6 +350,7 @@ export function createServerInstallContent(opts: {
function watchServerContextChanges() { function watchServerContextChanges() {
watch([serverIdQuery, effectiveServerWorldId], async ([sid, wid], [prevSid, prevWid]) => { watch([serverIdQuery, effectiveServerWorldId], async ([sid, wid], [prevSid, prevWid]) => {
if (!sid) { if (!sid) {
serverContextWorldId.value = null
serverContextServerData.value = null serverContextServerData.value = null
serverContentProjectIds.value = new Set() serverContentProjectIds.value = new Set()
serverContentInstallKeys.value = new Set() serverContentInstallKeys.value = new Set()
@@ -330,8 +362,12 @@ export function createServerInstallContent(opts: {
serverContentProjectIds.value = new Set() serverContentProjectIds.value = new Set()
serverContentInstallKeys.value = new Set() serverContentInstallKeys.value = new Set()
queuedServerInstalls.value = readStoredServerInstallQueue(sid, wid) queuedServerInstalls.value = readStoredServerInstallQueue(sid, wid)
serverContextServerData.value = getCachedServer(sid)
try { try {
serverContextServerData.value = await client.archon.servers_v0.get(sid) const server = await ensureServer(sid)
if (serverIdQuery.value === sid) {
serverContextServerData.value = server
}
} catch (err) { } catch (err) {
handleError(err as Error) handleError(err as Error)
} }