You've already forked AstralRinth
forked from didirus/AstralRinth
Versions environments updates (#4949)
* add environment to version page metadata card * remove environment migration warnings * show settings/environments in nav only for staff * use v2 versions route due to regressions * add modpack incorrect loaders migration * remove modpack migration step * remove unused var * run pnpm intl:extract * componentize environment migration page * rename environment selector * rename environment selector pt2 * add migration modal to admonition * hide environments in settings and show message * show environment in project versions table * pnpm fix * pnpm fix on ui package * intl:extract * fix: .value * lower case file * add icon to environment tags and use i18n * Update apps/frontend/src/pages/[type]/[id].vue Co-authored-by: Calum H. <contact@cal.engineer> Signed-off-by: Truman Gao <106889354+tdgao@users.noreply.github.com> * open migration modal from warning icon in project dashboard * fix settings side nav icon * use useRoute composable * pnpm fix * intl:extract * fix import * fix import again * run pnpm prepr * fix designMessage import * fix environment fetch * fix environment fetch properly without key conflict * fix environment refetching * fix not using current versions in table to check different environments * fix download tooltip --------- Signed-off-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
This commit is contained in:
@@ -155,7 +155,14 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { EditIcon } from '@modrinth/assets'
|
import { EditIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, Chips, TagItem } from '@modrinth/ui'
|
import {
|
||||||
|
ButtonStyled,
|
||||||
|
Chips,
|
||||||
|
defineMessages,
|
||||||
|
ENVIRONMENTS_COPY,
|
||||||
|
TagItem,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { formatCategory } from '@modrinth/utils'
|
import { formatCategory } from '@modrinth/utils'
|
||||||
|
|
||||||
import { useGeneratedState } from '~/composables/generated'
|
import { useGeneratedState } from '~/composables/generated'
|
||||||
@@ -215,61 +222,51 @@ const usingDetectedLoaders = computed(() => {
|
|||||||
return loadersMatch
|
return loadersMatch
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const noEnvironmentMessage = defineMessages({
|
||||||
|
title: {
|
||||||
|
id: 'version.environment.none.title',
|
||||||
|
defaultMessage: 'No environment set',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
id: 'version.environment.none.description',
|
||||||
|
defaultMessage: 'The environment for this version has not been specified.',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const unknownEnvironmentMessage = defineMessages({
|
||||||
|
title: {
|
||||||
|
id: 'version.environment.unknown.title',
|
||||||
|
defaultMessage: 'Unknown environment',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
id: 'version.environment.unknown.description',
|
||||||
|
defaultMessage: 'The environment: "{environment}" is not recognized.',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const environmentCopy = computed(() => {
|
const environmentCopy = computed(() => {
|
||||||
const emptyMessage = {
|
if (!draftVersion.value.environment) {
|
||||||
title: 'No environment set',
|
return {
|
||||||
description: 'The environment for this version has not been specified.',
|
title: formatMessage(noEnvironmentMessage.title),
|
||||||
}
|
description: formatMessage(noEnvironmentMessage.description),
|
||||||
if (!draftVersion.value.environment) return emptyMessage
|
|
||||||
|
|
||||||
const envCopy: Record<string, { title: string; description: string }> = {
|
|
||||||
client_only: {
|
|
||||||
title: 'Client-side only',
|
|
||||||
description: 'All functionality is done client-side and is compatible with vanilla servers.',
|
|
||||||
},
|
|
||||||
server_only: {
|
|
||||||
title: 'Server-side only',
|
|
||||||
description: 'All functionality is done server-side and is compatible with vanilla clients.',
|
|
||||||
},
|
|
||||||
singleplayer_only: {
|
|
||||||
title: 'Singleplayer only',
|
|
||||||
description: 'Only functions in Singleplayer or when not connected to a Multiplayer server.',
|
|
||||||
},
|
|
||||||
dedicated_server_only: {
|
|
||||||
title: 'Server-side only',
|
|
||||||
description: 'All functionality is done server-side and is compatible with vanilla clients.',
|
|
||||||
},
|
|
||||||
client_and_server: {
|
|
||||||
title: 'Client and server',
|
|
||||||
description: 'Has some functionality on both the client and server, even if only partially.',
|
|
||||||
},
|
|
||||||
client_only_server_optional: {
|
|
||||||
title: 'Client and server',
|
|
||||||
description: 'Has some functionality on both the client and server, even if only partially.',
|
|
||||||
},
|
|
||||||
server_only_client_optional: {
|
|
||||||
title: 'Client and server',
|
|
||||||
description: 'Has some functionality on both the client and server, even if only partially.',
|
|
||||||
},
|
|
||||||
client_or_server: {
|
|
||||||
title: 'Client and server',
|
|
||||||
description: 'Has some functionality on both the client and server, even if only partially.',
|
|
||||||
},
|
|
||||||
client_or_server_prefers_both: {
|
|
||||||
title: 'Client and server',
|
|
||||||
description: 'Has some functionality on both the client and server, even if only partially.',
|
|
||||||
},
|
|
||||||
unknown: {
|
|
||||||
title: 'Unknown environment',
|
|
||||||
description: 'The environment for this version could not be determined.',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
envCopy[draftVersion.value.environment] || {
|
|
||||||
title: 'Unknown environment',
|
|
||||||
description: `The environment: "${draftVersion.value.environment}" is not recognized.`,
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
const envCopy = ENVIRONMENTS_COPY[draftVersion.value.environment]
|
||||||
|
if (envCopy) {
|
||||||
|
return {
|
||||||
|
title: formatMessage(envCopy.title),
|
||||||
|
description: formatMessage(envCopy.description),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: formatMessage(unknownEnvironmentMessage.title),
|
||||||
|
description: formatMessage(unknownEnvironmentMessage.description, {
|
||||||
|
environment: draftVersion.value.environment,
|
||||||
|
}),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="sm:w-[512px]">
|
<div class="sm:w-[512px]">
|
||||||
<ProjectSettingsEnvSelector v-model="draftVersion.environment" />
|
<EnvironmentSelector v-model="draftVersion.environment" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ProjectSettingsEnvSelector } from '@modrinth/ui'
|
import { EnvironmentSelector } from '@modrinth/ui'
|
||||||
|
|
||||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||||
|
|
||||||
|
|||||||
@@ -2130,7 +2130,7 @@
|
|||||||
"message": "Learn more about this change"
|
"message": "Learn more about this change"
|
||||||
},
|
},
|
||||||
"project.environment.migration.message": {
|
"project.environment.migration.message": {
|
||||||
"message": "We've just overhauled the Environments system on Modrinth and new options are now available. Please visit your project's settings and verify that the metadata is correct."
|
"message": "We've just overhauled the Environments system on Modrinth and new options are now available. Please verify that the metadata is correct."
|
||||||
},
|
},
|
||||||
"project.environment.migration.review-button": {
|
"project.environment.migration.review-button": {
|
||||||
"message": "Review environment settings"
|
"message": "Review environment settings"
|
||||||
@@ -2177,36 +2177,6 @@
|
|||||||
"project.notification.updated.title": {
|
"project.notification.updated.title": {
|
||||||
"message": "Project updated"
|
"message": "Project updated"
|
||||||
},
|
},
|
||||||
"project.settings.environment.notice.missing-env.description": {
|
|
||||||
"message": "Your project is missing environment metadata, please select the appropriate option below."
|
|
||||||
},
|
|
||||||
"project.settings.environment.notice.missing-env.title": {
|
|
||||||
"message": "Please select an environment for your project"
|
|
||||||
},
|
|
||||||
"project.settings.environment.notice.multiple-environments.description": {
|
|
||||||
"message": "Different versions of your project have different environments selected, so you can't edit them globally at this time."
|
|
||||||
},
|
|
||||||
"project.settings.environment.notice.multiple-environments.title": {
|
|
||||||
"message": "Your project has multiple environments"
|
|
||||||
},
|
|
||||||
"project.settings.environment.notice.review-options.description": {
|
|
||||||
"message": "We've just overhauled the Environments system on Modrinth and new options are now available. Please ensure the correct option is selected below and then click 'Verify' when you're done!"
|
|
||||||
},
|
|
||||||
"project.settings.environment.notice.review-options.title": {
|
|
||||||
"message": "Please review the options below"
|
|
||||||
},
|
|
||||||
"project.settings.environment.notice.wrong-project-type.description": {
|
|
||||||
"message": "Only mod or modpack projects can have environment metadata."
|
|
||||||
},
|
|
||||||
"project.settings.environment.notice.wrong-project-type.title": {
|
|
||||||
"message": "This project type does not support environment metadata"
|
|
||||||
},
|
|
||||||
"project.settings.environment.verification.verify-button": {
|
|
||||||
"message": "Verify"
|
|
||||||
},
|
|
||||||
"project.settings.environment.verification.verify-text": {
|
|
||||||
"message": "Verify that this project's environment is set correctly."
|
|
||||||
},
|
|
||||||
"project.settings.general.name.description": {
|
"project.settings.general.name.description": {
|
||||||
"message": "Avoid prefixes, suffixes, parentheticals, or added descriptions—just the project's actual name."
|
"message": "Avoid prefixes, suffixes, parentheticals, or added descriptions—just the project's actual name."
|
||||||
},
|
},
|
||||||
@@ -2917,5 +2887,17 @@
|
|||||||
},
|
},
|
||||||
"ui.latest-news-row.view-all": {
|
"ui.latest-news-row.view-all": {
|
||||||
"message": "View all news"
|
"message": "View all news"
|
||||||
|
},
|
||||||
|
"version.environment.none.description": {
|
||||||
|
"message": "The environment for this version has not been specified."
|
||||||
|
},
|
||||||
|
"version.environment.none.title": {
|
||||||
|
"message": "No environment set"
|
||||||
|
},
|
||||||
|
"version.environment.unknown.description": {
|
||||||
|
"message": "The environment: \"{environment}\" is not recognized."
|
||||||
|
},
|
||||||
|
"version.environment.unknown.title": {
|
||||||
|
"message": "Unknown environment"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,11 +43,11 @@
|
|||||||
<NuxtPage
|
<NuxtPage
|
||||||
v-model:project="project"
|
v-model:project="project"
|
||||||
v-model:project-v3="projectV3"
|
v-model:project-v3="projectV3"
|
||||||
v-model:versions="versions"
|
|
||||||
v-model:members="members"
|
v-model:members="members"
|
||||||
v-model:all-members="allMembers"
|
v-model:all-members="allMembers"
|
||||||
v-model:dependencies="dependencies"
|
v-model:dependencies="dependencies"
|
||||||
v-model:organization="organization"
|
v-model:organization="organization"
|
||||||
|
v-model:versions="versions"
|
||||||
:current-member="currentMember"
|
:current-member="currentMember"
|
||||||
:patch-project="patchProject"
|
:patch-project="patchProject"
|
||||||
:patch-icon="patchIcon"
|
:patch-icon="patchIcon"
|
||||||
@@ -457,9 +457,6 @@
|
|||||||
|
|
||||||
<div class="hidden sm:contents">
|
<div class="hidden sm:contents">
|
||||||
<ButtonStyled
|
<ButtonStyled
|
||||||
v-tooltip="
|
|
||||||
auth.user && currentMember ? formatMessage(commonMessages.downloadButton) : ''
|
|
||||||
"
|
|
||||||
size="large"
|
size="large"
|
||||||
:color="
|
:color="
|
||||||
(auth.user && currentMember) || route.name === 'type-id-version-version'
|
(auth.user && currentMember) || route.name === 'type-id-version-version'
|
||||||
@@ -468,7 +465,12 @@
|
|||||||
"
|
"
|
||||||
:circular="auth.user && currentMember"
|
:circular="auth.user && currentMember"
|
||||||
>
|
>
|
||||||
<button @click="(event) => downloadModal.show(event)">
|
<button
|
||||||
|
v-tooltip="
|
||||||
|
auth.user && currentMember ? formatMessage(commonMessages.downloadButton) : ''
|
||||||
|
"
|
||||||
|
@click="(event) => downloadModal.show(event)"
|
||||||
|
>
|
||||||
<DownloadIcon aria-hidden="true" />
|
<DownloadIcon aria-hidden="true" />
|
||||||
{{
|
{{
|
||||||
auth.user && currentMember ? '' : formatMessage(commonMessages.downloadButton)
|
auth.user && currentMember ? '' : formatMessage(commonMessages.downloadButton)
|
||||||
@@ -778,9 +780,9 @@
|
|||||||
{{ formatMessage(messages.environmentMigrationLink) }}
|
{{ formatMessage(messages.environmentMigrationLink) }}
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<ButtonStyled v-if="hasEditDetailsPermission" color="orange">
|
<ButtonStyled v-if="hasEditDetailsPermission" color="orange">
|
||||||
<nuxt-link :to="`/project/${project.id}/settings/environment`" class="mt-3 w-fit">
|
<button class="mt-3 w-fit" @click="() => projectEnvironmentModal.show()">
|
||||||
<SettingsIcon /> {{ formatMessage(messages.reviewEnvironmentSettings) }}
|
<SettingsIcon /> {{ formatMessage(messages.reviewEnvironmentSettings) }}
|
||||||
</nuxt-link>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
<MessageBanner v-if="project.status === 'archived'" message-type="warning" class="my-4">
|
<MessageBanner v-if="project.status === 'archived'" message-type="warning" class="my-4">
|
||||||
@@ -935,6 +937,10 @@
|
|||||||
@toggle-collapsed="collapsedModerationChecklist = !collapsedModerationChecklist"
|
@toggle-collapsed="collapsedModerationChecklist = !collapsedModerationChecklist"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<template v-if="hasEditDetailsPermission">
|
||||||
|
<ProjectEnvironmentModal ref="projectEnvironmentModal" />
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -977,6 +983,7 @@ import {
|
|||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
PopoutMenu,
|
PopoutMenu,
|
||||||
ProjectBackgroundGradient,
|
ProjectBackgroundGradient,
|
||||||
|
ProjectEnvironmentModal,
|
||||||
ProjectHeader,
|
ProjectHeader,
|
||||||
ProjectSidebarCompatibility,
|
ProjectSidebarCompatibility,
|
||||||
ProjectSidebarCreators,
|
ProjectSidebarCreators,
|
||||||
@@ -994,6 +1001,7 @@ import { formatCategory, formatPrice, formatProjectType, renderString } from '@m
|
|||||||
import { useLocalStorage } from '@vueuse/core'
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Tooltip } from 'floating-vue'
|
import { Tooltip } from 'floating-vue'
|
||||||
|
import { useTemplateRef } from 'vue'
|
||||||
|
|
||||||
import { navigateTo } from '#app'
|
import { navigateTo } from '#app'
|
||||||
import Accordion from '~/components/ui/Accordion.vue'
|
import Accordion from '~/components/ui/Accordion.vue'
|
||||||
@@ -1037,6 +1045,8 @@ const gameVersionFilterInput = ref()
|
|||||||
|
|
||||||
const versionFilter = ref('')
|
const versionFilter = ref('')
|
||||||
|
|
||||||
|
const projectEnvironmentModal = useTemplateRef('projectEnvironmentModal')
|
||||||
|
|
||||||
const baseId = useId()
|
const baseId = useId()
|
||||||
|
|
||||||
const currentGameVersion = computed(() => {
|
const currentGameVersion = computed(() => {
|
||||||
@@ -1186,7 +1196,7 @@ const messages = defineMessages({
|
|||||||
environmentMigrationMessage: {
|
environmentMigrationMessage: {
|
||||||
id: 'project.environment.migration.message',
|
id: 'project.environment.migration.message',
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
"We've just overhauled the Environments system on Modrinth and new options are now available. Please visit your project's settings and verify that the metadata is correct.",
|
"We've just overhauled the Environments system on Modrinth and new options are now available. Please verify that the metadata is correct.",
|
||||||
},
|
},
|
||||||
environmentMigrationTitle: {
|
environmentMigrationTitle: {
|
||||||
id: 'project.environment.migration.title',
|
id: 'project.environment.migration.title',
|
||||||
@@ -1459,21 +1469,25 @@ let project,
|
|||||||
resetMembers,
|
resetMembers,
|
||||||
dependencies,
|
dependencies,
|
||||||
versions,
|
versions,
|
||||||
resetVersions,
|
versionsV3,
|
||||||
|
resetVersionsV2,
|
||||||
organization,
|
organization,
|
||||||
resetOrganization,
|
resetOrganization,
|
||||||
projectV2Error,
|
projectV2Error,
|
||||||
projectV3Error,
|
projectV3Error,
|
||||||
membersError,
|
membersError,
|
||||||
dependenciesError,
|
dependenciesError,
|
||||||
versionsError
|
versionsError,
|
||||||
|
versionsV3Error,
|
||||||
|
resetVersionsV3
|
||||||
try {
|
try {
|
||||||
;[
|
;[
|
||||||
{ data: project, error: projectV2Error, refresh: resetProjectV2 },
|
{ data: project, error: projectV2Error, refresh: resetProjectV2 },
|
||||||
{ data: projectV3, error: projectV3Error, refresh: resetProjectV3 },
|
{ data: projectV3, error: projectV3Error, refresh: resetProjectV3 },
|
||||||
{ data: allMembers, error: membersError, refresh: resetMembers },
|
{ data: allMembers, error: membersError, refresh: resetMembers },
|
||||||
{ data: dependencies, error: dependenciesError },
|
{ data: dependencies, error: dependenciesError },
|
||||||
{ data: versions, error: versionsError, refresh: resetVersions },
|
{ data: versions, error: versionsError, refresh: resetVersionsV2 },
|
||||||
|
{ data: versionsV3, error: versionsV3Error, refresh: resetVersionsV3 },
|
||||||
{ data: organization, refresh: resetOrganization },
|
{ data: organization, refresh: resetOrganization },
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
useAsyncData(`project/${projectId.value}`, () => useBaseFetch(`project/${projectId.value}`), {
|
useAsyncData(`project/${projectId.value}`, () => useBaseFetch(`project/${projectId.value}`), {
|
||||||
@@ -1515,6 +1529,9 @@ try {
|
|||||||
useAsyncData(`project/${projectId.value}/version`, () =>
|
useAsyncData(`project/${projectId.value}/version`, () =>
|
||||||
useBaseFetch(`project/${projectId.value}/version`),
|
useBaseFetch(`project/${projectId.value}/version`),
|
||||||
),
|
),
|
||||||
|
useAsyncData(`project/${projectId.value}/version/v3`, () =>
|
||||||
|
useBaseFetch(`project/${projectId.value}/version`, { apiVersion: 3 }),
|
||||||
|
),
|
||||||
useAsyncData(`project/${projectId.value}/organization`, () =>
|
useAsyncData(`project/${projectId.value}/organization`, () =>
|
||||||
useBaseFetch(`project/${projectId.value}/organization`, { apiVersion: 3 }),
|
useBaseFetch(`project/${projectId.value}/organization`, { apiVersion: 3 }),
|
||||||
),
|
),
|
||||||
@@ -1523,6 +1540,11 @@ try {
|
|||||||
await updateProjectRoute()
|
await updateProjectRoute()
|
||||||
|
|
||||||
versions = shallowRef(toRaw(versions))
|
versions = shallowRef(toRaw(versions))
|
||||||
|
versionsV3 = shallowRef(toRaw(versionsV3))
|
||||||
|
versions.value = versions.value.map((v) => ({
|
||||||
|
...v,
|
||||||
|
environment: versionsV3.value?.find((v3) => v3.id === v.id)?.environment,
|
||||||
|
}))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw createError({
|
throw createError({
|
||||||
fatal: true,
|
fatal: true,
|
||||||
@@ -1555,6 +1577,16 @@ async function resetProject() {
|
|||||||
await resetProjectV3()
|
await resetProjectV3()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resetVersions() {
|
||||||
|
await resetVersionsV2()
|
||||||
|
await resetVersionsV3()
|
||||||
|
|
||||||
|
versions.value = versions.value.map((v) => ({
|
||||||
|
...v,
|
||||||
|
environment: versionsV3.value?.find((v3) => v3.id === v.id)?.environment,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
function handleError(err, project = false) {
|
function handleError(err, project = false) {
|
||||||
if (err.value && err.value.statusCode) {
|
if (err.value && err.value.statusCode) {
|
||||||
throw createError({
|
throw createError({
|
||||||
@@ -1573,6 +1605,7 @@ handleError(projectV3Error)
|
|||||||
handleError(membersError)
|
handleError(membersError)
|
||||||
handleError(dependenciesError)
|
handleError(dependenciesError)
|
||||||
handleError(versionsError)
|
handleError(versionsError)
|
||||||
|
handleError(versionsV3Error)
|
||||||
|
|
||||||
if (!project.value) {
|
if (!project.value) {
|
||||||
throw createError({
|
throw createError({
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
AlignLeftIcon,
|
AlignLeftIcon,
|
||||||
BookTextIcon,
|
BookTextIcon,
|
||||||
ChartIcon,
|
ChartIcon,
|
||||||
|
GlobeIcon,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
@@ -16,7 +17,7 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { Project, ProjectV3Partial } from '@modrinth/utils'
|
import { isStaff, type Project, type ProjectV3Partial } from '@modrinth/utils'
|
||||||
import { useLocalStorage, useScroll } from '@vueuse/core'
|
import { useLocalStorage, useScroll } from '@vueuse/core'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ import NavStack from '~/components/ui/NavStack.vue'
|
|||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
currentMember: any
|
currentMember: any
|
||||||
patchProject: any
|
patchProject: any
|
||||||
patchIcon: any
|
patchIcon: any
|
||||||
@@ -47,6 +48,11 @@ const organization = defineModel<any>('organization')
|
|||||||
|
|
||||||
const navItems = computed(() => {
|
const navItems = computed(() => {
|
||||||
const base = `${project.value.project_type}/${project.value.slug ? project.value.slug : project.value.id}`
|
const base = `${project.value.project_type}/${project.value.slug ? project.value.slug : project.value.id}`
|
||||||
|
|
||||||
|
const showEnvironment =
|
||||||
|
projectV3.value.project_types.some((type) => ['mod', 'modpack'].includes(type)) &&
|
||||||
|
isStaff(props.currentMember.user)
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
link: `/${base}/settings`,
|
link: `/${base}/settings`,
|
||||||
@@ -101,6 +107,13 @@ const navItems = computed(() => {
|
|||||||
label: formatMessage(commonProjectSettingsMessages.analytics),
|
label: formatMessage(commonProjectSettingsMessages.analytics),
|
||||||
icon: ChartIcon,
|
icon: ChartIcon,
|
||||||
},
|
},
|
||||||
|
{ type: 'heading', label: 'moderation', shown: showEnvironment },
|
||||||
|
{
|
||||||
|
link: `/${base}/settings/environment`,
|
||||||
|
label: formatMessage(commonProjectSettingsMessages.environment),
|
||||||
|
icon: GlobeIcon,
|
||||||
|
shown: showEnvironment,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
return items.filter(Boolean) as any[]
|
return items.filter(Boolean) as any[]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,177 +1,36 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { CheckIcon } from '@modrinth/assets'
|
|
||||||
import {
|
|
||||||
Admonition,
|
|
||||||
commonProjectSettingsMessages,
|
|
||||||
defineMessages,
|
|
||||||
injectModrinthClient,
|
|
||||||
injectNotificationManager,
|
|
||||||
injectProjectPageContext,
|
|
||||||
ProjectSettingsEnvSelector,
|
|
||||||
UnsavedChangesPopup,
|
|
||||||
useSavable,
|
|
||||||
useVIntl,
|
|
||||||
} from '@modrinth/ui'
|
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
|
||||||
|
|
||||||
const { currentMember, projectV2, projectV3, refreshProject } = injectProjectPageContext()
|
|
||||||
const { handleError } = injectNotificationManager()
|
|
||||||
const client = injectModrinthClient()
|
|
||||||
|
|
||||||
const saving = ref(false)
|
|
||||||
|
|
||||||
const supportsEnvironment = computed(() =>
|
|
||||||
projectV3.value.project_types.some((type) => ['mod', 'modpack'].includes(type)),
|
|
||||||
)
|
|
||||||
|
|
||||||
const needsToVerify = computed(
|
|
||||||
() =>
|
|
||||||
projectV3.value.side_types_migration_review_status === 'pending' &&
|
|
||||||
(projectV3.value.environment?.length ?? 0) > 0 &&
|
|
||||||
projectV3.value.environment?.[0] !== 'unknown' &&
|
|
||||||
supportsEnvironment.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
const hasPermission = computed(() => {
|
|
||||||
const EDIT_DETAILS = 1 << 2
|
|
||||||
return (currentMember.value?.permissions & EDIT_DETAILS) === EDIT_DETAILS
|
|
||||||
})
|
|
||||||
|
|
||||||
function getInitialEnv() {
|
|
||||||
return projectV3.value.environment?.length === 1 ? projectV3.value.environment[0] : undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const { saved, current, reset, save } = useSavable(
|
|
||||||
() => ({
|
|
||||||
environment: getInitialEnv(),
|
|
||||||
side_types_migration_review_status: projectV3.value.side_types_migration_review_status,
|
|
||||||
}),
|
|
||||||
({ environment, side_types_migration_review_status }) => {
|
|
||||||
saving.value = true
|
|
||||||
side_types_migration_review_status = 'reviewed'
|
|
||||||
client.labrinth.projects_v3
|
|
||||||
.edit(projectV2.value.id, { environment, side_types_migration_review_status })
|
|
||||||
.then(() => refreshProject().then(reset))
|
|
||||||
.catch(handleError)
|
|
||||||
.finally(() => (saving.value = false))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
// Set current to reviewed, which will trigger unsaved changes popup.
|
|
||||||
// It should not be possible to save without reviewing it.
|
|
||||||
const originalEnv = getInitialEnv()
|
|
||||||
if (originalEnv && originalEnv !== 'unknown') {
|
|
||||||
current.value.side_types_migration_review_status = 'reviewed'
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
verifyButton: {
|
|
||||||
id: 'project.settings.environment.verification.verify-button',
|
|
||||||
defaultMessage: 'Verify',
|
|
||||||
},
|
|
||||||
verifyLabel: {
|
|
||||||
id: 'project.settings.environment.verification.verify-text',
|
|
||||||
defaultMessage: `Verify that this project's environment is set correctly.`,
|
|
||||||
},
|
|
||||||
wrongProjectTypeTitle: {
|
|
||||||
id: 'project.settings.environment.notice.wrong-project-type.title',
|
|
||||||
defaultMessage: `This project type does not support environment metadata`,
|
|
||||||
},
|
|
||||||
wrongProjectTypeDescription: {
|
|
||||||
id: 'project.settings.environment.notice.wrong-project-type.description',
|
|
||||||
defaultMessage: `Only mod or modpack projects can have environment metadata.`,
|
|
||||||
},
|
|
||||||
missingEnvTitle: {
|
|
||||||
id: 'project.settings.environment.notice.missing-env.title',
|
|
||||||
defaultMessage: `Please select an environment for your project`,
|
|
||||||
},
|
|
||||||
missingEnvDescription: {
|
|
||||||
id: 'project.settings.environment.notice.missing-env.description',
|
|
||||||
defaultMessage: `Your project is missing environment metadata, please select the appropriate option below.`,
|
|
||||||
},
|
|
||||||
multipleEnvironmentsTitle: {
|
|
||||||
id: 'project.settings.environment.notice.multiple-environments.title',
|
|
||||||
defaultMessage: 'Your project has multiple environments',
|
|
||||||
},
|
|
||||||
multipleEnvironmentsDescription: {
|
|
||||||
id: 'project.settings.environment.notice.multiple-environments.description',
|
|
||||||
defaultMessage:
|
|
||||||
"Different versions of your project have different environments selected, so you can't edit them globally at this time.",
|
|
||||||
},
|
|
||||||
reviewOptionsTitle: {
|
|
||||||
id: 'project.settings.environment.notice.review-options.title',
|
|
||||||
defaultMessage: 'Please review the options below',
|
|
||||||
},
|
|
||||||
reviewOptionsDescription: {
|
|
||||||
id: 'project.settings.environment.notice.review-options.description',
|
|
||||||
defaultMessage:
|
|
||||||
"We've just overhauled the Environments system on Modrinth and new options are now available. Please ensure the correct option is selected below and then click 'Verify' when you're done!",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-if="showEnvironmentMigration" class="card experimental-styles-within">
|
||||||
<div class="card experimental-styles-within">
|
<h2 class="m-0 mb-2 block text-lg font-extrabold text-contrast">Project environment</h2>
|
||||||
<h2 class="m-0 mb-2 block text-lg font-extrabold text-contrast">
|
<EnvironmentMigration />
|
||||||
{{ formatMessage(commonProjectSettingsMessages.environment) }}
|
</div>
|
||||||
</h2>
|
<div v-else class="grid place-content-center py-32">
|
||||||
<Admonition
|
<div class="flex flex-col items-center gap-5 text-center">
|
||||||
v-if="!supportsEnvironment"
|
<div class="flex flex-col gap-2">
|
||||||
type="critical"
|
<div class="text-xl font-semibold text-contrast">
|
||||||
:header="formatMessage(messages.wrongProjectTypeTitle)"
|
Environments are now managed per version.
|
||||||
:body="formatMessage(messages.wrongProjectTypeDescription)"
|
</div>
|
||||||
class="mb-3"
|
<div>Visit Project Settings to manage environments for each version.</div>
|
||||||
/>
|
</div>
|
||||||
<template v-else>
|
<ButtonStyled color="green">
|
||||||
<Admonition
|
<nuxt-link
|
||||||
v-if="!hasPermission"
|
:to="`/${projectV2.project_type}/${projectV2.id}/settings/versions`"
|
||||||
type="critical"
|
class="items flex"
|
||||||
:header="formatMessage(commonProjectSettingsMessages.noPermissionTitle)"
|
>
|
||||||
:body="formatMessage(commonProjectSettingsMessages.noPermissionDescription)"
|
<SettingsIcon /> Edit versions
|
||||||
class="mb-3"
|
</nuxt-link>
|
||||||
/>
|
</ButtonStyled>
|
||||||
<Admonition
|
|
||||||
v-else-if="
|
|
||||||
!projectV3.environment ||
|
|
||||||
projectV3.environment.length === 0 ||
|
|
||||||
(projectV3.environment.length === 1 && projectV3.environment[0] === 'unknown')
|
|
||||||
"
|
|
||||||
type="critical"
|
|
||||||
:header="formatMessage(messages.missingEnvTitle)"
|
|
||||||
:body="formatMessage(messages.missingEnvDescription)"
|
|
||||||
class="mb-3"
|
|
||||||
/>
|
|
||||||
<Admonition
|
|
||||||
v-else-if="projectV3.environment.length > 1"
|
|
||||||
type="info"
|
|
||||||
:header="formatMessage(messages.multipleEnvironmentsTitle)"
|
|
||||||
:body="formatMessage(messages.multipleEnvironmentsDescription)"
|
|
||||||
class="mb-3"
|
|
||||||
/>
|
|
||||||
<Admonition
|
|
||||||
v-else-if="needsToVerify"
|
|
||||||
type="warning"
|
|
||||||
:header="formatMessage(messages.reviewOptionsTitle)"
|
|
||||||
:body="formatMessage(messages.reviewOptionsDescription)"
|
|
||||||
class="mb-3"
|
|
||||||
/>
|
|
||||||
<ProjectSettingsEnvSelector
|
|
||||||
v-model="current.environment"
|
|
||||||
:disabled="!hasPermission || (projectV3?.environment?.length ?? 0) > 1"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
<UnsavedChangesPopup
|
|
||||||
v-if="supportsEnvironment && hasPermission && (projectV3?.environment?.length ?? 0) <= 1"
|
|
||||||
:original="saved"
|
|
||||||
:modified="current"
|
|
||||||
:saving="saving"
|
|
||||||
:can-reset="!needsToVerify"
|
|
||||||
:text="needsToVerify ? messages.verifyLabel : undefined"
|
|
||||||
:save-label="needsToVerify ? messages.verifyButton : undefined"
|
|
||||||
:save-icon="needsToVerify ? CheckIcon : undefined"
|
|
||||||
@reset="reset"
|
|
||||||
@save="save"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { SettingsIcon } from '@modrinth/assets'
|
||||||
|
import { ButtonStyled, EnvironmentMigration, injectProjectPageContext } from '@modrinth/ui'
|
||||||
|
import { isStaff } from '@modrinth/utils'
|
||||||
|
|
||||||
|
const { currentMember, projectV2 } = injectProjectPageContext()
|
||||||
|
|
||||||
|
const showEnvironmentMigration = computed(() => {
|
||||||
|
return isStaff(currentMember.value.user)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -560,6 +560,17 @@
|
|||||||
</template>
|
</template>
|
||||||
<span v-else>{{ $formatVersion(version.game_versions) }}</span>
|
<span v-else>{{ $formatVersion(version.game_versions) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!isEditing && environment">
|
||||||
|
<h4>Environment</h4>
|
||||||
|
<div class="flex items-center gap-1.5">
|
||||||
|
<template v-if="environment.icon">
|
||||||
|
<component :is="environment.icon" />
|
||||||
|
</template>
|
||||||
|
<span>
|
||||||
|
{{ environment.title.defaultMessage }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="!isEditing">
|
<div v-if="!isEditing">
|
||||||
<h4>Downloads</h4>
|
<h4>Downloads</h4>
|
||||||
<span>{{ version.downloads }}</span>
|
<span>{{ version.downloads }}</span>
|
||||||
@@ -635,6 +646,7 @@ import {
|
|||||||
Checkbox,
|
Checkbox,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
CopyCode,
|
CopyCode,
|
||||||
|
ENVIRONMENTS_COPY,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
MarkdownEditor,
|
MarkdownEditor,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
@@ -817,6 +829,12 @@ export default defineNuxtComponent({
|
|||||||
if (!version) {
|
if (!version) {
|
||||||
version = props.versions.find((x) => x.displayUrlEnding === route.params.version)
|
version = props.versions.find((x) => x.displayUrlEnding === route.params.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const versionV3 = await useBaseFetch(
|
||||||
|
`project/${props.project.id}/version/${route.params.version}`,
|
||||||
|
{ apiVersion: 3 },
|
||||||
|
)
|
||||||
|
if (versionV3) version.environment = versionV3.environment
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!version) {
|
if (!version) {
|
||||||
@@ -933,6 +951,9 @@ export default defineNuxtComponent({
|
|||||||
(a, b) => order.indexOf(a.dependency_type) - order.indexOf(b.dependency_type),
|
(a, b) => order.indexOf(a.dependency_type) - order.indexOf(b.dependency_type),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
environment() {
|
||||||
|
return ENVIRONMENTS_COPY[this.version.environment]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'$route.path'() {
|
'$route.path'() {
|
||||||
|
|||||||
@@ -290,7 +290,7 @@
|
|||||||
v-tooltip="'Please review environment metadata'"
|
v-tooltip="'Please review environment metadata'"
|
||||||
:to="`/${getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
:to="`/${getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
||||||
project.slug ? project.slug : project.id
|
project.slug ? project.slug : project.id
|
||||||
}/settings/environment`"
|
}?showEnvironmentMigrationWarning=true`"
|
||||||
>
|
>
|
||||||
<TriangleAlertIcon />
|
<TriangleAlertIcon />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|||||||
@@ -42,7 +42,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="versions.length > 0"
|
v-if="versions.length > 0"
|
||||||
class="flex flex-col gap-4 rounded-2xl bg-bg-raised px-6 pb-8 pt-4 supports-[grid-template-columns:subgrid]:grid supports-[grid-template-columns:subgrid]:grid-cols-[1fr_min-content] sm:px-8 supports-[grid-template-columns:subgrid]:sm:grid-cols-[min-content_auto_auto_auto_min-content] supports-[grid-template-columns:subgrid]:xl:grid-cols-[min-content_auto_auto_auto_auto_auto_min-content]"
|
class="flex flex-col gap-4 rounded-2xl bg-bg-raised px-6 pb-8 pt-4 supports-[grid-template-columns:subgrid]:grid supports-[grid-template-columns:subgrid]:grid-cols-[1fr_min-content] sm:px-8 supports-[grid-template-columns:subgrid]:sm:grid-cols-[min-content_auto_auto_auto_min-content]"
|
||||||
|
:class="[
|
||||||
|
hasMultipleEnvironments
|
||||||
|
? 'supports-[grid-template-columns:subgrid]:xl:grid-cols-[min-content_auto_auto_auto_auto_auto_auto_min-content] has-environment'
|
||||||
|
: 'supports-[grid-template-columns:subgrid]:xl:grid-cols-[min-content_auto_auto_auto_auto_auto_min-content] no-environment',
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<div class="versions-grid-row">
|
<div class="versions-grid-row">
|
||||||
<div class="w-9 max-sm:hidden"></div>
|
<div class="w-9 max-sm:hidden"></div>
|
||||||
@@ -57,6 +62,12 @@
|
|||||||
>
|
>
|
||||||
Platforms
|
Platforms
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="hasMultipleEnvironments"
|
||||||
|
class="text-sm font-bold text-contrast max-sm:hidden sm:max-xl:collapse sm:max-xl:hidden"
|
||||||
|
>
|
||||||
|
Environment
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="text-sm font-bold text-contrast max-sm:hidden sm:max-xl:collapse sm:max-xl:hidden"
|
class="text-sm font-bold text-contrast max-sm:hidden sm:max-xl:collapse sm:max-xl:hidden"
|
||||||
>
|
>
|
||||||
@@ -144,6 +155,24 @@
|
|||||||
</TagItem>
|
</TagItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="hasMultipleEnvironments"
|
||||||
|
v-tooltip="
|
||||||
|
ENVIRONMENTS_COPY[version.environment || 'unknown']?.description
|
||||||
|
? formatMessage(ENVIRONMENTS_COPY[version.environment || 'unknown'].description)
|
||||||
|
: undefined
|
||||||
|
"
|
||||||
|
class="flex items-center"
|
||||||
|
>
|
||||||
|
<TagItem class="z-[1] text-center">
|
||||||
|
<component :is="ENVIRONMENTS_COPY[version.environment || 'unknown']?.icon" />
|
||||||
|
{{
|
||||||
|
ENVIRONMENTS_COPY[version.environment || 'unknown']?.title
|
||||||
|
? formatMessage(ENVIRONMENTS_COPY[version.environment || 'unknown'].title)
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
</TagItem>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col justify-center gap-1 max-sm:flex-row max-sm:justify-start max-sm:gap-3 xl:contents"
|
class="flex flex-col justify-center gap-1 max-sm:flex-row max-sm:justify-start max-sm:gap-3 xl:contents"
|
||||||
@@ -215,12 +244,14 @@ import { commonMessages } from '../../utils/common-messages'
|
|||||||
import AutoLink from '../base/AutoLink.vue'
|
import AutoLink from '../base/AutoLink.vue'
|
||||||
import TagItem from '../base/TagItem.vue'
|
import TagItem from '../base/TagItem.vue'
|
||||||
import { Pagination, VersionChannelIndicator, VersionFilterControl } from '../index'
|
import { Pagination, VersionChannelIndicator, VersionFilterControl } from '../index'
|
||||||
|
import { ENVIRONMENTS_COPY } from './settings/environment/environments'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
|
||||||
type VersionWithDisplayUrlEnding = Version & {
|
type VersionWithDisplayUrlEnding = Version & {
|
||||||
displayUrlEnding: string
|
displayUrlEnding: string
|
||||||
|
environment?: Labrinth.Projects.v3.Environment
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -260,6 +291,11 @@ const selectedPlatforms: Ref<string[]> = computed(
|
|||||||
)
|
)
|
||||||
const selectedChannels: Ref<string[]> = computed(() => versionFilters.value?.selectedChannels ?? [])
|
const selectedChannels: Ref<string[]> = computed(() => versionFilters.value?.selectedChannels ?? [])
|
||||||
|
|
||||||
|
const hasMultipleEnvironments = computed(() => {
|
||||||
|
const environments = new Set(currentVersions.value.map((v) => v.environment).filter(Boolean))
|
||||||
|
return environments.size > 1
|
||||||
|
})
|
||||||
|
|
||||||
const filteredVersions = computed(() => {
|
const filteredVersions = computed(() => {
|
||||||
return props.versions.filter(
|
return props.versions.filter(
|
||||||
(version) =>
|
(version) =>
|
||||||
@@ -321,6 +357,14 @@ function updateQuery(newQueries: Record<string, string | string[] | undefined |
|
|||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.versions-grid-row {
|
.versions-grid-row {
|
||||||
@apply grid grid-cols-[1fr_min-content] gap-4 supports-[grid-template-columns:subgrid]:col-span-full supports-[grid-template-columns:subgrid]:!grid-cols-subgrid sm:grid-cols-[min-content_1fr_1fr_1fr_min-content] xl:grid-cols-[min-content_1fr_1fr_1fr_1fr_1fr_min-content];
|
@apply grid grid-cols-[1fr_min-content] gap-4 supports-[grid-template-columns:subgrid]:col-span-full supports-[grid-template-columns:subgrid]:!grid-cols-subgrid sm:grid-cols-[min-content_1fr_1fr_1fr_min-content];
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-environment .versions-grid-row {
|
||||||
|
@apply xl:grid-cols-[min-content_1fr_1fr_1fr_1fr_1fr_1fr_min-content];
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-environment .versions-grid-row {
|
||||||
|
@apply xl:grid-cols-[min-content_1fr_1fr_1fr_1fr_1fr_min-content];
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
:action="() => router.push(`/${project.project_type}s?g=categories:${platform}`)"
|
:action="() => router.push(`/${project.project_type}s?g=categories:${platform}`)"
|
||||||
:style="`--_color: var(--color-platform-${platform})`"
|
:style="`--_color: var(--color-platform-${platform})`"
|
||||||
>
|
>
|
||||||
<svg v-html="tags.loaders.find((x) => x.name === platform).icon"></svg>
|
<svg v-html="tags.loaders.find((x) => x.name === platform)?.icon"></svg>
|
||||||
{{ formatCategory(platform) }}
|
{{ formatCategory(platform) }}
|
||||||
</TagItem>
|
</TagItem>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
</TagItem>
|
</TagItem>
|
||||||
<TagItem
|
<TagItem
|
||||||
v-if="
|
v-if="
|
||||||
|
// @ts-ignore
|
||||||
project.project_type !== 'datapack' &&
|
project.project_type !== 'datapack' &&
|
||||||
project.client_side !== 'unsupported' &&
|
project.client_side !== 'unsupported' &&
|
||||||
project.server_side !== 'unsupported' &&
|
project.server_side !== 'unsupported' &&
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { CheckIcon } from '@modrinth/assets'
|
||||||
|
import {
|
||||||
|
Admonition,
|
||||||
|
commonProjectSettingsMessages,
|
||||||
|
defineMessages,
|
||||||
|
EnvironmentSelector,
|
||||||
|
injectModrinthClient,
|
||||||
|
injectNotificationManager,
|
||||||
|
injectProjectPageContext,
|
||||||
|
UnsavedChangesPopup,
|
||||||
|
useSavable,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const { currentMember, projectV2, projectV3, refreshProject } = injectProjectPageContext()
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
const client = injectModrinthClient()
|
||||||
|
|
||||||
|
const saving = ref(false)
|
||||||
|
|
||||||
|
const supportsEnvironment = computed(() =>
|
||||||
|
projectV3.value.project_types.some((type) => ['mod', 'modpack'].includes(type)),
|
||||||
|
)
|
||||||
|
|
||||||
|
const needsToVerify = computed(
|
||||||
|
() =>
|
||||||
|
projectV3.value.side_types_migration_review_status === 'pending' &&
|
||||||
|
(projectV3.value.environment?.length ?? 0) > 0 &&
|
||||||
|
projectV3.value.environment?.[0] !== 'unknown' &&
|
||||||
|
supportsEnvironment.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasPermission = computed(() => {
|
||||||
|
const EDIT_DETAILS = 1 << 2
|
||||||
|
return (currentMember.value?.permissions & EDIT_DETAILS) === EDIT_DETAILS
|
||||||
|
})
|
||||||
|
|
||||||
|
function getInitialEnv() {
|
||||||
|
return projectV3.value.environment?.length === 1 ? projectV3.value.environment[0] : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const { saved, current, reset, save } = useSavable(
|
||||||
|
() => ({
|
||||||
|
environment: getInitialEnv(),
|
||||||
|
side_types_migration_review_status: projectV3.value.side_types_migration_review_status,
|
||||||
|
}),
|
||||||
|
({ environment, side_types_migration_review_status }) => {
|
||||||
|
saving.value = true
|
||||||
|
side_types_migration_review_status = 'reviewed'
|
||||||
|
client.labrinth.projects_v3
|
||||||
|
.edit(projectV2.value.id, { environment, side_types_migration_review_status })
|
||||||
|
.then(() => refreshProject().then(reset))
|
||||||
|
.catch(handleError)
|
||||||
|
.finally(() => (saving.value = false))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// Set current to reviewed, which will trigger unsaved changes popup.
|
||||||
|
// It should not be possible to save without reviewing it.
|
||||||
|
const originalEnv = getInitialEnv()
|
||||||
|
if (originalEnv && originalEnv !== 'unknown') {
|
||||||
|
current.value.side_types_migration_review_status = 'reviewed'
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
verifyButton: {
|
||||||
|
id: 'project.settings.environment.verification.verify-button',
|
||||||
|
defaultMessage: 'Verify',
|
||||||
|
},
|
||||||
|
verifyLabel: {
|
||||||
|
id: 'project.settings.environment.verification.verify-text',
|
||||||
|
defaultMessage: `Verify that this project's environment is set correctly.`,
|
||||||
|
},
|
||||||
|
wrongProjectTypeTitle: {
|
||||||
|
id: 'project.settings.environment.notice.wrong-project-type.title',
|
||||||
|
defaultMessage: `This project type does not support environment metadata`,
|
||||||
|
},
|
||||||
|
wrongProjectTypeDescription: {
|
||||||
|
id: 'project.settings.environment.notice.wrong-project-type.description',
|
||||||
|
defaultMessage: `Only mod or modpack projects can have environment metadata.`,
|
||||||
|
},
|
||||||
|
missingEnvTitle: {
|
||||||
|
id: 'project.settings.environment.notice.missing-env.title',
|
||||||
|
defaultMessage: `Please select an environment for your project`,
|
||||||
|
},
|
||||||
|
missingEnvDescription: {
|
||||||
|
id: 'project.settings.environment.notice.missing-env.description',
|
||||||
|
defaultMessage: `Your project is missing environment metadata, please select the appropriate option below.`,
|
||||||
|
},
|
||||||
|
multipleEnvironmentsTitle: {
|
||||||
|
id: 'project.settings.environment.notice.multiple-environments.title',
|
||||||
|
defaultMessage: 'Your project has multiple environments',
|
||||||
|
},
|
||||||
|
multipleEnvironmentsDescription: {
|
||||||
|
id: 'project.settings.environment.notice.multiple-environments.description',
|
||||||
|
defaultMessage:
|
||||||
|
"Different versions of your project have different environments selected, so you can't edit them globally at this time.",
|
||||||
|
},
|
||||||
|
reviewOptionsTitle: {
|
||||||
|
id: 'project.settings.environment.notice.review-options.title',
|
||||||
|
defaultMessage: 'Please review the options below',
|
||||||
|
},
|
||||||
|
reviewOptionsDescription: {
|
||||||
|
id: 'project.settings.environment.notice.review-options.description',
|
||||||
|
defaultMessage:
|
||||||
|
"We've just overhauled the Environments system on Modrinth and new options are now available. Please ensure the correct option is selected below and then click 'Verify' when you're done!",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Admonition
|
||||||
|
v-if="!supportsEnvironment"
|
||||||
|
type="critical"
|
||||||
|
:header="formatMessage(messages.wrongProjectTypeTitle)"
|
||||||
|
:body="formatMessage(messages.wrongProjectTypeDescription)"
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<Admonition
|
||||||
|
v-if="!hasPermission"
|
||||||
|
type="critical"
|
||||||
|
:header="formatMessage(commonProjectSettingsMessages.noPermissionTitle)"
|
||||||
|
:body="formatMessage(commonProjectSettingsMessages.noPermissionDescription)"
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
<Admonition
|
||||||
|
v-else-if="
|
||||||
|
!projectV3.environment ||
|
||||||
|
projectV3.environment.length === 0 ||
|
||||||
|
(projectV3.environment.length === 1 && projectV3.environment[0] === 'unknown')
|
||||||
|
"
|
||||||
|
type="critical"
|
||||||
|
:header="formatMessage(messages.missingEnvTitle)"
|
||||||
|
:body="formatMessage(messages.missingEnvDescription)"
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
<Admonition
|
||||||
|
v-else-if="projectV3.environment.length > 1"
|
||||||
|
type="info"
|
||||||
|
:header="formatMessage(messages.multipleEnvironmentsTitle)"
|
||||||
|
:body="formatMessage(messages.multipleEnvironmentsDescription)"
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
<Admonition
|
||||||
|
v-else-if="needsToVerify"
|
||||||
|
type="warning"
|
||||||
|
:header="formatMessage(messages.reviewOptionsTitle)"
|
||||||
|
:body="formatMessage(messages.reviewOptionsDescription)"
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
<EnvironmentSelector
|
||||||
|
v-model="current.environment"
|
||||||
|
:disabled="!hasPermission || (projectV3?.environment?.length ?? 0) > 1"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<UnsavedChangesPopup
|
||||||
|
v-if="supportsEnvironment && hasPermission && (projectV3?.environment?.length ?? 0) <= 1"
|
||||||
|
:original="saved"
|
||||||
|
:modified="current"
|
||||||
|
:saving="saving"
|
||||||
|
:can-reset="!needsToVerify"
|
||||||
|
:text="needsToVerify ? messages.verifyLabel : undefined"
|
||||||
|
:save-label="needsToVerify ? messages.verifyButton : undefined"
|
||||||
|
:save-icon="needsToVerify ? CheckIcon : undefined"
|
||||||
|
@reset="reset"
|
||||||
|
@save="save"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -33,7 +33,10 @@ const optionLabelFormat = defineMessage({
|
|||||||
defaultMessage: '{title}: {description}',
|
defaultMessage: '{title}: {description}',
|
||||||
})
|
})
|
||||||
|
|
||||||
const OUTER_OPTIONS = {
|
const OUTER_OPTIONS: Record<
|
||||||
|
string,
|
||||||
|
EnvironmentRadioOption & { suboptions: Record<string, EnvironmentRadioOption> }
|
||||||
|
> = {
|
||||||
client: {
|
client: {
|
||||||
title: defineMessage({
|
title: defineMessage({
|
||||||
id: 'project.settings.environment.client_only.title',
|
id: 'project.settings.environment.client_only.title',
|
||||||
@@ -125,10 +128,8 @@ const OUTER_OPTIONS = {
|
|||||||
}),
|
}),
|
||||||
suboptions: {},
|
suboptions: {},
|
||||||
},
|
},
|
||||||
} as const satisfies Record<
|
} as const
|
||||||
string,
|
|
||||||
EnvironmentRadioOption & { suboptions: Record<string, EnvironmentRadioOption> }
|
|
||||||
>
|
|
||||||
type OuterOptionKey = keyof typeof OUTER_OPTIONS
|
type OuterOptionKey = keyof typeof OUTER_OPTIONS
|
||||||
type SubOptionKey = ValidKeys<(typeof OUTER_OPTIONS)[keyof typeof OUTER_OPTIONS]['suboptions']>
|
type SubOptionKey = ValidKeys<(typeof OUTER_OPTIONS)[keyof typeof OUTER_OPTIONS]['suboptions']>
|
||||||
|
|
||||||
@@ -248,7 +249,7 @@ const simulateSave = ref(false)
|
|||||||
:aria-label="
|
:aria-label="
|
||||||
formatMessage(optionLabelFormat, {
|
formatMessage(optionLabelFormat, {
|
||||||
title: formatMessage(title),
|
title: formatMessage(title),
|
||||||
description: formatMessage(description),
|
description: description ? formatMessage(description) : '',
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
@select="
|
@select="
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<NewModal ref="modal" :scrollable="true" max-content-height="82vh" :closable="true">
|
||||||
|
<template #title>
|
||||||
|
<span class="text-lg font-extrabold text-contrast">Edit project Environment</span>
|
||||||
|
</template>
|
||||||
|
<div class="mb-24 max-w-[600px]">
|
||||||
|
<EnvironmentMigration />
|
||||||
|
</div>
|
||||||
|
</NewModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, useTemplateRef } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
import { NewModal } from '../../../modal'
|
||||||
|
import EnvironmentMigration from './EnvironmentMigration.vue'
|
||||||
|
|
||||||
|
const modal = useTemplateRef<InstanceType<typeof NewModal>>('modal')
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
modal.value?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
modal.value?.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const route = useRoute()
|
||||||
|
if (route.query.showEnvironmentMigrationWarning === 'true') {
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
hide,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import type { Labrinth } from '@modrinth/api-client'
|
||||||
|
import { ClientIcon, MonitorSmartphoneIcon, ServerIcon, UserIcon } from '@modrinth/assets'
|
||||||
|
import type { Component } from 'vue'
|
||||||
|
|
||||||
|
import { defineMessage, type MessageDescriptor } from '../../../../composables/i18n'
|
||||||
|
|
||||||
|
export const ENVIRONMENTS_COPY: Record<
|
||||||
|
Labrinth.Projects.v3.Environment,
|
||||||
|
{ title: MessageDescriptor; description: MessageDescriptor; icon?: Component }
|
||||||
|
> = {
|
||||||
|
client_only: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'project.environment.client-only.title',
|
||||||
|
defaultMessage: 'Client-side only',
|
||||||
|
}),
|
||||||
|
description: defineMessage({
|
||||||
|
id: 'project.environment.client-only.description',
|
||||||
|
defaultMessage:
|
||||||
|
'All functionality is done client-side and is compatible with vanilla servers.',
|
||||||
|
}),
|
||||||
|
icon: ClientIcon,
|
||||||
|
},
|
||||||
|
server_only: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'project.environment.server-only.title',
|
||||||
|
defaultMessage: 'Server-side only',
|
||||||
|
}),
|
||||||
|
description: defineMessage({
|
||||||
|
id: 'project.environment.server-only.description',
|
||||||
|
defaultMessage:
|
||||||
|
'All functionality is done server-side and is compatible with vanilla clients.',
|
||||||
|
}),
|
||||||
|
icon: ServerIcon,
|
||||||
|
},
|
||||||
|
singleplayer_only: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'project.environment.singleplayer-only.title',
|
||||||
|
defaultMessage: 'Singleplayer only',
|
||||||
|
}),
|
||||||
|
description: defineMessage({
|
||||||
|
id: 'project.environment.singleplayer-only.description',
|
||||||
|
defaultMessage:
|
||||||
|
'Only functions in Singleplayer or when not connected to a Multiplayer server.',
|
||||||
|
}),
|
||||||
|
icon: UserIcon,
|
||||||
|
},
|
||||||
|
dedicated_server_only: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'project.environment.dedicated-server-only.title',
|
||||||
|
defaultMessage: 'Server-side only',
|
||||||
|
}),
|
||||||
|
description: defineMessage({
|
||||||
|
id: 'project.environment.dedicated-server-only.description',
|
||||||
|
defaultMessage:
|
||||||
|
'All functionality is done server-side and is compatible with vanilla clients.',
|
||||||
|
}),
|
||||||
|
icon: ServerIcon,
|
||||||
|
},
|
||||||
|
client_and_server: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'project.environment.client-and-server.title',
|
||||||
|
defaultMessage: 'Client and server',
|
||||||
|
}),
|
||||||
|
description: defineMessage({
|
||||||
|
id: 'project.environment.client-and-server.description',
|
||||||
|
defaultMessage:
|
||||||
|
'Has some functionality on both the client and server, even if only partially.',
|
||||||
|
}),
|
||||||
|
icon: MonitorSmartphoneIcon,
|
||||||
|
},
|
||||||
|
client_only_server_optional: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'project.environment.client-only-server-optional.title',
|
||||||
|
defaultMessage: 'Client and server',
|
||||||
|
}),
|
||||||
|
description: defineMessage({
|
||||||
|
id: 'project.environment.client-only-server-optional.description',
|
||||||
|
defaultMessage:
|
||||||
|
'Has some functionality on both the client and server, even if only partially.',
|
||||||
|
}),
|
||||||
|
icon: MonitorSmartphoneIcon,
|
||||||
|
},
|
||||||
|
server_only_client_optional: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'project.environment.server-only-client-optional.title',
|
||||||
|
defaultMessage: 'Client and server',
|
||||||
|
}),
|
||||||
|
description: defineMessage({
|
||||||
|
id: 'project.environment.server-only-client-optional.description',
|
||||||
|
defaultMessage:
|
||||||
|
'Has some functionality on both the client and server, even if only partially.',
|
||||||
|
}),
|
||||||
|
icon: MonitorSmartphoneIcon,
|
||||||
|
},
|
||||||
|
client_or_server: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'project.environment.client-or-server.title',
|
||||||
|
defaultMessage: 'Client and server',
|
||||||
|
}),
|
||||||
|
description: defineMessage({
|
||||||
|
id: 'project.environment.client-or-server.description',
|
||||||
|
defaultMessage:
|
||||||
|
'Has some functionality on both the client and server, even if only partially.',
|
||||||
|
}),
|
||||||
|
icon: MonitorSmartphoneIcon,
|
||||||
|
},
|
||||||
|
client_or_server_prefers_both: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'project.environment.client-or-server-prefers-both.title',
|
||||||
|
defaultMessage: 'Client and server',
|
||||||
|
}),
|
||||||
|
description: defineMessage({
|
||||||
|
id: 'project.environment.client-or-server-prefers-both.description',
|
||||||
|
defaultMessage:
|
||||||
|
'Has some functionality on both the client and server, even if only partially.',
|
||||||
|
}),
|
||||||
|
icon: MonitorSmartphoneIcon,
|
||||||
|
},
|
||||||
|
unknown: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'project.environment.unknown.title',
|
||||||
|
defaultMessage: 'Unknown environment',
|
||||||
|
}),
|
||||||
|
description: defineMessage({
|
||||||
|
id: 'project.environment.unknown.description',
|
||||||
|
defaultMessage: 'The environment for this version could not be determined.',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
// Environment
|
export { default as EnvironmentMigration } from './environment/EnvironmentMigration.vue'
|
||||||
export { default as ProjectSettingsEnvSelector } from './environment/ProjectSettingsEnvSelector.vue'
|
export { ENVIRONMENTS_COPY } from './environment/environments'
|
||||||
|
export { default as EnvironmentSelector } from './environment/EnvironmentSelector.vue'
|
||||||
|
export { default as ProjectEnvironmentModal } from './environment/ProjectEnvironmentModal.vue'
|
||||||
|
|||||||
@@ -677,6 +677,66 @@
|
|||||||
"project.about.links.wiki": {
|
"project.about.links.wiki": {
|
||||||
"defaultMessage": "Visit wiki"
|
"defaultMessage": "Visit wiki"
|
||||||
},
|
},
|
||||||
|
"project.environment.client-and-server.description": {
|
||||||
|
"defaultMessage": "Has some functionality on both the client and server, even if only partially."
|
||||||
|
},
|
||||||
|
"project.environment.client-and-server.title": {
|
||||||
|
"defaultMessage": "Client and server"
|
||||||
|
},
|
||||||
|
"project.environment.client-only-server-optional.description": {
|
||||||
|
"defaultMessage": "Has some functionality on both the client and server, even if only partially."
|
||||||
|
},
|
||||||
|
"project.environment.client-only-server-optional.title": {
|
||||||
|
"defaultMessage": "Client and server"
|
||||||
|
},
|
||||||
|
"project.environment.client-only.description": {
|
||||||
|
"defaultMessage": "All functionality is done client-side and is compatible with vanilla servers."
|
||||||
|
},
|
||||||
|
"project.environment.client-only.title": {
|
||||||
|
"defaultMessage": "Client-side only"
|
||||||
|
},
|
||||||
|
"project.environment.client-or-server-prefers-both.description": {
|
||||||
|
"defaultMessage": "Has some functionality on both the client and server, even if only partially."
|
||||||
|
},
|
||||||
|
"project.environment.client-or-server-prefers-both.title": {
|
||||||
|
"defaultMessage": "Client and server"
|
||||||
|
},
|
||||||
|
"project.environment.client-or-server.description": {
|
||||||
|
"defaultMessage": "Has some functionality on both the client and server, even if only partially."
|
||||||
|
},
|
||||||
|
"project.environment.client-or-server.title": {
|
||||||
|
"defaultMessage": "Client and server"
|
||||||
|
},
|
||||||
|
"project.environment.dedicated-server-only.description": {
|
||||||
|
"defaultMessage": "All functionality is done server-side and is compatible with vanilla clients."
|
||||||
|
},
|
||||||
|
"project.environment.dedicated-server-only.title": {
|
||||||
|
"defaultMessage": "Server-side only"
|
||||||
|
},
|
||||||
|
"project.environment.server-only-client-optional.description": {
|
||||||
|
"defaultMessage": "Has some functionality on both the client and server, even if only partially."
|
||||||
|
},
|
||||||
|
"project.environment.server-only-client-optional.title": {
|
||||||
|
"defaultMessage": "Client and server"
|
||||||
|
},
|
||||||
|
"project.environment.server-only.description": {
|
||||||
|
"defaultMessage": "All functionality is done server-side and is compatible with vanilla clients."
|
||||||
|
},
|
||||||
|
"project.environment.server-only.title": {
|
||||||
|
"defaultMessage": "Server-side only"
|
||||||
|
},
|
||||||
|
"project.environment.singleplayer-only.description": {
|
||||||
|
"defaultMessage": "Only functions in Singleplayer or when not connected to a Multiplayer server."
|
||||||
|
},
|
||||||
|
"project.environment.singleplayer-only.title": {
|
||||||
|
"defaultMessage": "Singleplayer only"
|
||||||
|
},
|
||||||
|
"project.environment.unknown.description": {
|
||||||
|
"defaultMessage": "The environment for this version could not be determined."
|
||||||
|
},
|
||||||
|
"project.environment.unknown.title": {
|
||||||
|
"defaultMessage": "Unknown environment"
|
||||||
|
},
|
||||||
"project.settings.analytics.title": {
|
"project.settings.analytics.title": {
|
||||||
"defaultMessage": "Analytics"
|
"defaultMessage": "Analytics"
|
||||||
},
|
},
|
||||||
@@ -710,6 +770,30 @@
|
|||||||
"project.settings.environment.client_only.title": {
|
"project.settings.environment.client_only.title": {
|
||||||
"defaultMessage": "Client-side only"
|
"defaultMessage": "Client-side only"
|
||||||
},
|
},
|
||||||
|
"project.settings.environment.notice.missing-env.description": {
|
||||||
|
"defaultMessage": "Your project is missing environment metadata, please select the appropriate option below."
|
||||||
|
},
|
||||||
|
"project.settings.environment.notice.missing-env.title": {
|
||||||
|
"defaultMessage": "Please select an environment for your project"
|
||||||
|
},
|
||||||
|
"project.settings.environment.notice.multiple-environments.description": {
|
||||||
|
"defaultMessage": "Different versions of your project have different environments selected, so you can't edit them globally at this time."
|
||||||
|
},
|
||||||
|
"project.settings.environment.notice.multiple-environments.title": {
|
||||||
|
"defaultMessage": "Your project has multiple environments"
|
||||||
|
},
|
||||||
|
"project.settings.environment.notice.review-options.description": {
|
||||||
|
"defaultMessage": "We've just overhauled the Environments system on Modrinth and new options are now available. Please ensure the correct option is selected below and then click 'Verify' when you're done!"
|
||||||
|
},
|
||||||
|
"project.settings.environment.notice.review-options.title": {
|
||||||
|
"defaultMessage": "Please review the options below"
|
||||||
|
},
|
||||||
|
"project.settings.environment.notice.wrong-project-type.description": {
|
||||||
|
"defaultMessage": "Only mod or modpack projects can have environment metadata."
|
||||||
|
},
|
||||||
|
"project.settings.environment.notice.wrong-project-type.title": {
|
||||||
|
"defaultMessage": "This project type does not support environment metadata"
|
||||||
|
},
|
||||||
"project.settings.environment.server_only.dedicated_only.title": {
|
"project.settings.environment.server_only.dedicated_only.title": {
|
||||||
"defaultMessage": "Dedicated server only"
|
"defaultMessage": "Dedicated server only"
|
||||||
},
|
},
|
||||||
@@ -737,6 +821,12 @@
|
|||||||
"project.settings.environment.title": {
|
"project.settings.environment.title": {
|
||||||
"defaultMessage": "Environment"
|
"defaultMessage": "Environment"
|
||||||
},
|
},
|
||||||
|
"project.settings.environment.verification.verify-button": {
|
||||||
|
"defaultMessage": "Verify"
|
||||||
|
},
|
||||||
|
"project.settings.environment.verification.verify-text": {
|
||||||
|
"defaultMessage": "Verify that this project's environment is set correctly."
|
||||||
|
},
|
||||||
"project.settings.gallery.title": {
|
"project.settings.gallery.title": {
|
||||||
"defaultMessage": "Gallery"
|
"defaultMessage": "Gallery"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user