feat: reshuffle layout for worlds

This commit is contained in:
Calum H. (IMB11)
2026-06-26 17:09:22 +01:00
parent 5d49e9fa53
commit f922145230
33 changed files with 591 additions and 131 deletions
+49
View File
@@ -495,6 +495,11 @@ const sidebarOverlayScrollbarsOptions = Object.freeze({
},
})
router.beforeEach(async (to) => {
const redirect = await resolveLegacyServerWorldTabRedirect(to)
if (redirect) return redirect
})
router.beforeEach(() => {
suspensePending = false
if (routerToken) loading.end(routerToken)
@@ -526,6 +531,50 @@ function onSuspensePending() {
suspenseToken = loading.begin()
}
async function resolveLegacyServerWorldTabRedirect(to) {
if (!['ServerManageContent', 'ServerManageFiles', 'ServerManageBackups'].includes(to.name)) {
return null
}
const serverId = getRouteParam(to.params.id)
if (!serverId) return null
const tabPath =
to.name === 'ServerManageFiles' ? '/files' : to.name === 'ServerManageBackups' ? '/backups' : ''
const worldsPath = `/hosting/manage/${encodeURIComponent(serverId)}/worlds`
try {
const serverFull = await tauriApiClient.archon.servers_v1.get(serverId)
const world = serverFull.worlds.find((item) => item.is_active) ?? serverFull.worlds[0]
if (world) {
return {
path: `${worldsPath}/${encodeURIComponent(world.id)}${tabPath}`,
query: to.query,
hash: to.hash,
replace: true,
}
}
} catch {
return {
path: worldsPath,
query: to.query,
hash: to.hash,
replace: true,
}
}
return {
path: worldsPath,
query: to.query,
hash: to.hash,
replace: true,
}
}
function getRouteParam(param) {
return Array.isArray(param) ? param[0] : param
}
function onSuspenseResolve() {
if (suspenseToken) {
loading.end(suspenseToken)
@@ -13,7 +13,7 @@ const queryClient = useQueryClient()
if (worldId.value) {
try {
await queryClient.ensureQueryData({
queryKey: ['backups', 'list', serverId],
queryKey: ['backups', 'list', serverId, worldId.value],
queryFn: () => client.archon.backups_v1.list(serverId, worldId.value!),
staleTime: 30_000,
})
@@ -13,7 +13,7 @@ const queryClient = useQueryClient()
if (worldId.value) {
try {
await queryClient.ensureQueryData({
queryKey: ['content', 'list', 'v1', serverId],
queryKey: ['content', 'list', 'v1', serverId, worldId.value],
queryFn: () =>
client.archon.content_v1.getAddons(serverId, worldId.value!, { from_modpack: false }),
staleTime: 30_000,
@@ -93,7 +93,7 @@ watch(
breadcrumbs.setName('Server', server.name)
breadcrumbs.setContext({
name: server.name,
link: `/hosting/manage/${serverId.value}/content`,
link: `/hosting/manage/${serverId.value}/worlds`,
})
}
},
@@ -0,0 +1,16 @@
<template>
<ServersManageWorldRootLayout>
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Suspense>
<component :is="Component" />
</Suspense>
</template>
</RouterView>
</ServersManageWorldRootLayout>
</template>
<script setup lang="ts">
import { ServersManageWorldRootLayout } from '@modrinth/ui'
import { RouterView } from 'vue-router'
</script>
@@ -4,6 +4,7 @@ import Content from './Content.vue'
import Files from './Files.vue'
import Index from './Index.vue'
import Overview from './Overview.vue'
import World from './World.vue'
import Worlds from './Worlds.vue'
export { Access, Backups, Content, Files, Index, Overview, Worlds }
export { Access, Backups, Content, Files, Index, Overview, World, Worlds }
@@ -252,7 +252,7 @@ export function createServerInstallContent(opts: {
if (serverFlowFrom.value === 'reset-server') {
return `/hosting/manage/${sid}?openSettings=installation`
}
return `/hosting/manage/${sid}/content`
return getServerWorldContentPath(sid, effectiveServerWorldId.value)
})
const serverBackLabel = computed(() => {
if (serverFlowFrom.value === 'onboarding') return 'Back to setup'
@@ -556,7 +556,7 @@ export function createServerInstallContent(opts: {
if (serverFlowFrom.value === 'onboarding') {
await client.archon.servers_v1.endIntro(sid)
await router.push(`/hosting/manage/${sid}/content`)
await router.push(getServerWorldContentPath(sid, wid))
return
}
@@ -571,6 +571,11 @@ export function createServerInstallContent(opts: {
serverContentProjectIds.value = new Set([...serverContentProjectIds.value, id])
}
function getServerWorldContentPath(serverId: string, worldId: string | null) {
const base = `/hosting/manage/${encodeURIComponent(serverId)}/worlds`
return worldId ? `${base}/${encodeURIComponent(worldId)}` : base
}
return {
serverIdQuery,
worldIdQuery,
+34
View File
@@ -65,6 +65,40 @@ export default new createRouter({
breadcrumb: [{ name: '?Server' }],
},
},
{
path: 'worlds/:world_id',
name: 'ServerManageWorld',
component: Hosting.World,
meta: {
breadcrumb: [{ name: '?Server' }],
},
children: [
{
path: '',
name: 'ServerManageWorldContent',
component: Hosting.Content,
meta: {
breadcrumb: [{ name: '?Server' }],
},
},
{
path: 'files',
name: 'ServerManageWorldFiles',
component: Hosting.Files,
meta: {
breadcrumb: [{ name: '?Server' }],
},
},
{
path: 'backups',
name: 'ServerManageWorldBackups',
component: Hosting.Backups,
meta: {
breadcrumb: [{ name: '?Server' }],
},
},
],
},
{
path: 'files',
name: 'ServerManageFiles',
@@ -187,7 +187,10 @@ export function useServerInstallContent({
},
}
const contentQueryKey = computed(() => ['content', 'list', currentServerId.value ?? ''] as const)
const contentQueryKey = computed(
() =>
['content', 'list', 'v1', currentServerId.value ?? '', currentWorldId.value ?? null] as const,
)
const { data: serverContentData, error: serverContentError } = useQuery({
queryKey: contentQueryKey,
queryFn: () =>
@@ -659,7 +662,7 @@ export function useServerInstallContent({
if (fromContext.value === 'onboarding') {
await client.archon.servers_v1.endIntro(currentServerId.value)
queryClient.invalidateQueries({ queryKey: ['servers', 'detail', currentServerId.value] })
navigateTo(`/hosting/manage/${currentServerId.value}/content`)
navigateTo(getServerWorldContentPath(currentServerId.value, currentWorldId.value ?? null))
} else {
navigateTo(`/hosting/manage/${currentServerId.value}?openSettings=installation`)
}
@@ -675,9 +678,14 @@ export function useServerInstallContent({
if (fromContext.value === 'onboarding') return `/hosting/manage/${id}?resumeModal=setup-type`
if (fromContext.value === 'reset-server')
return `/hosting/manage/${id}?openSettings=installation`
return `/hosting/manage/${id}/content`
return getServerWorldContentPath(id, currentWorldId.value)
})
function getServerWorldContentPath(serverId: string, worldId: string | null) {
const base = `/hosting/manage/${encodeURIComponent(serverId)}/worlds`
return worldId ? `${base}/${encodeURIComponent(worldId)}` : base
}
const serverBackLabel = computed(() => {
if (fromContext.value === 'onboarding') return formatMessage(messages.backToSetup)
if (fromContext.value === 'reset-server') return formatMessage(messages.cancelReset)
@@ -0,0 +1,40 @@
import { createModrinthClient } from '~/helpers/api.ts'
export default defineNuxtRouteMiddleware(async (to) => {
const match = to.path.match(/^\/hosting\/manage\/([^/]+)\/(content|files|backups)\/?$/)
if (!match) return
const serverId = decodeURIComponent(match[1])
const tab = match[2]
const worldsPath = `/hosting/manage/${encodeURIComponent(serverId)}/worlds`
const tabPath = tab === 'content' ? '' : `/${tab}`
const auth = await useAuth()
if (auth.value.token) {
try {
const config = useRuntimeConfig()
const client = createModrinthClient(auth, {
apiBaseUrl: config.public.apiBaseUrl.replace('/v2/', '/'),
archonBaseUrl: config.public.pyroBaseUrl.replace('/v2/', '/'),
rateLimitKey: config.rateLimitKey,
})
const serverFull = await client.archon.servers_v1.get(serverId)
const world = serverFull.worlds.find((item) => item.is_active) ?? serverFull.worlds[0]
if (world) {
return navigateTo(
{
path: `${worldsPath}/${encodeURIComponent(world.id)}${tabPath}`,
query: to.query,
hash: to.hash,
},
{ replace: true },
)
}
} catch {
return navigateTo({ path: worldsPath, query: to.query, hash: to.hash }, { replace: true })
}
}
return navigateTo({ path: worldsPath, query: to.query, hash: to.hash }, { replace: true })
})
@@ -0,0 +1,11 @@
<script setup lang="ts">
import { ServersManageWorldRootLayout } from '@modrinth/ui'
const route = useNativeRoute()
</script>
<template>
<ServersManageWorldRootLayout>
<NuxtPage :route="route" />
</ServersManageWorldRootLayout>
</template>
@@ -14,7 +14,7 @@ const flags = useFeatureFlags()
if (worldId.value) {
try {
await queryClient.ensureQueryData({
queryKey: ['backups', 'list', serverId],
queryKey: ['backups', 'list', serverId, worldId.value],
queryFn: () => client.archon.backups_v1.list(serverId, worldId.value!),
staleTime: 30_000,
})
@@ -38,7 +38,7 @@ const contentWorldId = await getContentWorldId()
if (contentWorldId) {
try {
const content = await queryClient.ensureQueryData({
queryKey: ['content', 'list', 'v1', serverId],
queryKey: ['content', 'list', 'v1', serverId, contentWorldId],
queryFn: () =>
client.archon.content_v1.getAddons(serverId, contentWorldId, { from_modpack: false }),
staleTime: 30_000,