mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 03:55:59 +00:00
fix: header issues
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<template #icon>
|
||||
<ServerIcon
|
||||
:image="headerImage"
|
||||
:class="isNuxt ? 'size-20 !rounded-2xl' : 'size-16 !rounded-xl'"
|
||||
:class="isNuxt ? 'size-15 !rounded-2xl' : 'size-15 !rounded-xl'"
|
||||
/>
|
||||
</template>
|
||||
<template #title>
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="w-full flex flex-col gap-4" :class="{ 'mt-4': isNuxt }">
|
||||
<ContentPageHeader :class="props.headerClass">
|
||||
<template #icon>
|
||||
<div class="flex size-16 shrink-0 items-center justify-center">
|
||||
<ButtonStyled circular size="large">
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="props.backLabel"
|
||||
@click="router.push(props.backHref)"
|
||||
>
|
||||
<LeftArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
<template #title>
|
||||
{{ props.name || props.fallbackName }}
|
||||
</template>
|
||||
<template #stats>
|
||||
<div v-if="headerMetadata.length" class="flex min-w-0 flex-wrap items-center gap-2">
|
||||
<template v-for="(item, index) in headerMetadata" :key="item.id">
|
||||
<BulletDivider v-if="index > 0" />
|
||||
<div 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"
|
||||
aria-hidden="true"
|
||||
v-bind="item.iconProps"
|
||||
/>
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<slot name="actions" />
|
||||
</template>
|
||||
</ContentPageHeader>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NuxtModrinthClient } from '@modrinth/api-client'
|
||||
import { LeftArrowIcon, TagCategoryGamepad2Icon as Gamepad2Icon, TimerIcon } from '@modrinth/assets'
|
||||
import { type Component, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import BulletDivider from '#ui/components/base/BulletDivider.vue'
|
||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||
import ContentPageHeader from '#ui/components/base/ContentPageHeader.vue'
|
||||
import LoaderIcon from '#ui/components/servers/icons/LoaderIcon.vue'
|
||||
import { injectModrinthClient } from '#ui/providers'
|
||||
import { formatLoaderLabel } from '#ui/utils/loaders'
|
||||
|
||||
type MetadataItem = {
|
||||
id: string
|
||||
label: string
|
||||
icon: Component
|
||||
iconProps?: Record<string, unknown>
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
name?: string | null
|
||||
metadataItems?: MetadataItem[]
|
||||
gameVersion?: string | null
|
||||
loader?: string | null
|
||||
loaderVersion?: string | null
|
||||
lastActive?: string | null
|
||||
backHref: string
|
||||
backLabel: string
|
||||
fallbackName?: string
|
||||
headerClass?: string
|
||||
}>(),
|
||||
{
|
||||
name: null,
|
||||
metadataItems: () => [],
|
||||
gameVersion: null,
|
||||
loader: null,
|
||||
loaderVersion: null,
|
||||
lastActive: null,
|
||||
fallbackName: 'World',
|
||||
headerClass: '',
|
||||
},
|
||||
)
|
||||
|
||||
const client = injectModrinthClient()
|
||||
const router = useRouter()
|
||||
const isNuxt = computed(() => client instanceof NuxtModrinthClient)
|
||||
const loaderLabel = computed(() => {
|
||||
if (!props.loader) return null
|
||||
const label = formatLoaderLabel(props.loader.toLowerCase())
|
||||
return [label, props.loaderVersion].filter(Boolean).join(' ')
|
||||
})
|
||||
const headerMetadata = computed<MetadataItem[]>(() => {
|
||||
if (props.metadataItems.length) return props.metadataItems
|
||||
|
||||
const items: MetadataItem[] = []
|
||||
if (props.gameVersion) {
|
||||
items.push({
|
||||
id: 'game-version',
|
||||
label: props.gameVersion,
|
||||
icon: Gamepad2Icon,
|
||||
})
|
||||
}
|
||||
if (props.loader && loaderLabel.value) {
|
||||
items.push({
|
||||
id: 'loader',
|
||||
label: loaderLabel.value,
|
||||
icon: LoaderIcon,
|
||||
iconProps: {
|
||||
loader: formatLoaderLabel(props.loader.toLowerCase()),
|
||||
},
|
||||
})
|
||||
}
|
||||
if (props.lastActive) {
|
||||
items.push({
|
||||
id: 'last-active',
|
||||
label: props.lastActive,
|
||||
icon: TimerIcon,
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
</script>
|
||||
@@ -1,2 +1,3 @@
|
||||
export { default as PanelServerActionButton } from './PanelServerActionButton.vue'
|
||||
export { default as ServerManageHeader } from './ServerManageHeader.vue'
|
||||
export { default as WorldManageHeader } from './WorldManageHeader.vue'
|
||||
|
||||
+36
-65
@@ -1,42 +1,27 @@
|
||||
<template>
|
||||
<div class="flex min-h-[36rem] flex-col gap-6 text-primary">
|
||||
<div class="flex flex-col gap-2">
|
||||
<RouterLink
|
||||
:to="instancesPath"
|
||||
class="flex w-fit items-center gap-1 text-base font-medium text-blue hover:underline"
|
||||
>
|
||||
<ChevronLeftIcon class="size-4" aria-hidden="true" />
|
||||
{{ formatMessage(messages.allInstances) }}
|
||||
</RouterLink>
|
||||
|
||||
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<div class="flex min-w-0 flex-col gap-1">
|
||||
<h1 class="m-0 truncate text-2xl font-semibold leading-8 text-contrast">
|
||||
{{ worldName }}
|
||||
</h1>
|
||||
<div class="flex flex-wrap items-center gap-2 text-base font-medium text-secondary">
|
||||
<template v-for="(item, index) in worldMetadata" :key="item">
|
||||
<span>{{ item }}</span>
|
||||
<BulletDivider v-if="index < worldMetadata.length - 1" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<PanelServerActionButton size="large" start-label="Start instance" />
|
||||
<ButtonStyled size="large" circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.instanceSettings)"
|
||||
@click="openServerSettings({ tabId: 'installation' })"
|
||||
>
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-px w-full bg-surface-5" />
|
||||
<div class="flex min-h-[36rem] flex-col gap-4 text-primary">
|
||||
<WorldManageHeader
|
||||
:name="worldName"
|
||||
:game-version="gameVersion"
|
||||
:loader="loader"
|
||||
:loader-version="loaderVersion"
|
||||
:last-active="lastActiveLabel"
|
||||
:back-href="instancesPath"
|
||||
:back-label="formatMessage(messages.allInstances)"
|
||||
:fallback-name="formatMessage(messages.worldFallbackName)"
|
||||
>
|
||||
<template #actions>
|
||||
<PanelServerActionButton size="large" start-label="Start instance" />
|
||||
<ButtonStyled size="large" circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.instanceSettings)"
|
||||
@click="openServerSettings({ tabId: 'installation' })"
|
||||
>
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</WorldManageHeader>
|
||||
|
||||
<NavTabs :links="worldTabLinks" replace />
|
||||
|
||||
@@ -46,21 +31,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Archon } from '@modrinth/api-client'
|
||||
import {
|
||||
BoxesIcon,
|
||||
ChevronLeftIcon,
|
||||
DatabaseBackupIcon,
|
||||
FolderOpenIcon,
|
||||
SettingsIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { BoxesIcon, DatabaseBackupIcon, FolderOpenIcon, SettingsIcon } from '@modrinth/assets'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { computed, watch } from 'vue'
|
||||
import { RouterLink, useRouter } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import BulletDivider from '#ui/components/base/BulletDivider.vue'
|
||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||
import NavTabs from '#ui/components/base/NavTabs.vue'
|
||||
import { PanelServerActionButton } from '#ui/components/servers/server-header'
|
||||
import { PanelServerActionButton, WorldManageHeader } from '#ui/components/servers/server-header'
|
||||
import { useRelativeTime } from '#ui/composables'
|
||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
import {
|
||||
@@ -68,7 +46,6 @@ import {
|
||||
injectModrinthServerContext,
|
||||
injectServerSettingsModal,
|
||||
} from '#ui/providers'
|
||||
import { formatLoaderLabel } from '#ui/utils/loaders'
|
||||
|
||||
interface Tab {
|
||||
label: string
|
||||
@@ -138,24 +115,18 @@ const worldName = computed(
|
||||
() => currentWorld.value?.name ?? server.value?.name ?? formatMessage(messages.worldFallbackName),
|
||||
)
|
||||
|
||||
const worldMetadata = computed(() =>
|
||||
[gameVersionLabel.value, loaderLabel.value, lastActiveLabel.value].filter(
|
||||
(item): item is string => !!item,
|
||||
),
|
||||
const gameVersion = computed(() => {
|
||||
const version = currentWorld.value?.content?.game_version ?? server.value?.mc_version
|
||||
return version ?? null
|
||||
})
|
||||
|
||||
const loader = computed(
|
||||
() => currentWorld.value?.content?.modloader ?? server.value?.loader ?? null,
|
||||
)
|
||||
|
||||
const gameVersionLabel = computed(() => {
|
||||
const version = currentWorld.value?.content?.game_version ?? server.value?.mc_version
|
||||
return version ? `MC ${version}` : null
|
||||
})
|
||||
|
||||
const loaderLabel = computed(() => {
|
||||
const loader = currentWorld.value?.content?.modloader ?? server.value?.loader?.toLowerCase()
|
||||
if (!loader) return null
|
||||
const loaderVersion =
|
||||
currentWorld.value?.content?.modloader_version ?? server.value?.loader_version ?? null
|
||||
return [formatLoaderLabel(loader), loaderVersion].filter(Boolean).join(' ')
|
||||
})
|
||||
const loaderVersion = computed(
|
||||
() => currentWorld.value?.content?.modloader_version ?? server.value?.loader_version ?? null,
|
||||
)
|
||||
|
||||
const lastActiveLabel = computed(() => {
|
||||
const latestBackup = currentWorld.value ? latestDate(currentWorld.value.backups) : null
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { SettingsIcon } from '@modrinth/assets'
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
import ButtonStyled from '../../components/base/ButtonStyled.vue'
|
||||
import WorldManageHeader from '../../components/servers/server-header/WorldManageHeader.vue'
|
||||
|
||||
const meta = {
|
||||
title: 'Servers/WorldManageHeader',
|
||||
component: WorldManageHeader,
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
},
|
||||
decorators: [
|
||||
(story) => ({
|
||||
components: { story },
|
||||
template: '<div style="max-width: 920px;"><story /></div>',
|
||||
}),
|
||||
],
|
||||
} satisfies Meta<typeof WorldManageHeader>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
name: 'My World',
|
||||
gameVersion: '1.20.1',
|
||||
loader: 'fabric',
|
||||
loaderVersion: '0.19.2',
|
||||
lastActive: 'Last active 2 weeks ago',
|
||||
backHref: '/hosting/manage/demo-server/instances',
|
||||
backLabel: 'All instances',
|
||||
},
|
||||
render: (args) => ({
|
||||
components: { ButtonStyled, SettingsIcon, WorldManageHeader },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<WorldManageHeader v-bind="args">
|
||||
<template #actions>
|
||||
<ButtonStyled size="large" circular>
|
||||
<button type="button" aria-label="Instance settings">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</WorldManageHeader>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
Reference in New Issue
Block a user