feat: implement selecting compatible versions when there are multiple

This commit is contained in:
tdgao
2026-07-07 13:55:37 -07:00
parent 1612720332
commit de1abf9040
2 changed files with 116 additions and 49 deletions
@@ -1,7 +1,25 @@
<template>
<div
class="grid grid-cols-[1fr_min-content] items-center gap-3 rounded-2xl bg-surface-2 px-3 py-3"
:role="selectable ? 'radio' : undefined"
:aria-checked="selectable ? selected : undefined"
:tabindex="selectable ? 0 : undefined"
class="grid items-center gap-3 rounded-2xl border border-solid px-3 py-3 transition-all"
:class="{
'grid-cols-[min-content_minmax(0,1fr)_min-content]': selectable,
'grid-cols-[minmax(0,1fr)_min-content]': !selectable,
'cursor-pointer border-brand bg-surface-4 text-contrast': selectable && selected,
'cursor-pointer border-surface-5 bg-surface-4 hover:brightness-[115%]':
selectable && !selected,
'border-transparent bg-surface-2': !selectable,
}"
@click="select"
@keydown.enter.self.prevent="select"
@keydown.space.self.prevent="select"
>
<template v-if="selectable">
<RadioButtonCheckedIcon v-if="selected" aria-hidden="true" class="size-5 text-brand" />
<RadioButtonIcon v-else aria-hidden="true" class="size-5 text-secondary" />
</template>
<div class="flex min-w-0 flex-col gap-1">
<div class="flex min-w-0 items-center gap-2">
<nuxt-link
@@ -21,13 +39,21 @@
<span v-tooltip="publishedTooltip" class="min-w-0 truncate capitalize">
{{ publishedLabel }}
</span>
<div v-if="primaryFile" class="h-1.5 w-1.5 flex-shrink-0 rounded-full bg-surface-5"></div>
<div
v-if="primaryFile"
class="h-1.5 w-1.5 flex-shrink-0 rounded-full bg-primary opacity-30"
></div>
<span v-if="primaryFile" class="flex-shrink-0">
{{ primaryFileSizeLabel }}
</span>
</div>
</div>
<ButtonStyled v-if="primaryFile" :color="color" :type="type" :circular="circular">
<ButtonStyled
v-if="primaryFile && showDownload"
:color="color"
:type="type"
:circular="circular"
>
<a
v-tooltip="circular ? formatMessage(messages.download) : null"
:href="primaryFileDownloadUrl"
@@ -50,7 +76,7 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { DownloadIcon } from '@modrinth/assets'
import { DownloadIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
import {
ButtonStyled,
type CdnDownloadReason,
@@ -84,6 +110,9 @@ const props = withDefaults(
color?: 'brand' | 'standard'
type?: 'standard' | 'transparent'
circular?: boolean
selectable?: boolean
selected?: boolean
showDownload?: boolean
}>(),
{
downloadReason: 'standalone',
@@ -92,11 +121,15 @@ const props = withDefaults(
color: 'brand',
type: 'standard',
circular: false,
selectable: false,
selected: false,
showDownload: true,
},
)
const emit = defineEmits<{
download: []
select: []
}>()
const { createProjectDownloadUrl } = useCdnDownloadContext()
@@ -131,6 +164,11 @@ const primaryFileSizeLabel = computed(() => {
return formatBytes(primaryFile.value.size)
})
function select() {
if (!props.selectable) return
emit('select')
}
const messages = defineMessages({
downloadVersion: {
id: 'project.download.download-version',
@@ -80,35 +80,47 @@
</Combobox>
</div>
<div v-if="selectedVersion && downloadDataLoaded" class="flex flex-col gap-2.5">
<div
v-if="selectedVersion && downloadDataLoaded"
:role="compatibleVersions.length > 1 ? 'radiogroup' : undefined"
:aria-label="
compatibleVersions.length > 1 ? formatMessage(messages.compatibleVersionTitle) : undefined
"
class="flex flex-col gap-2.5"
>
<h3
v-if="[...suggestedPreReleaseVersions, selectedVersion].length > 1"
v-if="compatibleVersions.length > 1"
class="relative top-0.5 m-0 text-base font-semibold text-contrast"
>
{{ formatMessage(messages.compatibleVersionTitle) }}
</h3>
<CompatibleVersionCard
v-for="compatibleVersion in compatibleVersions"
:key="compatibleVersion.id"
:project="project"
:version="selectedVersion"
:version="compatibleVersion"
:download-reason="downloadReason"
:current-game-version="currentGameVersion"
:current-platform="currentPlatform"
:color="hasAdditionalDownloads ? 'standard' : 'brand'"
:type="hasAdditionalDownloads ? 'transparent' : 'standard'"
:circular="hasAdditionalDownloads"
@download="emit('download')"
/>
<CompatibleVersionCard
v-for="suggestedVersion in suggestedPreReleaseVersions"
:key="suggestedVersion.version.id"
:project="project"
:version="suggestedVersion.version"
:download-reason="downloadReason"
:current-game-version="currentGameVersion"
:current-platform="currentPlatform"
color="standard"
type="transparent"
circular
:selectable="compatibleVersions.length > 1"
:selected="compatibleVersion.id === selectedVersion.id"
:show-download="compatibleVersions.length === 1 || compatibleVersion.id === selectedVersion.id"
:color="
compatibleVersion.id === selectedVersion.id &&
compatibleVersions.length === 1 &&
!hasAdditionalDownloads
? 'brand'
: 'standard'
"
:type="
compatibleVersion.id === selectedVersion.id &&
compatibleVersions.length === 1 &&
!hasAdditionalDownloads
? 'standard'
: 'transparent'
"
:circular="hasAdditionalDownloads || compatibleVersions.length > 1"
@select="selectCompatibleVersion(compatibleVersion)"
@download="emit('download')"
/>
</div>
@@ -154,10 +166,6 @@ type DownloadableFile = {
filename: string
}
type SuggestedPreReleaseVersion = {
version: Labrinth.Versions.v3.Version
}
const props = withDefaults(
defineProps<{
project: DownloadModalProject
@@ -196,6 +204,7 @@ const tags = useGeneratedState()
const userSelectedGameVersion = ref<string | null>(props.initialGameVersion)
const userSelectedPlatform = ref<string | null>(props.initialPlatform)
const userSelectedCompatibleVersionId = ref<string | null>(null)
const showAllVersions = ref(defaultShowAllVersions())
const versionFilter = ref('')
@@ -367,10 +376,42 @@ const filteredAlpha = computed<Labrinth.Versions.v3.Version | undefined>(() => {
return latestVersionByType('alpha')
})
const selectedVersion = computed<Labrinth.Versions.v3.Version | null>(() => {
const defaultSelectedVersion = computed<Labrinth.Versions.v3.Version | null>(() => {
return filteredRelease.value || filteredBeta.value || filteredAlpha.value || null
})
const suggestedPreReleaseVersions = computed<Labrinth.Versions.v3.Version[]>(() => {
if (!defaultSelectedVersion.value || defaultSelectedVersion.value.version_type !== 'release') return []
const versions: Labrinth.Versions.v3.Version[] = []
const beta = filteredBeta.value
if (beta && isNewerThan(beta, defaultSelectedVersion.value)) {
versions.push(beta)
}
const alpha = filteredAlpha.value
if (alpha && isNewerThan(alpha, defaultSelectedVersion.value)) {
versions.push(alpha)
}
return versions
})
const compatibleVersions = computed<Labrinth.Versions.v3.Version[]>(() => {
if (!defaultSelectedVersion.value) return []
return [defaultSelectedVersion.value, ...suggestedPreReleaseVersions.value]
})
const selectedVersion = computed<Labrinth.Versions.v3.Version | null>(() => {
return (
compatibleVersions.value.find(
(version) => version.id === userSelectedCompatibleVersionId.value,
) ||
defaultSelectedVersion.value ||
null
)
})
const selectedPrimaryFile = computed<Labrinth.Versions.v3.VersionFile | null>(() => {
return (
selectedVersion.value?.files?.find((file) => file.primary) ||
@@ -379,27 +420,6 @@ const selectedPrimaryFile = computed<Labrinth.Versions.v3.VersionFile | null>(()
)
})
const suggestedPreReleaseVersions = computed<SuggestedPreReleaseVersion[]>(() => {
if (!selectedVersion.value || selectedVersion.value.version_type !== 'release') return []
const versions: SuggestedPreReleaseVersion[] = []
const beta = filteredBeta.value
if (beta && isNewerThan(beta, selectedVersion.value)) {
versions.push({
version: beta,
})
}
const alpha = filteredAlpha.value
if (alpha && isNewerThan(alpha, selectedVersion.value)) {
versions.push({
version: alpha,
})
}
return versions
})
const hasAdditionalDownloads = computed(() => {
const hrefs = new Set<string>()
@@ -428,11 +448,16 @@ watch(
{ immediate: true },
)
watch([currentGameVersion, currentPlatform], () => {
userSelectedCompatibleVersionId.value = null
})
watch(
() => props.resetKey,
() => {
userSelectedGameVersion.value = props.initialGameVersion
userSelectedPlatform.value = props.initialPlatform
userSelectedCompatibleVersionId.value = null
showAllVersions.value = defaultShowAllVersions()
versionFilter.value = ''
},
@@ -451,6 +476,10 @@ function selectPlatform(platform?: string) {
emit('selectPlatform', platform)
}
function selectCompatibleVersion(version: Labrinth.Versions.v3.Version) {
userSelectedCompatibleVersionId.value = version.id
}
function latestVersionByType(type: Labrinth.Versions.v3.VersionChannel) {
return filteredVersions.value
.filter((version) => version.version_type === type)