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
@@ -18,46 +18,28 @@
<SettingsIcon />
Configuring server...
</div>
<div v-else class="flex flex-wrap items-center gap-2">
<div v-if="props.server?.loader" class="flex items-center gap-2 font-medium">
<LoaderIcon :loader="props.server.loader" class="flex shrink-0 [&&]:size-5" />
{{ formatLoaderLabel(props.server.loader) }} {{ props.server.mc_version }}
</div>
<div
v-if="
props.server?.loader &&
props.server?.net?.domain &&
!userPreferences.hideSubdomainLabel
"
class="h-1.5 w-1.5 rounded-full bg-surface-5"
/>
<div
v-if="props.server?.net?.domain && !userPreferences.hideSubdomainLabel"
v-tooltip="'Copy server address'"
class="flex cursor-pointer items-center gap-2 font-medium hover:underline text-nowrap"
@click="copyServerAddress"
>
<LinkIcon class="flex size-5 shrink-0" />
{{ props.server.net.domain }}.modrinth.gg
</div>
<div v-if="showUptime" class="h-1.5 w-1.5 rounded-full bg-surface-5" />
<div v-if="showUptime" class="flex items-center gap-2 font-medium">
<TimerIcon class="flex size-5 shrink-0" />
{{ formattedUptime }}
</div>
<div
v-if="showProject && (props.server?.loader || props.server?.net?.domain || showUptime)"
class="h-1.5 w-1.5 rounded-full bg-surface-5"
/>
<div v-else class="flex min-w-0 flex-wrap items-center gap-2">
<template v-for="(item, index) in headerStats" :key="item.id">
<div v-if="index > 0" class="h-1.5 w-1.5 rounded-full bg-surface-5" />
<button
v-if="item.copyable"
v-tooltip="'Copy server address'"
class="m-0 flex min-w-0 cursor-pointer items-center gap-2 border-0 bg-transparent p-0 font-medium text-secondary hover:underline text-nowrap"
type="button"
@click="copyServerAddress"
>
<component :is="item.icon" class="flex size-5 shrink-0" />
<span class="truncate">{{ item.label }}</span>
</button>
<div v-else class="flex min-w-0 items-center gap-2 font-medium text-secondary text-nowrap">
<component :is="item.icon" class="flex size-5 shrink-0" />
<span class="truncate">{{ item.label }}</span>
</div>
</template>
<div v-if="showProject && headerStats.length > 0" class="h-1.5 w-1.5 rounded-full bg-surface-5" />
<div
v-if="showProject"
class="flex items-center gap-1.5 font-medium text-primary text-nowrap"
class="flex min-w-0 items-center gap-1.5 font-medium text-primary text-nowrap"
>
Linked to
<Avatar
@@ -81,8 +63,9 @@
<script setup lang="ts">
import type { Archon } from '@modrinth/api-client'
import { NuxtModrinthClient } from '@modrinth/api-client'
import { LinkIcon, SettingsIcon, TimerIcon } from '@modrinth/assets'
import { GlobeIcon, LinkIcon, SettingsIcon, TimerIcon } from '@modrinth/assets'
import { useStorage } from '@vueuse/core'
import type { Component } from 'vue'
import { computed } from 'vue'
import { AutoLink, Avatar, ContentPageHeader, ServerIcon } from '#ui/components'
@@ -102,12 +85,20 @@ type ServerProjectSummary = {
icon_url?: string | null
}
type HeaderStat = {
id: string
label: string
icon: Component
copyable?: boolean
}
const props = withDefaults(
defineProps<{
server: Archon.Servers.v0.Server | null | undefined
serverImage?: string | null
serverProject?: ServerProjectSummary | null
serverProjectLink?: string
activeWorldName?: string | null
uptimeSeconds?: number
showUptime?: boolean
backHref?: string
@@ -118,6 +109,7 @@ const props = withDefaults(
serverImage: null,
serverProject: null,
serverProjectLink: '',
activeWorldName: null,
uptimeSeconds: 0,
showUptime: true,
backHref: '/hosting/manage',
@@ -158,6 +150,57 @@ const formattedUptime = computed(() => {
return formatted.trim()
})
const serverAddress = computed(() => {
const domain = props.server?.net?.domain
if (domain) return `${domain}.modrinth.gg`
const ip = props.server?.net?.ip
if (!ip) return null
const port = props.server?.net?.port
return port ? `${ip}:${port}` : ip
})
const showAddress = computed(
() => !!serverAddress.value && (!props.server?.net?.domain || !userPreferences.value.hideSubdomainLabel),
)
const headerStats = computed<HeaderStat[]>(() => {
const stats: HeaderStat[] = []
const worldName = props.activeWorldName
if (worldName) {
stats.push({
id: 'world',
label: worldName,
icon: GlobeIcon,
})
}
if (props.server?.loader) {
stats.push({
id: 'loader',
label: props.server.mc_version
? `${formatLoaderLabel(props.server.loader)} ${props.server.mc_version}`
: formatLoaderLabel(props.server.loader),
icon: LoaderIcon,
})
}
if (showAddress.value && serverAddress.value) {
stats.push({
id: 'address',
label: serverAddress.value,
icon: LinkIcon,
copyable: true,
})
}
if (showUptime.value) {
stats.push({
id: 'uptime',
label: formattedUptime.value,
icon: TimerIcon,
})
}
return stats
})
const showProject = computed(() => !!props.serverProject)
const serverProjectLink = computed(() => {
@@ -171,8 +214,8 @@ const serverProjectLink = computed(() => {
})
function copyServerAddress() {
if (!props.server?.net?.domain) return
navigator.clipboard.writeText(`${props.server.net.domain}.modrinth.gg`)
if (!serverAddress.value) return
navigator.clipboard.writeText(serverAddress.value)
addNotification({
title: 'Server address copied',
text: "Your server's address has been copied to your clipboard.",