Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 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
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 @@ -146,6 +146,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()
const sidebarToggled = ref(true)
const unsubscribeSidebarToggle = themeStore.$subscribe(() => {
Expand All @@ -155,6 +156,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 @@ -495,6 +512,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 @@ -526,6 +548,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 @@ -1624,11 +1690,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
50 changes: 20 additions & 30 deletions apps/app-frontend/src/composables/browse/use-app-server-browse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ import type { ComputedRef, Ref } from 'vue'
import { onUnmounted, ref, shallowRef } from 'vue'
import type { Router } from 'vue-router'

import {
fetchCachedServerStatus,
getFreshCachedServerStatus,
} from '@/composables/instances/use-server-status-query'
import { process_listener } from '@/helpers/events'
import { kill, list as listInstances } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process'
import type { GameInstance } from '@/helpers/types'
import { add_server_to_instance, getServerAddress, getServerLatency } from '@/helpers/worlds'
import { add_server_to_instance, getServerAddress } from '@/helpers/worlds'

interface BrowseServerInstance {
id: string
name: string
path: string
}
Expand Down Expand Up @@ -68,12 +73,10 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
const queryClient = useQueryClient()
const debugLog = useDebugLogger('BrowseServer')
const serverPings = shallowRef<Record<string, number | undefined>>({})
const serverPingCache = new Map<string, number | undefined>()
const pendingServerPings = new Map<string, Promise<number | undefined>>()
const runningServerProjects = ref<Record<string, string>>({})
const lastServerHits = shallowRef<Labrinth.Search.v3.ResultSearchProject[]>([])
const contextMenuRef = ref<ContextMenuHandle | null>(null)
let serverPingCacheActive = true
let serverPingsActive = true
let unlistenProcesses: (() => void) | null = null

async function checkServerRunningStates(hits: Labrinth.Search.v3.ResultSearchProject[]) {
Expand Down Expand Up @@ -146,37 +149,26 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
})
const nextPings = { ...serverPings.value }
for (const { hit, address } of pingsToFetch) {
if (serverPingCache.has(address)) {
nextPings[hit.project_id] = serverPingCache.get(address)
const cachedStatus = getFreshCachedServerStatus(queryClient, address)
if (cachedStatus) {
nextPings[hit.project_id] = cachedStatus.ping
}
}
serverPings.value = nextPings

await Promise.all(
pingsToFetch.map(async ({ hit, address }) => {
if (serverPingCache.has(address)) return
if (getFreshCachedServerStatus(queryClient, address)) return

let pending = pendingServerPings.get(address)
if (!pending) {
pending = getServerLatency(address)
.then((latency) => {
if (serverPingCacheActive) serverPingCache.set(address, latency)
return latency
})
.catch((error) => {
console.error(`Failed to ping server ${address}:`, error)
if (serverPingCacheActive) serverPingCache.set(address, undefined)
return undefined
})
.finally(() => {
pendingServerPings.delete(address)
})
pendingServerPings.set(address, pending)
try {
const status = await fetchCachedServerStatus(queryClient, address)
if (!serverPingsActive) return
serverPings.value = { ...serverPings.value, [hit.project_id]: status.ping }
} catch (error) {
console.error(`Failed to ping server ${address}:`, error)
if (!serverPingsActive) return
serverPings.value = { ...serverPings.value, [hit.project_id]: undefined }
}

const latency = await pending
if (!serverPingCacheActive) return
serverPings.value = { ...serverPings.value, [hit.project_id]: latency }
}),
)
}
Expand Down Expand Up @@ -308,10 +300,8 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
.catch(options.handleError)

onUnmounted(() => {
serverPingCacheActive = false
serverPingsActive = false
unlistenProcesses?.()
serverPingCache.clear()
pendingServerPings.clear()
})

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { QueryClient } from '@tanstack/vue-query'

import {
get_server_status,
normalizeServerAddress,
type ProtocolVersion,
type ServerStatus,
} from '@/helpers/worlds'

export const SERVER_STATUS_CACHE_MS = 10 * 60 * 1000

function getProtocolVersionKey(protocolVersion: ProtocolVersion | null) {
if (!protocolVersion) return 'default'
return `${protocolVersion.version}:${protocolVersion.legacy ? 'legacy' : 'modern'}`
}

export function getServerStatusQueryKey(
address: string,
protocolVersion: ProtocolVersion | null = null,
) {
return [
'minecraft-server-status',
normalizeServerAddress(address) || address.trim().toLowerCase(),
getProtocolVersionKey(protocolVersion),
] as const
}

export function getFreshCachedServerStatus(
queryClient: QueryClient,
address: string,
protocolVersion: ProtocolVersion | null = null,
) {
const queryKey = getServerStatusQueryKey(address, protocolVersion)
const updatedAt = queryClient.getQueryState(queryKey)?.dataUpdatedAt ?? 0
if (!updatedAt || Date.now() - updatedAt >= SERVER_STATUS_CACHE_MS) return undefined
return queryClient.getQueryData<ServerStatus>(queryKey)
}

export async function fetchCachedServerStatus(
queryClient: QueryClient,
address: string,
protocolVersion: ProtocolVersion | null = null,
) {
return await queryClient.fetchQuery({
queryKey: getServerStatusQueryKey(address, protocolVersion),
queryFn: () => get_server_status(address, protocolVersion),
staleTime: SERVER_STATUS_CACHE_MS,
gcTime: SERVER_STATUS_CACHE_MS,
})
}
12 changes: 12 additions & 0 deletions apps/app-frontend/src/locales/en-US/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,18 @@
"app.browse.server.installing": {
"message": "Installing"
},
"app.browse.server.world-fallback-name": {
"message": "Instance"
},
"app.content-install.no-compatible-versions": {
"message": "No available versions match {compatibilityLabel}. Select a version to install anyway. Dependencies will not be installed automatically."
},
"app.creation-modal.installing-modpack.description": {
"message": "{fileName}"
},
"app.creation-modal.installing-modpack.title": {
"message": "Installing modpack..."
},
"app.export-modal.description-placeholder": {
"message": "Enter modpack description..."
},
Expand Down Expand Up @@ -422,6 +431,9 @@
"app.project.install-context.install-content-to-instance": {
"message": "Install content to instance"
},
"app.project.install-context.world-fallback-name": {
"message": "Instance"
},
"app.settings.developer-mode-enabled": {
"message": "Developer mode enabled."
},
Expand Down
Loading
Loading