You've already forked AstralRinth
forked from didirus/AstralRinth
Envs v3 frontend (#4267)
* New envs frontend * lint fix * Add blog post, user-facing changes, dashboard warning, project page member warning, and migration reviewing. maybe some other misc stuff * lint * lint * ignore .data in .prettierignore * i18n as fuck * fix proj page * Improve news markdown rendering * improve phrasing of initial paragraph * Fix environments not reloading after save * index.ts instead of underscored name * shrink-0 back on these icons
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
**/.nuxt
|
||||
**/dist
|
||||
**/.output
|
||||
**/.data
|
||||
src/generated/**
|
||||
src/locales/**
|
||||
src/public/news/feed
|
||||
|
||||
@@ -7,10 +7,30 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { NotificationPanel, provideNotificationManager } from '@modrinth/ui'
|
||||
import { provideApi } from '@modrinth/ui/src/providers/api.ts'
|
||||
import { RestModrinthApi } from '@modrinth/utils'
|
||||
|
||||
import ModrinthLoadingIndicator from '~/components/ui/modrinth-loading-indicator.ts'
|
||||
|
||||
import { FrontendNotificationManager } from './providers/frontend-notifications.ts'
|
||||
|
||||
provideNotificationManager(new FrontendNotificationManager())
|
||||
|
||||
provideApi(
|
||||
new RestModrinthApi((url: string, options?: object) => {
|
||||
const match = url.match(/^\/v(\d+)\/(.+)$/)
|
||||
|
||||
if (match) {
|
||||
const apiVersion = Number(match[1])
|
||||
const path = match[2]
|
||||
|
||||
return useBaseFetch(path, {
|
||||
...options,
|
||||
apiVersion,
|
||||
}) as Promise<Response>
|
||||
} else {
|
||||
throw new Error('Invalid format')
|
||||
}
|
||||
}),
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
padding: 0 1.5rem;
|
||||
|
||||
grid-template:
|
||||
'header'
|
||||
'sidebar'
|
||||
'content'
|
||||
'info'
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
<template>
|
||||
<NuxtLink v-if="link !== null" class="nav-link button-base" :to="link">
|
||||
<div class="nav-content">
|
||||
<slot />
|
||||
<span>{{ label }}</span>
|
||||
<span v-if="beta" class="beta-badge">BETA</span>
|
||||
<span v-if="chevron" class="chevron"><ChevronRightIcon /></span>
|
||||
</div>
|
||||
<NuxtLink v-if="link !== null" :to="link" class="nav-item">
|
||||
<slot />
|
||||
<span>{{ label }}</span>
|
||||
<span v-if="badge" class="rounded-full bg-brand-highlight px-2 text-sm font-bold text-brand">{{
|
||||
badge
|
||||
}}</span>
|
||||
<span v-if="chevron" class="ml-auto"><ChevronRightIcon /></span>
|
||||
</NuxtLink>
|
||||
<button
|
||||
v-else-if="action"
|
||||
class="nav-link button-base"
|
||||
:class="{ 'danger-button': danger }"
|
||||
@click="action"
|
||||
>
|
||||
<span class="nav-content">
|
||||
<slot />
|
||||
<span>{{ label }}</span>
|
||||
<span v-if="beta" class="beta-badge">BETA</span>
|
||||
</span>
|
||||
<button v-else-if="action" class="nav-item" :class="{ 'danger-button': danger }" @click="action">
|
||||
<slot />
|
||||
<span>{{ label }}</span>
|
||||
<span v-if="badge" class="rounded-full bg-brand-highlight px-2 text-sm font-bold text-brand">{{
|
||||
badge
|
||||
}}</span>
|
||||
</button>
|
||||
<span v-else>i forgor 💀</span>
|
||||
</template>
|
||||
@@ -42,9 +37,9 @@ export default {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
beta: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
badge: {
|
||||
default: null,
|
||||
type: String,
|
||||
},
|
||||
chevron: {
|
||||
default: false,
|
||||
@@ -59,58 +54,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav-link {
|
||||
font-weight: var(--font-weight-bold);
|
||||
background-color: transparent;
|
||||
color: var(--text-color);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.25rem;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
.nav-item {
|
||||
@apply flex w-full cursor-pointer items-center gap-2 text-nowrap rounded-xl border-none bg-transparent px-4 py-2 text-left font-semibold text-button-text transition-all hover:bg-button-bg hover:text-contrast active:scale-[0.97];
|
||||
}
|
||||
|
||||
:where(.nav-link) {
|
||||
--text-color: var(--color-text);
|
||||
--background-color: var(--color-raised-bg);
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
box-sizing: border-box;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--size-rounded-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
flex-grow: 1;
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
.nav-content {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.router-link-exact-active {
|
||||
outline: 2px solid transparent;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
.nav-content {
|
||||
color: var(--color-button-text-active);
|
||||
background-color: var(--color-button-bg);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.beta-badge {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
margin-left: auto;
|
||||
}
|
||||
.router-link-exact-active.nav-item {
|
||||
@apply bg-button-bgSelected text-button-textSelected;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
</h2>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="flex items-center gap-1">
|
||||
<AsteriskIcon class="size-4 text-red" />
|
||||
<AsteriskIcon class="size-4 shrink-0 text-red" />
|
||||
<span class="text-secondary">{{ getFormattedMessage(messages.required) }}</span>
|
||||
</div>
|
||||
|
|
||||
<div class="flex items-center gap-1">
|
||||
<TriangleAlertIcon class="size-4 text-orange" />
|
||||
<TriangleAlertIcon class="size-4 shrink-0 text-orange" />
|
||||
<span class="text-secondary">{{ getFormattedMessage(messages.warning) }}</span>
|
||||
</div>
|
||||
|
|
||||
<div class="flex items-center gap-1">
|
||||
<LightBulbIcon class="size-4 text-purple" />
|
||||
<LightBulbIcon class="size-4 shrink-0 text-purple" />
|
||||
<span class="text-secondary">{{ getFormattedMessage(messages.suggestion) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,6 +35,8 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
||||
showProjectPageDownloadModalServersPromo: false,
|
||||
showProjectPageCreateServersTooltip: true,
|
||||
showProjectPageQuickServerButton: false,
|
||||
newProjectGeneralSettings: false,
|
||||
newProjectEnvironmentSettings: true,
|
||||
// advancedRendering: true,
|
||||
// externalLinksNewTab: true,
|
||||
// notUsingBlockers: false,
|
||||
|
||||
@@ -77,6 +77,7 @@ export const initUserProjects = async () => {
|
||||
if (auth.user && auth.user.id) {
|
||||
try {
|
||||
user.projects = await useBaseFetch(`user/${auth.user.id}/projects`)
|
||||
user.projectsV3 = await useBaseFetch(`user/${auth.user.id}/projects`, { apiVersion: 3 })
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
@@ -683,21 +683,231 @@
|
||||
"project.about.details.updated": {
|
||||
"message": "Updated {date}"
|
||||
},
|
||||
"project.actions.create-server": {
|
||||
"message": "Create a server"
|
||||
},
|
||||
"project.actions.create-server-tooltip": {
|
||||
"message": "Create a server"
|
||||
},
|
||||
"project.actions.dont-show-again": {
|
||||
"message": "Don't show again"
|
||||
},
|
||||
"project.actions.review-project": {
|
||||
"message": "Review project"
|
||||
},
|
||||
"project.actions.servers-promo.description": {
|
||||
"message": "Modrinth Servers is the easiest way to play with your friends without hassle!"
|
||||
},
|
||||
"project.actions.servers-promo.monthly": {
|
||||
"message": " / month"
|
||||
},
|
||||
"project.actions.servers-promo.pricing": {
|
||||
"message": "Starting at $5{monthly}"
|
||||
},
|
||||
"project.actions.servers-promo.title": {
|
||||
"message": "Create a server"
|
||||
},
|
||||
"project.collections.create-new": {
|
||||
"message": "Create new collection"
|
||||
},
|
||||
"project.collections.none-found": {
|
||||
"message": "No collections found."
|
||||
},
|
||||
"project.description.title": {
|
||||
"message": "Description"
|
||||
},
|
||||
"project.details.licensed": {
|
||||
"message": "Licensed"
|
||||
},
|
||||
"project.download.game-version": {
|
||||
"message": "Game version: {version}"
|
||||
},
|
||||
"project.download.game-version-error": {
|
||||
"message": "Error: no game versions found"
|
||||
},
|
||||
"project.download.game-version-tooltip": {
|
||||
"message": "{title} is only available for {version}"
|
||||
},
|
||||
"project.download.game-version-unsupported-tooltip": {
|
||||
"message": "{title} does not support {gameVersion} for {platform}"
|
||||
},
|
||||
"project.download.install-with-app": {
|
||||
"message": "Install with Modrinth App"
|
||||
},
|
||||
"project.download.no-app": {
|
||||
"message": "Don't have Modrinth App?"
|
||||
},
|
||||
"project.download.no-versions-available": {
|
||||
"message": "No versions available for {gameVersion} and {platform}."
|
||||
},
|
||||
"project.download.platform": {
|
||||
"message": "Platform: {platform}"
|
||||
},
|
||||
"project.download.platform-error": {
|
||||
"message": "Error: no platforms found"
|
||||
},
|
||||
"project.download.platform-tooltip": {
|
||||
"message": "{title} is only available for {platform}"
|
||||
},
|
||||
"project.download.platform-unsupported-tooltip": {
|
||||
"message": "{title} does not support {platform} for {gameVersion}"
|
||||
},
|
||||
"project.download.search-game-versions": {
|
||||
"message": "Search game versions..."
|
||||
},
|
||||
"project.download.search-game-versions-label": {
|
||||
"message": "Search game versions..."
|
||||
},
|
||||
"project.download.select-game-version": {
|
||||
"message": "Select game version"
|
||||
},
|
||||
"project.download.select-platform": {
|
||||
"message": "Select platform"
|
||||
},
|
||||
"project.download.show-all-versions": {
|
||||
"message": "Show all versions"
|
||||
},
|
||||
"project.download.title": {
|
||||
"message": "Download {title}"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"project.environment.migration.review-button": {
|
||||
"message": "Review environment settings"
|
||||
},
|
||||
"project.environment.migration.title": {
|
||||
"message": "Please review environment metadata"
|
||||
},
|
||||
"project.error.loading": {
|
||||
"message": "Error loading project data{message}"
|
||||
},
|
||||
"project.error.page-not-found": {
|
||||
"message": "The page could not be found"
|
||||
},
|
||||
"project.error.project-not-found": {
|
||||
"message": "Project not found"
|
||||
},
|
||||
"project.gallery.title": {
|
||||
"message": "Gallery"
|
||||
},
|
||||
"project.license.error": {
|
||||
"message": "License text could not be retrieved."
|
||||
},
|
||||
"project.license.loading": {
|
||||
"message": "Loading license text..."
|
||||
},
|
||||
"project.license.title": {
|
||||
"message": "License"
|
||||
},
|
||||
"project.moderation.title": {
|
||||
"message": "Moderation"
|
||||
},
|
||||
"project.navigation.changelog": {
|
||||
"message": "Changelog"
|
||||
},
|
||||
"project.notification.icon-updated.message": {
|
||||
"message": "Your project's icon has been updated."
|
||||
},
|
||||
"project.notification.icon-updated.title": {
|
||||
"message": "Project icon updated"
|
||||
},
|
||||
"project.notification.updated.message": {
|
||||
"message": "Your project has been updated."
|
||||
},
|
||||
"project.notification.updated.title": {
|
||||
"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": {
|
||||
"message": "Avoid prefixes, suffixes, parentheticals, or added descriptions—just the project's actual name."
|
||||
},
|
||||
"project.settings.general.name.placeholder.1": {
|
||||
"message": "e.g. Nether Overhaul 2"
|
||||
},
|
||||
"project.settings.general.name.placeholder.2": {
|
||||
"message": "e.g. Construction Equipment"
|
||||
},
|
||||
"project.settings.general.name.placeholder.3": {
|
||||
"message": "e.g. Better than Caving"
|
||||
},
|
||||
"project.settings.general.name.placeholder.4": {
|
||||
"message": "e.g. Enhanced Portals"
|
||||
},
|
||||
"project.settings.general.name.placeholder.5": {
|
||||
"message": "e.g. Dangerous Mobs"
|
||||
},
|
||||
"project.settings.general.name.title": {
|
||||
"message": "Name"
|
||||
},
|
||||
"project.settings.general.tagline.description": {
|
||||
"message": "Summarize your project in no more than one sentence."
|
||||
},
|
||||
"project.settings.general.tagline.placeholder.1": {
|
||||
"message": "e.g. Overhauls game progression to revolve around the Nether."
|
||||
},
|
||||
"project.settings.general.tagline.placeholder.2": {
|
||||
"message": "e.g. Adds wearable construction gear."
|
||||
},
|
||||
"project.settings.general.tagline.placeholder.3": {
|
||||
"message": "e.g. Adds realistic mineshaft-building mechanics."
|
||||
},
|
||||
"project.settings.general.tagline.placeholder.4": {
|
||||
"message": "e.g. Improves how Nether portals link to each other."
|
||||
},
|
||||
"project.settings.general.tagline.placeholder.5": {
|
||||
"message": "e.g. Adds powerful boss versions of the normal mobs to encounter in the night."
|
||||
},
|
||||
"project.settings.general.tagline.title": {
|
||||
"message": "Tagline"
|
||||
},
|
||||
"project.settings.general.url.title": {
|
||||
"message": "URL"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Settings"
|
||||
},
|
||||
"project.settings.visit-dashboard": {
|
||||
"message": "Visit projects dashboard"
|
||||
},
|
||||
"project.stats.downloads-label": {
|
||||
"message": "download{count, plural, one {} other {s}}"
|
||||
},
|
||||
"project.stats.followers-label": {
|
||||
"message": "follower{count, plural, one {} other {s}}"
|
||||
},
|
||||
"project.status.archived.message": {
|
||||
"message": "{title} has been archived. {title} will not receive any further updates unless the author decides to unarchive the project."
|
||||
},
|
||||
"project.version.all-versions": {
|
||||
"message": "All versions"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
162
apps/frontend/src/pages/[type]/[id]/settings.vue
Normal file
162
apps/frontend/src/pages/[type]/[id]/settings.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
AlignLeftIcon,
|
||||
BookTextIcon,
|
||||
ChartIcon,
|
||||
GlobeIcon,
|
||||
ImageIcon,
|
||||
InfoIcon,
|
||||
LinkIcon,
|
||||
TagsIcon,
|
||||
UsersIcon,
|
||||
VersionIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { commonMessages, commonProjectSettingsMessages } from '@modrinth/ui'
|
||||
import type { Project, ProjectV3Partial } from '@modrinth/utils'
|
||||
import { useVIntl } from '@vintl/vintl'
|
||||
|
||||
import NavStack from '~/components/ui/NavStack.vue'
|
||||
import NavStackItem from '~/components/ui/NavStackItem.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
defineProps<{
|
||||
currentMember: any
|
||||
patchProject: any
|
||||
patchIcon: any
|
||||
resetProject: any
|
||||
resetOrganization: any
|
||||
resetMembers: any
|
||||
}>()
|
||||
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const project = defineModel<Project>('project', { required: true })
|
||||
const projectV3 = defineModel<ProjectV3Partial>('projectV3', { required: true })
|
||||
const versions = defineModel<any>('versions')
|
||||
const featuredVersions = defineModel<any>('featuredVersions')
|
||||
const members = defineModel<any>('members')
|
||||
const allMembers = defineModel<any>('allMembers')
|
||||
const dependencies = defineModel<any>('dependencies')
|
||||
const organization = defineModel<any>('organization')
|
||||
</script>
|
||||
<template>
|
||||
<div class="experimental-styles-within grid grid-cols-[1fr_3fr] gap-4">
|
||||
<div>
|
||||
<aside class="universal-card">
|
||||
<NavStack>
|
||||
<NavStackItem
|
||||
:link="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.general)"
|
||||
>
|
||||
<InfoIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
<NavStackItem
|
||||
v-if="flags.newProjectGeneralSettings"
|
||||
:link="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings/general`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.general)"
|
||||
:badge="formatMessage(commonMessages.newBadge)"
|
||||
>
|
||||
<InfoIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
<NavStackItem
|
||||
v-if="
|
||||
flags.newProjectEnvironmentSettings &&
|
||||
projectV3.project_types.some((type) => ['mod', 'modpack'].includes(type))
|
||||
"
|
||||
:link="`/${project.project_type}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/settings/environment`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.environment)"
|
||||
:badge="formatMessage(commonMessages.newBadge)"
|
||||
>
|
||||
<GlobeIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
<NavStackItem
|
||||
:link="`/${project.project_type}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/settings/tags`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.tags)"
|
||||
>
|
||||
<TagsIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
<NavStackItem
|
||||
:link="`/${project.project_type}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/settings/description`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.description)"
|
||||
>
|
||||
<AlignLeftIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
<NavStackItem
|
||||
:link="`/${project.project_type}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/settings/license`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.license)"
|
||||
>
|
||||
<BookTextIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
<NavStackItem
|
||||
:link="`/${project.project_type}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/settings/links`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.links)"
|
||||
>
|
||||
<LinkIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
<NavStackItem
|
||||
:link="`/${project.project_type}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/settings/members`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.members)"
|
||||
>
|
||||
<UsersIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
<h3>{{ formatMessage(commonProjectSettingsMessages.view) }}</h3>
|
||||
<NavStackItem
|
||||
:link="`/${project.project_type}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/settings/analytics`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.analytics)"
|
||||
chevron
|
||||
>
|
||||
<ChartIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
<h3>{{ formatMessage(commonProjectSettingsMessages.upload) }}</h3>
|
||||
<NavStackItem
|
||||
:link="`/${project.project_type}/${project.slug ? project.slug : project.id}/gallery`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.gallery)"
|
||||
chevron
|
||||
>
|
||||
<ImageIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
<NavStackItem
|
||||
:link="`/${project.project_type}/${project.slug ? project.slug : project.id}/versions`"
|
||||
:label="formatMessage(commonProjectSettingsMessages.versions)"
|
||||
chevron
|
||||
>
|
||||
<VersionIcon aria-hidden="true" />
|
||||
</NavStackItem>
|
||||
</NavStack>
|
||||
</aside>
|
||||
</div>
|
||||
<div>
|
||||
<NuxtPage
|
||||
v-model:project="project"
|
||||
v-model:project-v3="projectV3"
|
||||
v-model:versions="versions"
|
||||
v-model:featured-versions="featuredVersions"
|
||||
v-model:members="members"
|
||||
v-model:all-members="allMembers"
|
||||
v-model:dependencies="dependencies"
|
||||
v-model:organization="organization"
|
||||
:current-member="currentMember"
|
||||
:patch-project="patchProject"
|
||||
:patch-icon="patchIcon"
|
||||
:reset-project="resetProject"
|
||||
:reset-organization="resetOrganization"
|
||||
:reset-members="resetMembers"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
165
apps/frontend/src/pages/[type]/[id]/settings/environment.vue
Normal file
165
apps/frontend/src/pages/[type]/[id]/settings/environment.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Admonition,
|
||||
commonProjectSettingsMessages,
|
||||
injectNotificationManager,
|
||||
injectProjectPageContext,
|
||||
ProjectSettingsEnvSelector,
|
||||
UnsavedChangesPopup,
|
||||
useSavable,
|
||||
} from '@modrinth/ui'
|
||||
import { injectApi } from '@modrinth/ui/src/providers/api.ts'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const { projectV2, projectV3, refreshProject } = injectProjectPageContext()
|
||||
const { handleError } = injectNotificationManager()
|
||||
const api = injectApi()
|
||||
|
||||
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 &&
|
||||
projectV3.value.environment?.[0] !== 'unknown' &&
|
||||
supportsEnvironment.value,
|
||||
)
|
||||
|
||||
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'
|
||||
api.projects
|
||||
.editV3(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>
|
||||
<UnsavedChangesPopup
|
||||
v-if="supportsEnvironment"
|
||||
: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 class="card experimental-styles-within">
|
||||
<h2 class="m-0 mb-2 block text-lg font-extrabold text-contrast">
|
||||
{{ formatMessage(commonProjectSettingsMessages.environment) }}
|
||||
</h2>
|
||||
<Admonition
|
||||
v-if="!supportsEnvironment"
|
||||
type="critical"
|
||||
:header="formatMessage(messages.wrongProjectTypeTitle)"
|
||||
class="mb-3"
|
||||
>
|
||||
{{ formatMessage(messages.wrongProjectTypeDescription) }}
|
||||
</Admonition>
|
||||
<template v-else>
|
||||
<Admonition
|
||||
v-if="
|
||||
!projectV3.environment ||
|
||||
projectV3.environment.length === 0 ||
|
||||
projectV3.environment[0] === 'unknown'
|
||||
"
|
||||
type="critical"
|
||||
:header="formatMessage(messages.missingEnvTitle)"
|
||||
class="mb-3"
|
||||
>
|
||||
{{ formatMessage(messages.missingEnvDescription) }}
|
||||
</Admonition>
|
||||
<Admonition
|
||||
v-else-if="projectV3.environment.length > 1"
|
||||
type="info"
|
||||
:header="formatMessage(messages.multipleEnvironmentsTitle)"
|
||||
class="mb-3"
|
||||
>
|
||||
{{ formatMessage(messages.multipleEnvironmentsDescription) }}
|
||||
</Admonition>
|
||||
<Admonition
|
||||
v-else-if="needsToVerify"
|
||||
type="warning"
|
||||
:header="formatMessage(messages.reviewOptionsTitle)"
|
||||
class="mb-3"
|
||||
>
|
||||
{{ formatMessage(messages.reviewOptionsDescription) }}
|
||||
</Admonition>
|
||||
<ProjectSettingsEnvSelector v-model="current.environment" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
192
apps/frontend/src/pages/[type]/[id]/settings/general.vue
Normal file
192
apps/frontend/src/pages/[type]/[id]/settings/general.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
IconSelect,
|
||||
injectNotificationManager,
|
||||
injectProjectPageContext,
|
||||
SettingsLabel,
|
||||
UnsavedChangesPopup,
|
||||
useSavable,
|
||||
} from '@modrinth/ui'
|
||||
import { injectApi } from '@modrinth/ui/src/providers/api.ts'
|
||||
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const { projectV2: project, refreshProject } = injectProjectPageContext()
|
||||
const { handleError } = injectNotificationManager()
|
||||
const api = injectApi()
|
||||
|
||||
const saving = ref(false)
|
||||
|
||||
const { saved, current, reset, save } = useSavable(
|
||||
() => ({
|
||||
title: project.value.title,
|
||||
tagline: project.value.description,
|
||||
url: project.value.slug,
|
||||
icon: project.value.icon_url,
|
||||
}),
|
||||
({ title, tagline, url }) => {
|
||||
const data: Record<string, string> = {
|
||||
...(title !== undefined && { title }),
|
||||
...(tagline !== undefined && { description: tagline }),
|
||||
...(url !== undefined && { slug: url }),
|
||||
}
|
||||
|
||||
if (data) {
|
||||
saving.value = true
|
||||
api.projects
|
||||
.edit(project.value.id, { title, description: tagline, slug: url })
|
||||
.then(() => refreshProject().then(reset))
|
||||
.catch(handleError)
|
||||
.finally(() => (saving.value = false))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const messages = defineMessages({
|
||||
nameTitle: {
|
||||
id: 'project.settings.general.name.title',
|
||||
defaultMessage: 'Name',
|
||||
},
|
||||
nameDescription: {
|
||||
id: 'project.settings.general.name.description',
|
||||
defaultMessage:
|
||||
"Avoid prefixes, suffixes, parentheticals, or added descriptions—just the project's actual name.",
|
||||
},
|
||||
taglineTitle: {
|
||||
id: 'project.settings.general.tagline.title',
|
||||
defaultMessage: 'Tagline',
|
||||
},
|
||||
taglineDescription: {
|
||||
id: 'project.settings.general.tagline.description',
|
||||
defaultMessage: 'Summarize your project in no more than one sentence.',
|
||||
},
|
||||
urlTitle: {
|
||||
id: 'project.settings.general.url.title',
|
||||
defaultMessage: 'URL',
|
||||
},
|
||||
})
|
||||
|
||||
const placeholders: { name: MessageDescriptor; tagline: MessageDescriptor }[] = [
|
||||
defineMessages({
|
||||
name: {
|
||||
id: 'project.settings.general.name.placeholder.1',
|
||||
defaultMessage: 'e.g. Nether Overhaul 2',
|
||||
},
|
||||
tagline: {
|
||||
id: 'project.settings.general.tagline.placeholder.1',
|
||||
defaultMessage: 'e.g. Overhauls game progression to revolve around the Nether.',
|
||||
},
|
||||
}),
|
||||
defineMessages({
|
||||
name: {
|
||||
id: 'project.settings.general.name.placeholder.2',
|
||||
defaultMessage: 'e.g. Construction Equipment',
|
||||
},
|
||||
tagline: {
|
||||
id: 'project.settings.general.tagline.placeholder.2',
|
||||
defaultMessage: 'e.g. Adds wearable construction gear.',
|
||||
},
|
||||
}),
|
||||
defineMessages({
|
||||
name: {
|
||||
id: 'project.settings.general.name.placeholder.3',
|
||||
defaultMessage: 'e.g. Better than Caving',
|
||||
},
|
||||
tagline: {
|
||||
id: 'project.settings.general.tagline.placeholder.3',
|
||||
defaultMessage: 'e.g. Adds realistic mineshaft-building mechanics.',
|
||||
},
|
||||
}),
|
||||
defineMessages({
|
||||
name: {
|
||||
id: 'project.settings.general.name.placeholder.4',
|
||||
defaultMessage: 'e.g. Enhanced Portals',
|
||||
},
|
||||
tagline: {
|
||||
id: 'project.settings.general.tagline.placeholder.4',
|
||||
defaultMessage: 'e.g. Improves how Nether portals link to each other.',
|
||||
},
|
||||
}),
|
||||
defineMessages({
|
||||
name: {
|
||||
id: 'project.settings.general.name.placeholder.5',
|
||||
defaultMessage: 'e.g. Dangerous Mobs',
|
||||
},
|
||||
tagline: {
|
||||
id: 'project.settings.general.tagline.placeholder.5',
|
||||
defaultMessage:
|
||||
'e.g. Adds powerful boss versions of the normal mobs to encounter in the night.',
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
const placeholderIndex = useState<number>('project-settings-random-placeholder', () =>
|
||||
Math.floor(Math.random() * (placeholders.length + 1)),
|
||||
)
|
||||
|
||||
const placeholder = computed(() => placeholders[placeholderIndex.value] ?? placeholders[0])
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<UnsavedChangesPopup
|
||||
:original="saved"
|
||||
:modified="current"
|
||||
:saving="saving"
|
||||
@reset="reset"
|
||||
@save="save"
|
||||
/>
|
||||
<div class="base-card block">
|
||||
<div class="group relative float-end ml-4">
|
||||
<IconSelect v-model="current.icon" />
|
||||
</div>
|
||||
<div>
|
||||
<SettingsLabel
|
||||
id="project-name"
|
||||
:title="messages.nameTitle"
|
||||
:description="messages.nameDescription"
|
||||
/>
|
||||
<div class="flex">
|
||||
<input
|
||||
id="project-name"
|
||||
v-model="current.title"
|
||||
:placeholder="formatMessage(placeholder.name)"
|
||||
autocomplete="off"
|
||||
maxlength="50"
|
||||
class="flex-grow"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<SettingsLabel
|
||||
id="project-tagline"
|
||||
:title="messages.taglineTitle"
|
||||
:description="messages.taglineDescription"
|
||||
/>
|
||||
<input
|
||||
id="project-tagline"
|
||||
v-model="current.tagline"
|
||||
:placeholder="formatMessage(placeholder.tagline)"
|
||||
autocomplete="off"
|
||||
maxlength="120"
|
||||
class="w-full"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<SettingsLabel id="project-url" :title="messages.urlTitle" />
|
||||
<div class="text-input-wrapper">
|
||||
<div class="text-input-wrapper__before">https://modrinth.com/project/</div>
|
||||
<input
|
||||
id="project-url"
|
||||
v-model="current.url"
|
||||
type="text"
|
||||
maxlength="64"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -96,6 +96,7 @@
|
||||
</div>
|
||||
<template
|
||||
v-if="
|
||||
!flags.newProjectEnvironmentSettings &&
|
||||
project.versions?.length !== 0 &&
|
||||
project.project_type !== 'resourcepack' &&
|
||||
project.project_type !== 'plugin' &&
|
||||
@@ -258,15 +259,23 @@ import { formatProjectStatus, formatProjectType } from '@modrinth/utils'
|
||||
import { Multiselect } from 'vue-multiselect'
|
||||
|
||||
import FileInput from '~/components/ui/FileInput.vue'
|
||||
import { useFeatureFlags } from '~/composables/featureFlags.ts'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const props = defineProps({
|
||||
project: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
projectV3: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
currentMember: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
||||
@@ -282,9 +282,24 @@
|
||||
<ProjectStatusBadge v-if="project.status" :status="project.status" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex !flex-row items-center !justify-end gap-2">
|
||||
<ButtonStyled
|
||||
v-if="projectsWithMigrationWarning.includes(project.id)"
|
||||
circular
|
||||
color="orange"
|
||||
>
|
||||
<nuxt-link
|
||||
v-tooltip="'Please review environment metadata'"
|
||||
:to="`/${getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/settings/environment`"
|
||||
>
|
||||
<TriangleAlertIcon />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<nuxt-link
|
||||
v-tooltip="formatMessage(commonMessages.settingsLabel)"
|
||||
:to="`/${getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}/settings`"
|
||||
@@ -310,6 +325,7 @@ import {
|
||||
SortAscIcon,
|
||||
SortDescIcon,
|
||||
TrashIcon,
|
||||
TriangleAlertIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
@@ -344,6 +360,7 @@ const { formatMessage } = useVIntl()
|
||||
|
||||
const user = await useUser()
|
||||
const projects = ref([])
|
||||
const projectsWithMigrationWarning = ref([])
|
||||
const selectedProjects = ref([])
|
||||
const sortBy = ref('Name')
|
||||
const descending = ref(false)
|
||||
@@ -437,6 +454,15 @@ async function bulkEditLinks() {
|
||||
await initUserProjects()
|
||||
if (user.value?.projects) {
|
||||
projects.value = updateSort(user.value.projects, 'Name', false)
|
||||
user.value?.projectsV3?.forEach((project) => {
|
||||
if (
|
||||
(project.side_types_migration_review_status === 'pending' &&
|
||||
project.project_types.includes('mod')) ||
|
||||
project.project_types.includes('modpack')
|
||||
) {
|
||||
projectsWithMigrationWarning.value.push(project.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -181,7 +181,8 @@ useSeoMeta({
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul > li:not(:last-child) {
|
||||
ul,
|
||||
ol > li:not(:last-child) {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -203,7 +204,7 @@ useSeoMeta({
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
margin-bottom: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -1,5 +1,12 @@
|
||||
{
|
||||
"articles": [
|
||||
{
|
||||
"title": "Creators: Verify Your Environment Metadata",
|
||||
"summary": "We've overhauled the environment metadata on Modrinth, and all creators must verify their settings.",
|
||||
"thumbnail": "https://modrinth.com/news/article/new-environments/thumbnail.webp",
|
||||
"date": "2025-08-28T07:00:00.000Z",
|
||||
"link": "https://modrinth.com/news/article/new-environments"
|
||||
},
|
||||
{
|
||||
"title": "Get a Free Modrinth Server",
|
||||
"summary": "In partnership with Medal.tv, get a 5-day free trial for Modrinth Servers",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user