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:
@@ -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}',
|
||||
})
|
||||
|
||||
const OUTER_OPTIONS = {
|
||||
const OUTER_OPTIONS: Record<
|
||||
string,
|
||||
EnvironmentRadioOption & { suboptions: Record<string, EnvironmentRadioOption> }
|
||||
> = {
|
||||
client: {
|
||||
title: defineMessage({
|
||||
id: 'project.settings.environment.client_only.title',
|
||||
@@ -125,10 +128,8 @@ const OUTER_OPTIONS = {
|
||||
}),
|
||||
suboptions: {},
|
||||
},
|
||||
} as const satisfies Record<
|
||||
string,
|
||||
EnvironmentRadioOption & { suboptions: Record<string, EnvironmentRadioOption> }
|
||||
>
|
||||
} as const
|
||||
|
||||
type OuterOptionKey = keyof typeof OUTER_OPTIONS
|
||||
type SubOptionKey = ValidKeys<(typeof OUTER_OPTIONS)[keyof typeof OUTER_OPTIONS]['suboptions']>
|
||||
|
||||
@@ -248,7 +249,7 @@ const simulateSave = ref(false)
|
||||
:aria-label="
|
||||
formatMessage(optionLabelFormat, {
|
||||
title: formatMessage(title),
|
||||
description: formatMessage(description),
|
||||
description: description ? formatMessage(description) : '',
|
||||
})
|
||||
"
|
||||
@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 ProjectSettingsEnvSelector } from './environment/ProjectSettingsEnvSelector.vue'
|
||||
export { default as EnvironmentMigration } from './environment/EnvironmentMigration.vue'
|
||||
export { ENVIRONMENTS_COPY } from './environment/environments'
|
||||
export { default as EnvironmentSelector } from './environment/EnvironmentSelector.vue'
|
||||
export { default as ProjectEnvironmentModal } from './environment/ProjectEnvironmentModal.vue'
|
||||
|
||||
Reference in New Issue
Block a user