fix: website visual issues (#5675)

* fix no modpack loader showing as resource pack loader

* fix table overflow, add game version tags "+ {num}" overflow menu

* pnpm prepr
This commit is contained in:
Truman Gao
2026-03-26 11:40:44 -07:00
committed by GitHub
parent f1648298c4
commit 706eb800cb
6 changed files with 154 additions and 34 deletions

View File

@@ -5,7 +5,7 @@
v-if="!isServerProject"
:project="data"
:tags="{ loaders: allLoaders, gameVersions: allGameVersions }"
:v3-metadata="projectV3"
:project-v3="projectV3"
class="project-sidebar-section"
/>
<ProjectSidebarServerInfo

View File

@@ -877,7 +877,7 @@
v-if="projectV3Loaded && !isServerProject"
:project="project"
:tags="tags"
:v3-metadata="projectV3"
:project-v3="projectV3"
class="card flex-card experimental-styles-within"
/>
<AdPlaceholder v-if="!auth.user && tags.approvedStatuses.includes(project.status)" />

View File

@@ -341,7 +341,8 @@
<div v-if="project.project_type !== 'resourcepack'">
<h4>Loaders</h4>
<Categories :categories="version.loaders" :type="project.project_type" />
<span v-if="noModpackLoader">No mod loader</span>
<Categories v-else :categories="version.loaders ?? []" :type="project.project_type" />
</div>
<div>
<h4>Game versions</h4>
@@ -698,6 +699,25 @@ const title = computed(
() => `${isCreating.value ? 'Create Version' : version.value.name} - ${project.value.title}`,
)
const modpackLoaders = computed<string[]>(() => {
if (project.value.project_type !== 'modpack') {
return []
}
if (Array.isArray(version.value.mrpack_loaders) && version.value.mrpack_loaders.length > 0) {
return version.value.mrpack_loaders
}
return (version.value.loaders ?? []).filter((loader: string) => loader !== 'mrpack')
})
const noModpackLoader = computed(
() =>
project.value.project_type === 'modpack' &&
modpackLoaders.value.length === 1 &&
modpackLoaders.value[0] === 'minecraft',
)
const description = computed(
() =>
`Download ${project.value.title} ${

View File

@@ -593,6 +593,7 @@ export namespace Labrinth {
categories: string[]
additional_categories: string[]
loaders: string[]
mrpack_loaders: string[]
versions: string[]
icon_url?: string
link_urls: Record<string, Link>

View File

@@ -3,7 +3,7 @@
<div class="flex flex-wrap justify-between gap-2">
<VersionFilterControl
ref="versionFilters"
:versions="versions"
:versions="normalizedVersions"
:game-versions="gameVersions"
:base-id="`${baseId}-filter`"
@update:query="updateQuery"
@@ -110,13 +110,18 @@
</div>
</div>
<div
class="pointer-events-none relative z-[1] flex flex-col justify-center"
class="pointer-events-none relative z-[1] flex flex-col justify-center overflow-hidden min-w-32"
:class="{
'group-hover:underline': !!versionLink,
}"
title="`${version.version_number} - ${version.name}`"
>
<div class="font-bold text-contrast">{{ version.version_number }}</div>
<div class="text-xs font-medium">{{ version.name }}</div>
<div class="font-bold text-contrast text-ellipsis overflow-hidden">
{{ version.version_number }}
</div>
<div class="text-xs font-medium text-ellipsis overflow-hidden">
{{ version.name }}
</div>
</div>
</div>
<div class="flex flex-col justify-center gap-2 sm:contents">
@@ -127,7 +132,7 @@
v-for="gameVersion in formatVersionsForDisplay(
version.game_versions,
gameVersions,
)"
).slice(0, maxGameVersionTags)"
:key="`version-tag-${gameVersion}`"
v-tooltip="`Toggle filter for ${gameVersion}`"
class="z-[1]"
@@ -137,21 +142,61 @@
>
{{ gameVersion }}
</TagItem>
<Menu
v-if="
formatVersionsForDisplay(version.game_versions, gameVersions).length >
maxGameVersionTags
"
:delay="{ hide: 50, show: 0 }"
no-auto-focus
class="z-[1] cursor-default"
>
<TagItem tabindex="0">
+{{
formatVersionsForDisplay(version.game_versions, gameVersions).length -
maxGameVersionTags
}}
</TagItem>
<template #popper>
<div class="flex gap-1 flex-wrap max-w-[20rem]">
<TagItem
v-for="gameVersion in formatVersionsForDisplay(
version.game_versions,
gameVersions,
).slice(maxGameVersionTags)"
:key="`overflow-version-tag-${gameVersion}`"
:action="
() =>
versionFilters?.toggleFilters('gameVersion', version.game_versions)
"
>
{{ gameVersion }}
</TagItem>
</div>
</template>
</Menu>
</div>
</div>
<div class="flex items-center">
<div class="flex flex-wrap gap-1">
<TagItem
v-for="platform in version.loaders"
:key="`platform-tag-${platform}`"
v-tooltip="`Toggle filter for ${platform}`"
class="z-[1]"
:style="`--_color: var(--color-platform-${platform})`"
:action="() => versionFilters?.toggleFilter('platform', platform)"
>
<component :is="getLoaderIcon(platform)" v-if="getLoaderIcon(platform)" />
<FormattedTag :tag="platform" enforce-type="loader" />
</TagItem>
<template v-if="version.noModLoader">
<TagItem class="z-[1] border !border-solid border-surface-5">
No mod loader
</TagItem>
</template>
<template v-else>
<TagItem
v-for="platform in version.loaders"
:key="`platform-tag-${platform}`"
v-tooltip="`Toggle filter for ${platform}`"
class="z-[1]"
:style="`--_color: var(--color-platform-${platform})`"
:action="() => versionFilters?.toggleFilter('platform', platform)"
>
<component :is="getLoaderIcon(platform)" v-if="getLoaderIcon(platform)" />
<FormattedTag :tag="platform" enforce-type="loader" />
</TagItem>
</template>
</div>
</div>
<div v-if="hasMultipleEnvironments" class="flex items-center">
@@ -162,7 +207,7 @@
class="z-[1] text-center"
>
<component :is="tag.icon" />
{{ formatMessage(tag.label) }}
{{ formatMessage(tag.label).replace('and', '&') }}
</TagItem>
</div>
</div>
@@ -233,6 +278,7 @@ import {
type GameVersionTag,
type Version,
} from '@modrinth/utils'
import { Menu } from 'floating-vue'
import { computed, type Ref, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
@@ -251,6 +297,11 @@ const formatDateTime = useFormatDateTime({
type VersionWithDisplayUrlEnding = Version & {
displayUrlEnding: string
environment?: Labrinth.Projects.v3.Environment
mrpack_loaders?: string[]
}
type DisplayVersion = VersionWithDisplayUrlEnding & {
noModLoader: boolean
}
const props = withDefaults(
@@ -278,6 +329,39 @@ const props = withDefaults(
},
)
function getModpackLoaders(version: VersionWithDisplayUrlEnding): string[] {
if (props.project.project_type !== 'modpack') {
return version.loaders
}
if (version.mrpack_loaders?.length) {
return version.mrpack_loaders
}
return version.loaders.filter((loader) => loader !== 'mrpack')
}
function hasNoModLoader(loaders: string[]): boolean {
return (
props.project.project_type === 'modpack' && loaders.length === 1 && loaders[0] === 'minecraft'
)
}
const normalizedVersions = computed<DisplayVersion[]>(() =>
props.versions.map((version) => {
const loaders = getModpackLoaders(version)
const noModLoader = hasNoModLoader(loaders)
return {
...version,
loaders: noModLoader ? [] : loaders,
noModLoader,
}
}),
)
const maxGameVersionTags = 6
const currentPage: Ref<number> = ref(1)
const pageSize: Ref<number> = ref(20)
const versionFilters: Ref<InstanceType<typeof VersionFilterControl> | null> = ref(null)
@@ -296,7 +380,7 @@ const hasMultipleEnvironments = computed(() => {
})
const filteredVersions = computed(() => {
return props.versions.filter(
return normalizedVersions.value.filter(
(version) =>
hasAnySelected(version.game_versions, selectedGameVersions.value) &&
hasAnySelected(version.loaders, selectedPlatforms.value) &&

View File

@@ -15,15 +15,22 @@
<section v-if="project.project_type !== 'resourcepack'" class="flex flex-col gap-2">
<h3 class="text-primary text-base m-0">{{ formatMessage(messages.platforms) }}</h3>
<div class="flex flex-wrap gap-1">
<TagItem
v-for="platform in project.loaders"
:key="`platform-tag-${platform}`"
:action="() => router.push(`/${project.project_type}s?g=categories:${platform}`)"
:style="`--_color: var(--color-platform-${platform})`"
>
<component :is="getLoaderIcon(platform)" v-if="getLoaderIcon(platform)" />
<FormattedTag :tag="platform" enforce-type="loader" />
</TagItem>
<template v-if="noModpackLoader">
<TagItem class="border !border-solid border-surface-5 hover:no-underline">
No mod loader
</TagItem>
</template>
<template v-else>
<TagItem
v-for="platform in project.loaders"
:key="`platform-tag-${platform}`"
:action="() => router.push(`/${project.project_type}s?g=categories:${platform}`)"
:style="`--_color: var(--color-platform-${platform})`"
>
<component :is="getLoaderIcon(platform)" v-if="getLoaderIcon(platform)" />
<FormattedTag :tag="platform" enforce-type="loader" />
</TagItem>
</template>
</div>
</section>
<section v-if="showEnvironments" class="flex flex-col gap-2">
@@ -85,6 +92,7 @@
</div>
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import {
ClientIcon,
getLoaderIcon,
@@ -93,7 +101,7 @@ import {
UserIcon,
} from '@modrinth/assets'
import { FormattedTag, TagItem } from '@modrinth/ui'
import type { EnvironmentV3, GameVersionTag, PlatformTag, ProjectV3Partial } from '@modrinth/utils'
import type { EnvironmentV3, GameVersionTag, PlatformTag } from '@modrinth/utils'
import { getVersionsToDisplay } from '@modrinth/utils'
import { type Component, computed } from 'vue'
import { useRouter } from 'vue-router'
@@ -128,17 +136,24 @@ const props = defineProps<{
gameVersions: GameVersionTag[]
loaders: PlatformTag[]
}
v3Metadata?: ProjectV3Partial
projectV3?: Labrinth.Projects.v3.Project
}>()
const noModpackLoader = computed(
() =>
props.projectV3?.project_types.includes('modpack') &&
props.projectV3?.mrpack_loaders.length === 1 &&
props.projectV3?.mrpack_loaders[0] === 'minecraft',
)
const showEnvironments = computed(
() =>
TYPES_WITH_ENVS.some((x) => props.v3Metadata?.project_types.includes(x)) &&
TYPES_WITH_ENVS.some((x) => props.projectV3?.project_types.includes(x)) &&
primaryEnvironment.value,
)
const primaryEnvironment = computed<EnvironmentV3 | undefined>(() =>
props.v3Metadata?.environment?.find((x) => x !== 'unknown'),
props.projectV3?.environment?.find((x) => x !== 'unknown'),
)
type EnvironmentTag = {