mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 09:34:50 +00:00
refactor: introduce PageHeader component (#6629)
* refactor: introduce PageHeader component * feat: split up according to component structure guide * fix: lint * fix: label inconsistencies * feat: refactor PageHeader (again) * refactor: old impls * fix: changes * fix: rescan * fix: prepr * fix: dedupe * fix: rev * fix: lint * fix: lint --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<PageHeader :title="instance.name">
|
||||
<template #leading>
|
||||
<Avatar :src="iconSrc" :alt="instance.name" size="64px" :tint-by="instance.id" />
|
||||
</template>
|
||||
|
||||
<template #metadata>
|
||||
<InstanceHeaderServerMetadata
|
||||
v-if="isServerInstance"
|
||||
:loading-server-ping="loadingServerPing"
|
||||
:players-online="playersOnline"
|
||||
:status-online="statusOnline"
|
||||
:recent-plays="recentPlays"
|
||||
:ping="ping"
|
||||
:minecraft-server="minecraftServer"
|
||||
:linked-project-v3="linkedProjectV3"
|
||||
:instance-id="instance.id"
|
||||
/>
|
||||
<PageHeaderMetadata v-else>
|
||||
<PageHeaderMetadataItem :icon="Gamepad2Icon" tooltip="Minecraft version">
|
||||
Minecraft {{ instance.game_version }}
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem
|
||||
:icon="ServerLoaderIcon"
|
||||
:icon-props="{ loader: loaderDisplayName }"
|
||||
tooltip="Mod loader"
|
||||
>
|
||||
{{ loaderLabel }}
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem
|
||||
v-if="showInstancePlayTime"
|
||||
:icon="TimerIcon"
|
||||
tooltip="Total playtime"
|
||||
>
|
||||
{{ playtimeLabel }}
|
||||
</PageHeaderMetadataItem>
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<PageHeaderActions>
|
||||
<ButtonStyled v-if="isInstalling" color="brand" size="large">
|
||||
<button type="button" disabled>
|
||||
{{ formatMessage(commonMessages.installingLabel) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else-if="instance.install_stage !== 'installed'" color="brand" size="large">
|
||||
<button type="button" @click="emit('repair')">
|
||||
<DownloadIcon />
|
||||
{{ formatMessage(messages.repair) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else-if="playing" color="red" size="large">
|
||||
<button type="button" :disabled="stopping" @click="emit('stop')">
|
||||
<StopCircleIcon />
|
||||
{{
|
||||
stopping ? formatMessage(messages.stopping) : formatMessage(commonMessages.stopButton)
|
||||
}}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<JoinedButtons
|
||||
v-else-if="!loading && isServerInstance"
|
||||
:actions="serverPlayActions"
|
||||
color="brand"
|
||||
size="large"
|
||||
/>
|
||||
<ButtonStyled v-else-if="!loading" color="brand" size="large">
|
||||
<button type="button" @click="emit('play')">
|
||||
<PlayIcon />
|
||||
{{ formatMessage(commonMessages.playButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="brand" size="large">
|
||||
<button type="button" disabled>{{ formatMessage(messages.starting) }}</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<ButtonStyled circular size="large">
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.instanceSettings)"
|
||||
type="button"
|
||||
:aria-label="formatMessage(messages.instanceSettings)"
|
||||
@click="emit('settings')"
|
||||
>
|
||||
<SettingsIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular size="large" type="transparent">
|
||||
<TeleportOverflowMenu
|
||||
:options="moreActions"
|
||||
:tooltip="formatMessage(messages.moreActions)"
|
||||
:aria-label="formatMessage(messages.moreActions)"
|
||||
>
|
||||
<MoreVerticalIcon />
|
||||
</TeleportOverflowMenu>
|
||||
</ButtonStyled>
|
||||
</PageHeaderActions>
|
||||
</template>
|
||||
</PageHeader>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
DownloadIcon,
|
||||
ExternalIcon,
|
||||
FolderOpenIcon,
|
||||
MoreVerticalIcon,
|
||||
PackageIcon,
|
||||
PlayIcon,
|
||||
SettingsIcon,
|
||||
StopCircleIcon,
|
||||
TagCategoryGamepad2Icon as Gamepad2Icon,
|
||||
TimerIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
formatLoaderLabel,
|
||||
type JoinedButtonAction,
|
||||
JoinedButtons,
|
||||
LoaderIcon as ServerLoaderIcon,
|
||||
PageHeader,
|
||||
PageHeaderActions,
|
||||
PageHeaderMetadata,
|
||||
PageHeaderMetadataItem,
|
||||
type ServerLoader,
|
||||
TeleportOverflowMenu,
|
||||
type TeleportOverflowMenuItem,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
|
||||
import InstanceHeaderServerMetadata from './instance-page-header-server-metadata.vue'
|
||||
|
||||
const messages = defineMessages({
|
||||
createShortcut: {
|
||||
id: 'instance.action.create-shortcut',
|
||||
defaultMessage: 'Create shortcut',
|
||||
},
|
||||
exportModpack: {
|
||||
id: 'instance.action.export-modpack',
|
||||
defaultMessage: 'Export modpack',
|
||||
},
|
||||
instanceSettings: {
|
||||
id: 'instance.action.settings',
|
||||
defaultMessage: 'Instance settings',
|
||||
},
|
||||
launchInstance: {
|
||||
id: 'instance.action.launch-instance',
|
||||
defaultMessage: 'Launch instance',
|
||||
},
|
||||
moreActions: {
|
||||
id: 'instance.action.more-actions',
|
||||
defaultMessage: 'More actions',
|
||||
},
|
||||
neverPlayed: {
|
||||
id: 'instance.playtime.never-played',
|
||||
defaultMessage: 'Never played',
|
||||
},
|
||||
openFolder: {
|
||||
id: 'instance.action.open-folder',
|
||||
defaultMessage: 'Open folder',
|
||||
},
|
||||
repair: {
|
||||
id: 'instance.action.repair',
|
||||
defaultMessage: 'Repair',
|
||||
},
|
||||
starting: {
|
||||
id: 'instance.action.starting',
|
||||
defaultMessage: 'Starting...',
|
||||
},
|
||||
stopping: {
|
||||
id: 'instance.action.stopping',
|
||||
defaultMessage: 'Stopping...',
|
||||
},
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
instance: GameInstance
|
||||
iconSrc?: string | null
|
||||
isServerInstance?: boolean
|
||||
showInstancePlayTime?: boolean
|
||||
timePlayed?: number
|
||||
playing?: boolean
|
||||
loading?: boolean
|
||||
stopping?: boolean
|
||||
loadingServerPing?: boolean
|
||||
playersOnline?: number
|
||||
statusOnline?: boolean
|
||||
recentPlays?: number
|
||||
ping?: number
|
||||
minecraftServer?: Labrinth.Projects.v3.Project['minecraft_server']
|
||||
linkedProjectV3?: Labrinth.Projects.v3.Project
|
||||
}>(),
|
||||
{
|
||||
iconSrc: null,
|
||||
isServerInstance: false,
|
||||
showInstancePlayTime: false,
|
||||
timePlayed: 0,
|
||||
playing: false,
|
||||
loading: false,
|
||||
stopping: false,
|
||||
loadingServerPing: false,
|
||||
playersOnline: undefined,
|
||||
statusOnline: false,
|
||||
recentPlays: undefined,
|
||||
ping: undefined,
|
||||
minecraftServer: undefined,
|
||||
linkedProjectV3: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
repair: []
|
||||
stop: []
|
||||
play: []
|
||||
playServer: []
|
||||
settings: []
|
||||
openFolder: []
|
||||
export: []
|
||||
createShortcut: []
|
||||
}>()
|
||||
|
||||
const installingStages = [
|
||||
'installing',
|
||||
'pack_installing',
|
||||
'pack_installed',
|
||||
'not_installed',
|
||||
'minecraft_installing',
|
||||
]
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const isInstalling = computed(() => installingStages.includes(props.instance.install_stage))
|
||||
const loaderDisplayName = computed(() => formatLoaderLabel(props.instance.loader) as ServerLoader)
|
||||
const loaderLabel = computed(() =>
|
||||
[loaderDisplayName.value, props.instance.loader_version].filter(Boolean).join(' '),
|
||||
)
|
||||
const playtimeLabel = computed(() => {
|
||||
if (props.timePlayed <= 0) return formatMessage(messages.neverPlayed)
|
||||
|
||||
const hours = Math.floor(props.timePlayed / 3600)
|
||||
if (hours >= 1) {
|
||||
return `${hours} hour${hours > 1 ? 's' : ''}`
|
||||
}
|
||||
|
||||
const minutes = Math.floor(props.timePlayed / 60)
|
||||
if (minutes >= 1) {
|
||||
return `${minutes} minute${minutes > 1 ? 's' : ''}`
|
||||
}
|
||||
|
||||
const seconds = Math.floor(props.timePlayed)
|
||||
return `${seconds} second${seconds > 1 ? 's' : ''}`
|
||||
})
|
||||
const serverPlayActions = computed<JoinedButtonAction[]>(() => [
|
||||
{
|
||||
id: 'join_server',
|
||||
label: formatMessage(commonMessages.playButton),
|
||||
icon: PlayIcon,
|
||||
action: () => emit('playServer'),
|
||||
},
|
||||
{
|
||||
id: 'launch_instance',
|
||||
label: formatMessage(messages.launchInstance),
|
||||
icon: PlayIcon,
|
||||
action: () => emit('play'),
|
||||
},
|
||||
])
|
||||
const moreActions = computed<TeleportOverflowMenuItem[]>(() => [
|
||||
{
|
||||
id: 'open-folder',
|
||||
label: formatMessage(messages.openFolder),
|
||||
icon: FolderOpenIcon,
|
||||
action: () => emit('openFolder'),
|
||||
},
|
||||
{
|
||||
id: 'export-mrpack',
|
||||
label: formatMessage(messages.exportModpack),
|
||||
icon: PackageIcon,
|
||||
action: () => emit('export'),
|
||||
},
|
||||
{
|
||||
id: 'create-shortcut',
|
||||
label: formatMessage(messages.createShortcut),
|
||||
icon: ExternalIcon,
|
||||
action: () => emit('createShortcut'),
|
||||
},
|
||||
])
|
||||
</script>
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="flex items-center flex-wrap gap-2">
|
||||
<template v-if="loadingServerPing">
|
||||
<ServerOnlinePlayers
|
||||
v-if="playersOnline !== undefined"
|
||||
:online="playersOnline"
|
||||
:status-online="statusOnline"
|
||||
hide-label
|
||||
/>
|
||||
<ServerRecentPlays :recent-plays="recentPlays ?? 0" hide-label />
|
||||
<div
|
||||
v-if="
|
||||
(playersOnline !== undefined || recentPlays !== undefined) &&
|
||||
(minecraftServer?.region || ping)
|
||||
"
|
||||
class="w-1.5 h-1.5 rounded-full bg-surface-5"
|
||||
></div>
|
||||
<ServerPing v-if="ping" :ping="ping" />
|
||||
</template>
|
||||
|
||||
<ServerRegion v-if="minecraftServer?.region" :region="minecraftServer?.region" />
|
||||
|
||||
<div v-if="minecraftServer?.region || ping" class="w-1.5 h-1.5 rounded-full bg-surface-5"></div>
|
||||
|
||||
<div v-if="linkedProjectV3" class="flex gap-1.5 items-center font-medium text-primary">
|
||||
Linked to
|
||||
<Avatar
|
||||
:src="linkedProjectV3.icon_url"
|
||||
:alt="linkedProjectV3.name"
|
||||
:tint-by="instanceId"
|
||||
size="24px"
|
||||
/>
|
||||
<router-link
|
||||
:to="`/project/${linkedProjectV3.slug ?? linkedProjectV3.id}`"
|
||||
class="hover:underline text-primary truncate"
|
||||
>
|
||||
{{ linkedProjectV3.name }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
Avatar,
|
||||
ServerOnlinePlayers,
|
||||
ServerPing,
|
||||
ServerRecentPlays,
|
||||
ServerRegion,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
defineProps<{
|
||||
loadingServerPing?: boolean
|
||||
playersOnline?: number
|
||||
statusOnline?: boolean
|
||||
recentPlays?: number
|
||||
ping?: number
|
||||
minecraftServer?: Labrinth.Projects.v3.Project['minecraft_server']
|
||||
linkedProjectV3?: Labrinth.Projects.v3.Project
|
||||
instanceId?: string
|
||||
}>()
|
||||
</script>
|
||||
Reference in New Issue
Block a user