fix: versions v2 fixes (#5106)

* update dependencies step to show when cannot detect suggested dependencies

* rollback environment to previous copy

* implement disable close when uploading in modal

* pnpm prepr
This commit is contained in:
Truman Gao
2026-01-12 17:12:10 -07:00
committed by GitHub
parent 8d72a42be5
commit 40f5db64d8
9 changed files with 162 additions and 65 deletions

View File

@@ -7,6 +7,7 @@
:closable="true"
:close-on-click-outside="false"
:width="resolvedMaxWidth"
:disable-close="resolveCtxFn(currentStage.disableClose, context)"
>
<template #title>
<div
@@ -126,6 +127,7 @@ export interface StageConfigInput<T> {
hideStageInBreadcrumb?: MaybeCtxFn<T, boolean>
nonProgressStage?: MaybeCtxFn<T, boolean>
cannotNavigateForward?: MaybeCtxFn<T, boolean>
disableClose?: MaybeCtxFn<T, boolean>
leftButtonConfig: MaybeCtxFn<T, StageButtonConfig | null>
rightButtonConfig: MaybeCtxFn<T, StageButtonConfig | null>
/** Max width for the modal content and header defined in px (e.g., '460px', '600px'). Defaults to '460px'. */

View File

@@ -42,7 +42,7 @@
</slot>
</div>
<ButtonStyled v-if="closable" circular>
<button v-tooltip="'Close'" aria-label="Close" @click="hide">
<button v-tooltip="'Close'" aria-label="Close" :disabled="disableClose" @click="hide">
<XIcon aria-hidden="true" />
</button>
</ButtonStyled>
@@ -53,7 +53,7 @@
class="absolute top-4 right-4 z-10"
circular
>
<button v-tooltip="'Close'" aria-label="Close" @click="hide">
<button v-tooltip="'Close'" aria-label="Close" :disabled="disableClose" @click="hide">
<XIcon aria-hidden="true" />
</button>
</ButtonStyled>
@@ -141,6 +141,8 @@ const props = withDefaults(
maxWidth?: string
/** Width for the modal body (e.g., '460px', '600px'). */
width?: string
/** Disables all close actions (close button, ESC key, click outside). */
disableClose?: boolean
}>(),
{
type: true,
@@ -160,6 +162,7 @@ const props = withDefaults(
maxContentHeight: '70vh',
maxWidth: undefined,
width: undefined,
disableClose: false,
},
)
@@ -194,6 +197,7 @@ function show(event?: MouseEvent) {
}
function hide() {
if (props.disableClose) return
props.onHide?.()
visible.value = false
document.body.style.overflow = ''

View File

@@ -29,9 +29,9 @@
<section v-if="showEnvironments" class="flex flex-col gap-2">
<h3 class="text-primary text-base m-0">{{ formatMessage(messages.environments) }}</h3>
<div class="flex flex-wrap gap-1">
<TagItem v-for="(tag, tagIdx) in primaryEnvironmentTags" :key="`environment-tag-${tagIdx}`">
<TagItem v-for="tag in primaryEnvironmentTags" :key="`environment-tag-${tag.message.id}`">
<component :is="tag.icon" />
{{ formatMessage(tag.label) }}
{{ formatMessage(tag.message) }}
</TagItem>
</div>
</section>
@@ -88,12 +88,16 @@
import { ClientIcon, MonitorSmartphoneIcon, ServerIcon, UserIcon } from '@modrinth/assets'
import type { EnvironmentV3, GameVersionTag, PlatformTag, ProjectV3Partial } from '@modrinth/utils'
import { formatCategory, getVersionsToDisplay } from '@modrinth/utils'
import { computed } from 'vue'
import { type Component, computed } from 'vue'
import { useRouter } from 'vue-router'
import { defineMessages, useVIntl } from '../../composables/i18n'
import {
defineMessage,
defineMessages,
type MessageDescriptor,
useVIntl,
} from '../../composables/i18n'
import TagItem from '../base/TagItem.vue'
import { getEnvironmentTags } from './settings/environment/environments'
const { formatMessage } = useVIntl()
const router = useRouter()
@@ -129,8 +133,82 @@ const primaryEnvironment = computed<EnvironmentV3 | undefined>(() =>
props.v3Metadata?.environment?.find((x) => x !== 'unknown'),
)
type EnvironmentTag = {
icon: Component
message: MessageDescriptor
environments: EnvironmentV3[]
}
const environmentTags: EnvironmentTag[] = [
{
icon: ClientIcon,
message: defineMessage({
id: `project.about.compatibility.environments.client-side`,
defaultMessage: 'Client-side',
}),
environments: [
'client_only',
'client_only_server_optional',
'client_or_server',
'client_or_server_prefers_both',
],
},
{
icon: ServerIcon,
message: defineMessage({
id: `project.about.compatibility.environments.server-side`,
defaultMessage: 'Server-side',
}),
environments: [
'server_only',
'server_only_client_optional',
'client_or_server',
'client_or_server_prefers_both',
],
},
{
icon: ServerIcon,
message: defineMessage({
id: `project.about.compatibility.environments.dedicated-servers-only`,
defaultMessage: 'Dedicated servers only',
}),
environments: ['dedicated_server_only'],
},
{
icon: UserIcon,
message: defineMessage({
id: `project.about.compatibility.environments.singleplayer-only`,
defaultMessage: 'Singleplayer only',
}),
environments: ['singleplayer_only'],
},
{
icon: UserIcon,
message: defineMessage({
id: `project.about.compatibility.environments.singleplayer`,
defaultMessage: 'Singleplayer',
}),
environments: ['server_only'],
},
{
icon: MonitorSmartphoneIcon,
message: defineMessage({
id: `project.about.compatibility.environments.client-and-server`,
defaultMessage: 'Client and server',
}),
environments: [
'client_and_server',
'client_only_server_optional',
'server_only_client_optional',
'client_or_server_prefers_both',
],
},
]
const primaryEnvironmentTags = computed(() => {
return getEnvironmentTags(primaryEnvironment.value)
return primaryEnvironment.value
? environmentTags.filter((x) => x.environments.includes(primaryEnvironment.value ?? 'unknown'))
: []
})
const messages = defineMessages({

View File

@@ -123,25 +123,29 @@ export const ENVIRONMENTS_COPY: Record<
}
export const ENVIRONMENT_TAG_LABELS = {
client: defineMessage({
id: 'project.environment.tag.client',
defaultMessage: 'Client',
clientSide: defineMessage({
id: 'project.about.compatibility.environments.client-side',
defaultMessage: 'Client-side',
}),
server: defineMessage({
id: 'project.environment.tag.server',
defaultMessage: 'Server',
serverSide: defineMessage({
id: 'project.about.compatibility.environments.server-side',
defaultMessage: 'Server-side',
}),
dedicatedServersOnly: defineMessage({
id: 'project.about.compatibility.environments.dedicated-servers-only',
defaultMessage: 'Dedicated servers only',
}),
singleplayerOnly: defineMessage({
id: 'project.about.compatibility.environments.singleplayer-only',
defaultMessage: 'Singleplayer only',
}),
singleplayer: defineMessage({
id: 'project.environment.tag.singleplayer',
id: 'project.about.compatibility.environments.singleplayer',
defaultMessage: 'Singleplayer',
}),
clientOptional: defineMessage({
id: 'project.environment.tag.client-optional',
defaultMessage: 'Client optional',
}),
serverOptional: defineMessage({
id: 'project.environment.tag.server-optional',
defaultMessage: 'Server optional',
clientAndServer: defineMessage({
id: 'project.about.compatibility.environments.client-and-server',
defaultMessage: 'Client and server',
}),
unknown: defineMessage({
id: 'project.environment.tag.unknown',
@@ -158,48 +162,46 @@ export function getEnvironmentTags(
): Array<{ icon: Component | null; label: MessageDescriptor }> {
switch (environment) {
case 'client_only':
return [{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.client }]
return [{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientSide }]
case 'server_only':
return [
{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.server },
{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.serverSide },
{ icon: UserIcon, label: ENVIRONMENT_TAG_LABELS.singleplayer },
]
case 'singleplayer_only':
return [{ icon: UserIcon, label: ENVIRONMENT_TAG_LABELS.singleplayer }]
return [{ icon: UserIcon, label: ENVIRONMENT_TAG_LABELS.singleplayerOnly }]
case 'dedicated_server_only':
return [{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.server }]
return [{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.dedicatedServersOnly }]
case 'client_and_server':
return [
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.client },
{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.server },
]
return [{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientAndServer }]
case 'client_only_server_optional':
return [
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.client },
{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.serverOptional },
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientSide },
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientAndServer },
]
case 'server_only_client_optional':
return [
{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.server },
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientOptional },
{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.serverSide },
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientAndServer },
]
case 'client_or_server':
return [
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientOptional },
{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.serverOptional },
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientSide },
{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.serverSide },
]
case 'client_or_server_prefers_both':
return [
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientOptional },
{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.serverOptional },
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientSide },
{ icon: ServerIcon, label: ENVIRONMENT_TAG_LABELS.serverSide },
{ icon: ClientIcon, label: ENVIRONMENT_TAG_LABELS.clientAndServer },
]
case 'unknown':

View File

@@ -593,6 +593,24 @@
"project.about.compatibility.environments": {
"defaultMessage": "Supported environments"
},
"project.about.compatibility.environments.client-and-server": {
"defaultMessage": "Client and server"
},
"project.about.compatibility.environments.client-side": {
"defaultMessage": "Client-side"
},
"project.about.compatibility.environments.dedicated-servers-only": {
"defaultMessage": "Dedicated servers only"
},
"project.about.compatibility.environments.server-side": {
"defaultMessage": "Server-side"
},
"project.about.compatibility.environments.singleplayer": {
"defaultMessage": "Singleplayer"
},
"project.about.compatibility.environments.singleplayer-only": {
"defaultMessage": "Singleplayer only"
},
"project.about.compatibility.game.minecraftJava": {
"defaultMessage": "Minecraft: Java Edition"
},
@@ -713,24 +731,9 @@
"project.environment.singleplayer-only.title": {
"defaultMessage": "Singleplayer only"
},
"project.environment.tag.client": {
"defaultMessage": "Client"
},
"project.environment.tag.client-optional": {
"defaultMessage": "Client optional"
},
"project.environment.tag.not-applicable": {
"defaultMessage": "N/A"
},
"project.environment.tag.server": {
"defaultMessage": "Server"
},
"project.environment.tag.server-optional": {
"defaultMessage": "Server optional"
},
"project.environment.tag.singleplayer": {
"defaultMessage": "Singleplayer"
},
"project.environment.tag.unknown": {
"defaultMessage": "Unknown"
},