You've already forked AstralRinth
Improve accessibiltiy of env selector, improve mobile support, and message for those with no permission (#4304)
* Fix redirect from project ID * improve fix * improve accessibility of environment selector * lint * fix mobile accessibility of project settings and improve message for those without permission * disable envs when multiple + lint
This commit is contained in:
@@ -338,7 +338,7 @@ body {
|
||||
|
||||
--size-navbar-height: 3.5rem;
|
||||
--size-mobile-navbar-height: 3.5rem;
|
||||
--size-mobile-navbar-height-expanded: 13.75rem;
|
||||
--size-mobile-navbar-height-expanded: 26.5rem;
|
||||
|
||||
--spacing-card-lg: 1.5rem;
|
||||
--spacing-card-bg: 1rem;
|
||||
@@ -367,16 +367,8 @@ body {
|
||||
--font-weight-heading: var(--font-weight-extrabold);
|
||||
--font-weight-title: var(--font-weight-extrabold);
|
||||
|
||||
@media screen and (min-width: 320px) {
|
||||
--size-mobile-navbar-height-expanded: 11.5rem;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 432px) {
|
||||
--size-mobile-navbar-height-expanded: 9.25rem;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 765px) {
|
||||
--size-mobile-navbar-height-expanded: 7rem;
|
||||
@media screen and (min-width: 354px) {
|
||||
--size-mobile-navbar-height-expanded: 15.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
/ 100%;
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
margin-top: var(--spacing-card-md);
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.normal-page__sidebar {
|
||||
|
||||
@@ -770,6 +770,15 @@
|
||||
"project.download.title": {
|
||||
"message": "Download {title}"
|
||||
},
|
||||
"project.environment.migration-no-permission.message": {
|
||||
"message": "We've just overhauled the Environments system on Modrinth and new options are now available. You don't have permission to modify these settings, but please let another member of the project know that the environment metadata needs to be verified."
|
||||
},
|
||||
"project.environment.migration-no-permission.title": {
|
||||
"message": "Environment metadata needs to be reviewed"
|
||||
},
|
||||
"project.environment.migration.learn-more": {
|
||||
"message": "Learn more about this change"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div v-if="route.name.startsWith('type-id-settings')" class="normal-page no-sidebar">
|
||||
<div class="normal-page__header">
|
||||
<div
|
||||
class="mb-4 flex items-center gap-2 border-0 border-b-[1px] border-solid border-divider pb-4 text-lg font-semibold"
|
||||
class="mb-4 flex flex-wrap items-center gap-x-2 gap-y-3 border-0 border-b-[1px] border-solid border-divider pb-4 text-lg font-semibold"
|
||||
>
|
||||
<nuxt-link
|
||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}`"
|
||||
@@ -759,21 +759,31 @@
|
||||
projectV3.environment[0] !== 'unknown'
|
||||
"
|
||||
type="warning"
|
||||
:header="formatMessage(messages.environmentMigrationTitle)"
|
||||
:header="
|
||||
formatMessage(
|
||||
hasEditDetailsPermission
|
||||
? messages.environmentMigrationTitle
|
||||
: messages.environmentMigrationNoPermissionTitle,
|
||||
)
|
||||
"
|
||||
class="mt-3"
|
||||
>
|
||||
{{ formatMessage(messages.environmentMigrationMessage) }}
|
||||
<ButtonStyled color="orange">
|
||||
<nuxt-link
|
||||
v-tooltip="
|
||||
hasEditDetailsPermission
|
||||
? undefined
|
||||
: formatMessage(commonProjectSettingsMessages.noPermissionDescription)
|
||||
"
|
||||
:to="`/project/${project.id}/settings/environment`"
|
||||
class="mt-3 w-fit"
|
||||
:disabled="!hasEditDetailsPermission"
|
||||
>
|
||||
{{
|
||||
formatMessage(
|
||||
hasEditDetailsPermission
|
||||
? messages.environmentMigrationMessage
|
||||
: messages.environmentMigrationNoPermissionMessage,
|
||||
)
|
||||
}}
|
||||
<nuxt-link
|
||||
to="/news/article/new-environments"
|
||||
target="_blank"
|
||||
class="mt-1 block w-fit font-semibold text-orange hover:underline"
|
||||
>
|
||||
{{ formatMessage(messages.environmentMigrationLink) }}
|
||||
</nuxt-link>
|
||||
<ButtonStyled v-if="hasEditDetailsPermission" color="orange">
|
||||
<nuxt-link :to="`/project/${project.id}/settings/environment`" class="mt-3 w-fit">
|
||||
<SettingsIcon /> {{ formatMessage(messages.reviewEnvironmentSettings) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
@@ -966,7 +976,6 @@ import {
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
commonProjectSettingsMessages,
|
||||
injectNotificationManager,
|
||||
NewModal,
|
||||
OverflowMenu,
|
||||
@@ -1152,6 +1161,19 @@ const messages = defineMessages({
|
||||
id: 'project.environment.migration.title',
|
||||
defaultMessage: 'Please review environment metadata',
|
||||
},
|
||||
environmentMigrationNoPermissionMessage: {
|
||||
id: 'project.environment.migration-no-permission.message',
|
||||
defaultMessage:
|
||||
"We've just overhauled the Environments system on Modrinth and new options are now available. You don't have permission to modify these settings, but please let another member of the project know that the environment metadata needs to be verified.",
|
||||
},
|
||||
environmentMigrationNoPermissionTitle: {
|
||||
id: 'project.environment.migration-no-permission.title',
|
||||
defaultMessage: 'Environment metadata needs to be reviewed',
|
||||
},
|
||||
environmentMigrationLink: {
|
||||
id: 'project.environment.migration.learn-more',
|
||||
defaultMessage: 'Learn more about this change',
|
||||
},
|
||||
followersStat: {
|
||||
id: 'project.stats.followers-label',
|
||||
defaultMessage: 'follower{count, plural, one {} other {s}}',
|
||||
|
||||
@@ -41,7 +41,7 @@ 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 class="experimental-styles-within grid gap-4 lg:grid-cols-[1fr_3fr]">
|
||||
<div>
|
||||
<aside class="universal-card">
|
||||
<NavStack>
|
||||
@@ -140,7 +140,7 @@ const organization = defineModel<any>('organization')
|
||||
</NavStack>
|
||||
</aside>
|
||||
</div>
|
||||
<div>
|
||||
<div class="min-w-0">
|
||||
<NuxtPage
|
||||
v-model:project="project"
|
||||
v-model:project-v3="projectV3"
|
||||
|
||||
@@ -110,18 +110,6 @@ const messages = defineMessages({
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<UnsavedChangesPopup
|
||||
v-if="supportsEnvironment && hasPermission"
|
||||
: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) }}
|
||||
@@ -166,8 +154,23 @@ const messages = defineMessages({
|
||||
:body="formatMessage(messages.reviewOptionsDescription)"
|
||||
class="mb-3"
|
||||
/>
|
||||
<ProjectSettingsEnvSelector v-model="current.environment" :disabled="!hasPermission" />
|
||||
<ProjectSettingsEnvSelector
|
||||
v-model="current.environment"
|
||||
:disabled="!hasPermission || projectV3?.environment?.length > 1"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<UnsavedChangesPopup
|
||||
v-if="supportsEnvironment && hasPermission"
|
||||
: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>
|
||||
|
||||
@@ -67,7 +67,9 @@
|
||||
</label>
|
||||
<div class="text-input-wrapper">
|
||||
<div class="text-input-wrapper__before">
|
||||
https://modrinth.com/{{ $getProjectTypeForUrl(project.project_type, project.loaders) }}/
|
||||
<span class="hidden sm:inline">https://modrinth.com</span>/{{
|
||||
$getProjectTypeForUrl(project.project_type, project.loaders)
|
||||
}}/
|
||||
</div>
|
||||
<input
|
||||
id="project-slug"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="font-semibold flex justify-between gap-4">
|
||||
<slot name="header">{{ header }}</slot>
|
||||
</div>
|
||||
<div class="font-normal">
|
||||
<div class="font-normal text-sm sm:text-base">
|
||||
<slot>{{ body }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<button
|
||||
role="radio"
|
||||
:aria-checked="selected"
|
||||
:aria-disabled="disabled"
|
||||
class="px-4 py-3 text-left border-0 font-medium border-2 border-button-bg border-solid flex gap-2 transition-all cursor-pointer rounded-xl"
|
||||
:class="
|
||||
(selected ? 'text-contrast bg-button-bg' : 'text-primary bg-transparent') +
|
||||
@@ -10,8 +13,8 @@
|
||||
:disabled="disabled"
|
||||
@click="emit('select')"
|
||||
>
|
||||
<RadioButtonCheckedIcon v-if="selected" class="text-brand h-5 w-5 shrink-0" />
|
||||
<RadioButtonIcon v-else class="h-5 w-5 shrink-0" />
|
||||
<RadioButtonCheckedIcon v-if="selected" class="text-brand h-5 w-5 shrink-0" aria-hidden="true" />
|
||||
<RadioButtonIcon v-else class="h-5 w-5 shrink-0" aria-hidden="true" />
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -50,20 +50,15 @@ const shown = computed(() => {
|
||||
function localizeIfPossible(message: MessageDescriptor | string) {
|
||||
return typeof message === 'string' ? message : formatMessage(message)
|
||||
}
|
||||
|
||||
const bodyText = computed(() => localizeIfPossible(props.text))
|
||||
const saveText = computed(() =>
|
||||
localizeIfPossible(props.saving ? props.savingLabel : props.saveLabel),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="pop-in">
|
||||
<div v-if="shown" class="fixed w-full z-10 left-0 bottom-0 p-4">
|
||||
<div v-if="shown" class="fixed w-full z-10 left-0 p-4 unsaved-changes-popup">
|
||||
<div
|
||||
class="flex items-center rounded-2xl bg-bg-raised border-2 border-divider border-solid mx-auto max-w-[77rem] p-4"
|
||||
class="flex items-center gap-2 rounded-2xl bg-bg-raised border-2 border-divider border-solid mx-auto max-w-[77rem] p-4"
|
||||
>
|
||||
<p class="m-0 font-semibold">{{ bodyText }}</p>
|
||||
<p class="m-0 font-semibold text-sm md:text-base">{{ localizeIfPossible(text) }}</p>
|
||||
<div class="ml-auto flex gap-2">
|
||||
<ButtonStyled v-if="canReset" type="transparent">
|
||||
<button :disabled="saving" @click="(e) => emit('reset', e)">
|
||||
@@ -74,7 +69,7 @@ const saveText = computed(() =>
|
||||
<button :disabled="saving" @click="(e) => emit('save', e)">
|
||||
<SpinnerIcon v-if="saving" class="animate-spin" />
|
||||
<component :is="saveIcon" v-else />
|
||||
{{ saveText }}
|
||||
{{ localizeIfPossible(saving ? savingLabel : saveLabel) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -103,4 +98,19 @@ const saveText = computed(() =>
|
||||
translate: 0 0.25rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.unsaved-changes-popup {
|
||||
transition: bottom 0.25s ease-in-out;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
@media (any-hover: none) and (max-width: 640px) {
|
||||
.unsaved-changes-popup {
|
||||
bottom: var(--size-mobile-navbar-height);
|
||||
}
|
||||
|
||||
.expanded-mobile-nav .unsaved-changes-popup {
|
||||
bottom: var(--size-mobile-navbar-height-expanded);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { EnvironmentV3 } from '@modrinth/utils'
|
||||
import { defineMessage, type MessageDescriptor, useVIntl } from '@vintl/vintl'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { commonProjectSettingsMessages } from '../../../../utils'
|
||||
import LargeRadioButton from '../../../base/LargeRadioButton.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
@@ -23,6 +24,15 @@ type EnvironmentRadioOption = {
|
||||
description?: MessageDescriptor
|
||||
}
|
||||
|
||||
const subOptionLabel = defineMessage({
|
||||
id: 'project.settings.environment.suboption.accessibility-suboption-group-label',
|
||||
defaultMessage: 'Suboptions of {option}',
|
||||
})
|
||||
const optionLabelFormat = defineMessage({
|
||||
id: 'project.settings.environment.suboption.accessibility-option-label',
|
||||
defaultMessage: '{title}: {description}',
|
||||
})
|
||||
|
||||
const OUTER_OPTIONS = {
|
||||
client: {
|
||||
title: defineMessage({
|
||||
@@ -225,58 +235,68 @@ const simulateSave = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template
|
||||
v-for="({ title, description, suboptions }, key, index) in OUTER_OPTIONS"
|
||||
:key="`env-option-${key}`"
|
||||
>
|
||||
<LargeRadioButton
|
||||
class="!w-full"
|
||||
:class="{ 'mt-2': index > 0 }"
|
||||
:selected="currentOuterOption === key"
|
||||
:disabled="disabled"
|
||||
@select="
|
||||
() => {
|
||||
if (currentOuterOption !== key) {
|
||||
currentSubOption = suboptions ? (Object.keys(suboptions)[0] as SubOptionKey) : undefined
|
||||
}
|
||||
currentOuterOption = key
|
||||
simulateSave = false
|
||||
}
|
||||
"
|
||||
<div role="radiogroup" :aria-label="formatMessage(commonProjectSettingsMessages.environment)">
|
||||
<template
|
||||
v-for="({ title, description, suboptions }, key, index) in OUTER_OPTIONS"
|
||||
:key="`env-option-${key}`"
|
||||
>
|
||||
<span class="flex flex-col">
|
||||
<span>{{ formatMessage(title) }}</span>
|
||||
<span v-if="description" class="text-sm text-secondary">{{
|
||||
formatMessage(description)
|
||||
}}</span>
|
||||
</span>
|
||||
</LargeRadioButton>
|
||||
<div v-if="suboptions" class="pl-8">
|
||||
<LargeRadioButton
|
||||
v-for="(
|
||||
{ title: suboptionTitle, description: suboptionDescription }, suboptionKey
|
||||
) in suboptions"
|
||||
:key="`env-option-${key}-${suboptionKey}`"
|
||||
class="!w-full mt-2"
|
||||
:class="{
|
||||
'opacity-50': currentOuterOption !== key,
|
||||
}"
|
||||
:selected="currentSubOption === suboptionKey"
|
||||
class="!w-full"
|
||||
:class="{ 'mt-2': index > 0 }"
|
||||
:selected="currentOuterOption === key"
|
||||
:disabled="disabled"
|
||||
:aria-label="formatMessage(optionLabelFormat, { title: formatMessage(title), description: formatMessage(description)})"
|
||||
@select="
|
||||
() => {
|
||||
if (currentOuterOption !== key) {
|
||||
currentSubOption = suboptions
|
||||
? (Object.keys(suboptions)[0] as SubOptionKey)
|
||||
: undefined
|
||||
}
|
||||
currentOuterOption = key
|
||||
currentSubOption = suboptionKey
|
||||
simulateSave = false
|
||||
}
|
||||
"
|
||||
>
|
||||
<span class="flex flex-col">
|
||||
<span>{{ formatMessage(suboptionTitle) }}</span>
|
||||
<span v-if="suboptionDescription" class="text-sm text-secondary">{{
|
||||
formatMessage(suboptionDescription)
|
||||
<span>{{ formatMessage(title) }}</span>
|
||||
<span v-if="description" class="text-sm text-secondary">{{
|
||||
formatMessage(description)
|
||||
}}</span>
|
||||
</span>
|
||||
</LargeRadioButton>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-if="suboptions"
|
||||
class="pl-8"
|
||||
role="radiogroup"
|
||||
:aria-label="formatMessage(subOptionLabel, { option: formatMessage(title) })"
|
||||
>
|
||||
<LargeRadioButton
|
||||
v-for="(
|
||||
{ title: suboptionTitle, description: suboptionDescription }, suboptionKey
|
||||
) in suboptions"
|
||||
:key="`env-option-${key}-${suboptionKey}`"
|
||||
class="!w-full mt-2"
|
||||
:class="{
|
||||
'opacity-50': currentOuterOption !== key,
|
||||
}"
|
||||
:selected="currentSubOption === suboptionKey"
|
||||
:disabled="disabled"
|
||||
@select="
|
||||
() => {
|
||||
currentOuterOption = key
|
||||
currentSubOption = suboptionKey
|
||||
}
|
||||
"
|
||||
>
|
||||
<span class="flex flex-col">
|
||||
<span>{{ formatMessage(suboptionTitle) }}</span>
|
||||
<span v-if="suboptionDescription" class="text-sm text-secondary">{{
|
||||
formatMessage(suboptionDescription)
|
||||
}}</span>
|
||||
</span>
|
||||
</LargeRadioButton>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -488,6 +488,12 @@
|
||||
"project.settings.environment.singleplayer.title": {
|
||||
"defaultMessage": "Singleplayer only"
|
||||
},
|
||||
"project.settings.environment.suboption.accessibility-option-label": {
|
||||
"defaultMessage": "{title}: {description}"
|
||||
},
|
||||
"project.settings.environment.suboption.accessibility-suboption-group-label": {
|
||||
"defaultMessage": "Suboptions of {option}"
|
||||
},
|
||||
"project.settings.environment.title": {
|
||||
"defaultMessage": "Environment"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user