Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
a94e15e
feat: world switching page implementation
IMB11 Apr 27, 2026
d73d7ca
fix: lint + i18n
IMB11 Apr 27, 2026
5d49e9f
feat: world card alignment
IMB11 Apr 27, 2026
f922145
feat: reshuffle layout for worlds
IMB11 Apr 27, 2026
003c830
chore: clean up wrapped layout folder structure
IMB11 Apr 27, 2026
110a83c
fix: lint
IMB11 Apr 27, 2026
546a207
feat: fix btn sizing
IMB11 Apr 30, 2026
70887e8
fix: worlds layout
IMB11 Apr 30, 2026
f799e6f
fix: modpack linked text alignment
IMB11 Apr 30, 2026
f097409
chore: rename worlds -> instances
IMB11 May 18, 2026
946b9d3
qa: pass
IMB11 May 18, 2026
8fca23e
fix: lint
IMB11 May 18, 2026
b72c319
fix: header issues
IMB11 May 18, 2026
69ba34a
feat: PageHeader migration start
IMB11 May 18, 2026
e3c50d1
feat: header migration pt 2
IMB11 May 18, 2026
d1a26ee
fix: header power state
IMB11 May 18, 2026
53ca400
fix: ssr
IMB11 May 18, 2026
8493e66
fix: ssr for instances subpages with middleware
IMB11 May 18, 2026
de00798
feat: migrate all headers to use PageHeader shared component.
IMB11 May 18, 2026
45d02bd
feat: qa + app routing bugs
IMB11 May 23, 2026
c0d839c
feat: qa
IMB11 May 23, 2026
27f629a
fix: project middleware startLoading
IMB11 May 23, 2026
9ab9fe9
feat: server settings split up + copy changes across panel warning mo…
IMB11 May 23, 2026
db225b7
fix: lint
IMB11 May 23, 2026
8381482
fix: loss of isNuxt
IMB11 Jun 5, 2026
0a451d9
feat: switch world route impl
IMB11 Jun 9, 2026
54e6fe0
feat: implement world creation flow routes
IMB11 Jun 10, 2026
ad7a4d7
fix: qa + lint + i18n
IMB11 Jun 10, 2026
2c1b8a2
feat: impl world scoped power endpoint
IMB11 Jun 11, 2026
2021c22
feat: impl delete world + move onboarding reset to server scope
IMB11 Jun 12, 2026
8490718
fix: reset to onboarding flow not waiting
IMB11 Jun 13, 2026
77e74f8
feat: improve onboarding flow wait logic
IMB11 Jun 13, 2026
7618f9b
fix: browse page not respecting instance source via wid param
IMB11 Jun 13, 2026
32ca8d5
fix: better handling of datapacks in frontend & desync issues between…
IMB11 Jun 13, 2026
c771751
fix: lint
IMB11 Jun 13, 2026
ea94bcc
fix: missing serverpaneladmonitions + csp app
IMB11 Jun 16, 2026
8196d62
fix: power
IMB11 Jun 16, 2026
e5f91ea
fix: info admon breaking
IMB11 Jun 17, 2026
af6aec8
feat: migrate files to v1
IMB11 Jun 23, 2026
2e08fe7
fix: use list descendants isntead of v0
IMB11 Jun 23, 2026
ef07172
fix: ditch files v0
IMB11 Jun 24, 2026
417c696
fix: guard preload content
IMB11 Jun 24, 2026
4986bc1
fix: qa
IMB11 Jun 24, 2026
4676386
fix: lint
IMB11 Jun 24, 2026
485ca62
fix: lint
IMB11 Jun 24, 2026
fe64b6c
fix: change icons + copy
IMB11 Jun 26, 2026
32517df
Merge remote-tracking branch 'origin/main' into cal/hosting-server-in…
IMB11 Aug 3, 2026
4129be1
fix: preserve hosting header updates after merge
IMB11 Aug 3, 2026
91f8d25
fix: bad merge
IMB11 Aug 3, 2026
8169000
fix: migrate server instance header to new PageHeader
IMB11 Aug 3, 2026
38bf2c5
chore: restore locale files from main
IMB11 Aug 3, 2026
ed93672
fix: merge issues
IMB11 Aug 4, 2026
5262325
feat: sync individual content installation states on panel
IMB11 Jul 27, 2026
50fa5af
feat: improve handling
IMB11 Jul 28, 2026
986a40e
fix: lint
IMB11 Jul 28, 2026
2463627
fix: ws connection duplication + disconnecting during browse
IMB11 Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 76 additions & 4 deletions apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ const APP_LEFT_NAV_WIDTH = '4rem'
const APP_SIDEBAR_WIDTH = 300
const INTERCOM_BUBBLE_DEFAULT_PADDING = 20
const PRIDE_FUNDRAISER_END_DATE = new Date('2026-07-01T00:00:00Z').getTime()
const ROUTE_SUSPENSE_TIMEOUT_MS = 60_000
const credentials = ref()
let credentialsRefreshId = 0
const sidebarToggled = ref(true)
Expand All @@ -180,6 +181,22 @@ const forceSidebar = computed(
() => route.path.startsWith('/browse') || route.path.startsWith('/project'),
)
const sidebarVisible = computed(() => sidebarToggled.value || forceSidebar.value)
const keepAliveRouteComponents = computed(() => [
...new Set(
router
.getRoutes()
.map((route) => route.meta.keepAliveComponent)
.filter((name) => typeof name === 'string'),
),
])

function getRouteViewKey(viewRoute) {
const keepAliveKey = viewRoute.meta.keepAliveKey
if (typeof keepAliveKey === 'function') return keepAliveKey(viewRoute)
if (typeof keepAliveKey === 'string') return keepAliveKey
return undefined
}

const hostingRouteActive = computed(() => route.path.startsWith('/hosting'))
const prideFundraiserEnabled = computed(
() => themeStore.getFeatureFlag('pride_fundraiser') && Date.now() < PRIDE_FUNDRAISER_END_DATE,
Expand Down Expand Up @@ -664,6 +681,11 @@ const sidebarOverlayScrollbarsOptions = Object.freeze({
},
})

router.beforeEach(async (to) => {
const redirect = await resolveLegacyServerInstanceTabRedirect(to)
if (redirect) return redirect
})

router.beforeEach(() => {
suspensePending = false
if (routerToken) loading.end(routerToken)
Expand Down Expand Up @@ -696,6 +718,50 @@ function onSuspensePending() {
suspenseToken = loading.begin()
}

async function resolveLegacyServerInstanceTabRedirect(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 instancesPath = `/hosting/manage/${encodeURIComponent(serverId)}/instances`

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: `${instancesPath}/${encodeURIComponent(world.id)}${tabPath}`,
query: to.query,
hash: to.hash,
replace: true,
}
}
} catch {
return {
path: instancesPath,
query: to.query,
hash: to.hash,
replace: true,
}
}

return {
path: instancesPath,
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)
Expand Down Expand Up @@ -1746,11 +1812,17 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
>
{{ formatMessage(messages.authUnreachableBody) }}
</Admonition>
<RouterView v-slot="{ Component }">
<RouterView v-slot="{ Component, route: viewRoute }">
<template v-if="Component">
<Suspense @pending="onSuspensePending" @resolve="onSuspenseResolve">
<component :is="Component"></component>
</Suspense>
<KeepAlive :include="keepAliveRouteComponents" :max="3">
<Suspense
:timeout="ROUTE_SUSPENSE_TIMEOUT_MS"
@pending="onSuspensePending"
@resolve="onSuspenseResolve"
>
<component :is="Component" :key="getRouteViewKey(viewRoute)"></component>
</Suspense>
</KeepAlive>
</template>
</RouterView>
</div>
Expand Down
57 changes: 49 additions & 8 deletions apps/app-frontend/src/pages/Browse.vue
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ const breadcrumbLabel = computed(() => {
const themeStore = useTheming()
const browseRouteActive = computed(() => route.path.startsWith('/browse/'))
const serverSetupModalRef = ref<InstanceType<typeof CreationFlowModal> | null>(null)
const serverInstallContent = createServerInstallContent({ serverSetupModalRef })
const serverInstallContent = createServerInstallContent({
serverSetupModalRef,
isRouteInContext: (targetRoute) => targetRoute.path.startsWith('/browse/'),
})
provideServerInstallContent(serverInstallContent)
const {
serverIdQuery,
Expand All @@ -127,6 +130,10 @@ const {
isSetupServerContext,
effectiveServerWorldId,
serverContextServerData,
serverContextWorldName,
serverContextWorldGameVersion,
serverContextWorldLoader,
serverContextWorldLoaderVersion,
serverContentProjectIds,
queuedServerInstallProjectIds,
queuedServerInstallCount,
Expand Down Expand Up @@ -432,10 +439,10 @@ const serverContextFilters = computed(() => {
const pt = projectType.value

if (pt !== 'modpack') {
const gameVersion = serverContextServerData.value.mc_version
const gameVersion = serverContextWorldGameVersion.value
if (gameVersion) filters.push({ type: 'game_version', option: gameVersion })

const platform = serverContextServerData.value.loader?.toLowerCase()
const platform = serverContextWorldLoader.value?.toLowerCase().replaceAll('_', '')
if (platform && ['fabric', 'forge', 'quilt', 'neoforge'].includes(platform))
filters.push({ type: 'mod_loader', option: platform })
if (platform && ['paper', 'purpur'].includes(platform))
Expand Down Expand Up @@ -476,6 +483,14 @@ const combinedProvidedFilters = computed(() =>
isServerContext.value ? serverContextFilters.value : instanceFilters.value,
)

const serverContentProjectType = computed<ProjectType | null>(() => {
const loader = serverContextWorldLoader.value?.toLowerCase()
if (!loader) return null
if (loader === 'paper' || loader === 'purpur') return 'plugin'
if (loader === 'vanilla') return 'datapack'
return 'mod'
})

const {
serverPings,
contextMenuRef,
Expand Down Expand Up @@ -543,6 +558,10 @@ const messages = defineMessages({
id: 'app.browse.back-to-instance',
defaultMessage: 'Back to instance',
},
worldFallbackName: {
id: 'app.browse.server.world-fallback-name',
defaultMessage: 'Instance',
},
serverInstanceContentWarning: {
id: 'app.browse.server-instance-content-warning',
defaultMessage:
Expand Down Expand Up @@ -576,6 +595,27 @@ const messages = defineMessages({

const projectType = ref<ProjectType>(route.params.projectType as ProjectType)

watch(
[isServerContext, isSetupServerContext, projectType, serverContentProjectType],
([serverContext, setupServerContext, currentProjectType, targetProjectType]) => {
if (!serverContext || setupServerContext || !targetProjectType) return
if (!['mod', 'plugin', 'datapack'].includes(currentProjectType)) return
if (currentProjectType === targetProjectType) return

router.replace({
path: `/browse/${targetProjectType}`,
query: {
sid: route.query.sid,
wid: route.query.wid,
shi: route.query.shi,
from: route.query.from,
q: route.query.q,
},
})
},
{ immediate: true },
)

function resetInstanceContext() {
if (!instance.value) return

Expand Down Expand Up @@ -676,9 +716,10 @@ const selectableProjectTypes = computed(() => {
const installContext = computed(() => {
if (isServerContext.value && serverContextServerData.value) {
return {
name: serverContextServerData.value.name,
loader: serverContextServerData.value.loader ?? '',
gameVersion: serverContextServerData.value.mc_version ?? '',
name: serverContextWorldName.value ?? formatMessage(messages.worldFallbackName),
loader: serverContextWorldLoader.value ?? '',
loaderVersion: serverContextWorldLoaderVersion.value ?? '',
gameVersion: serverContextWorldGameVersion.value ?? '',
serverId: serverIdQuery.value,
upstream: serverContextServerData.value.upstream,
iconSrc: null as string | null,
Expand Down Expand Up @@ -747,8 +788,8 @@ function getCurrentSelectedInstallPreferences(projectTypeValue: string) {
function getServerInstallTargetPreferences(contentType: BrowseInstallContentType) {
return getTargetInstallPreferences(
{
gameVersion: serverContextServerData.value?.mc_version,
loader: serverContextServerData.value?.loader,
gameVersion: serverContextWorldGameVersion.value,
loader: serverContextWorldLoader.value,
},
contentType,
)
Expand Down
2 changes: 1 addition & 1 deletion apps/app-frontend/src/pages/hosting/manage/Backups.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down
2 changes: 1 addition & 1 deletion apps/app-frontend/src/pages/hosting/manage/Content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 8 additions & 6 deletions apps/app-frontend/src/pages/hosting/manage/Files.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ import {
import { useQueryClient } from '@tanstack/vue-query'

const client = injectModrinthClient()
const { serverId } = injectModrinthServerContext()
const { worldId } = injectModrinthServerContext()
const queryClient = useQueryClient()

try {
await queryClient.ensureQueryData({
queryKey: ['files', serverId, '/'],
queryFn: () => client.kyros.files_v0.listDirectory('/', 1, 2000),
staleTime: 30_000,
})
if (worldId.value) {
await queryClient.ensureQueryData({
queryKey: ['files', 'v1', worldId.value, '/'],
queryFn: () => client.kyros.files_v1.listDescendants(worldId.value!, '/', 1, 200),
staleTime: 30_000,
})
}
} catch {
// Let mounted layouts' useQuery surface errors; do not fail route setup.
}
Expand Down
26 changes: 19 additions & 7 deletions apps/app-frontend/src/pages/hosting/manage/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"
>
<template #default="{ onReinstall, onReinstallFailed }">
<RouterView v-slot="{ Component }">
<RouterView v-slot="{ Component }" :route="managedRoute">
<template v-if="Component">
<Suspense>
<component
Expand Down Expand Up @@ -58,7 +58,7 @@ import {
} from '@modrinth/ui'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { openUrl } from '@tauri-apps/plugin-opener'
import { computed, ref, watch } from 'vue'
import { computed, ref, shallowRef, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'

import { get_user } from '@/helpers/cache'
Expand All @@ -74,12 +74,24 @@ const queryClient = useQueryClient()
const themeStore = useTheming()
const { formatMessage } = useVIntl()

const isContainedServerRoute = computed(() => route.name === 'ServerManageOverview')
const managedRoute = shallowRef(router.currentRoute.value)
const serverId = ref(getRouteParam(managedRoute.value.params.id) ?? '')
const isContainedServerRoute = computed(() => managedRoute.value.name === 'ServerManageOverview')

const serverId = computed(() => {
const rawId = route.params.id
return Array.isArray(rawId) ? (rawId[0] ?? '') : (rawId ?? '')
})
watch(
router.currentRoute,
(nextRoute) => {
if (!nextRoute.path.startsWith('/hosting/manage/')) return
managedRoute.value = nextRoute
serverId.value = getRouteParam(nextRoute.params.id) ?? ''
},
{ immediate: true },
)

function getRouteParam(param: string | string[] | undefined): string | null {
if (Array.isArray(param)) return param[0] ?? null
return param ?? null
}

function getCachedServerName(id: string): string | undefined {
return queryClient
Expand Down
16 changes: 16 additions & 0 deletions apps/app-frontend/src/pages/hosting/manage/Instance.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<ServersManageInstanceRootLayout>
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Suspense>
<component :is="Component" />
</Suspense>
</template>
</RouterView>
</ServersManageInstanceRootLayout>
</template>

<script setup lang="ts">
import { ServersManageInstanceRootLayout } from '@modrinth/ui'
import { RouterView } from 'vue-router'
</script>
7 changes: 7 additions & 0 deletions apps/app-frontend/src/pages/hosting/manage/Instances.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script setup lang="ts">
import { ServersManageInstancesPage } from '@modrinth/ui'
</script>

<template>
<ServersManageInstancesPage />
</template>
4 changes: 3 additions & 1 deletion apps/app-frontend/src/pages/hosting/manage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Backups from './Backups.vue'
import Content from './Content.vue'
import Files from './Files.vue'
import Index from './Index.vue'
import Instance from './Instance.vue'
import Instances from './Instances.vue'
import Overview from './Overview.vue'

export { Access, Backups, Content, Files, Index, Overview }
export { Access, Backups, Content, Files, Index, Instance, Instances, Overview }
Loading