mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 09:04:55 +00:00
update tags page + sort project types consistently (#7044)
This commit is contained in:
@@ -138,22 +138,6 @@
|
||||
<BracesIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.datapack) }}
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
type="quiet"
|
||||
to="/discover/modpacks"
|
||||
:class="
|
||||
route.name === 'discover-modpacks' || route.path.startsWith('/modpack/')
|
||||
? (route.name === 'discover-modpacks'
|
||||
? 'main-nav-primary'
|
||||
: 'main-nav-secondary') === 'main-nav-primary'
|
||||
? '!bg-[var(--color-button-bg-selected)] !text-[var(--color-button-text-selected)] [&>svg]:!text-[var(--color-button-text-selected)]'
|
||||
: '!bg-[var(--color-button-bg)] !text-contrast'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<PackageOpenIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.modpack) }}
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
type="quiet"
|
||||
to="/discover/shaders"
|
||||
@@ -170,6 +154,22 @@
|
||||
<GlassesIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.shader) }}
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
type="quiet"
|
||||
to="/discover/modpacks"
|
||||
:class="
|
||||
route.name === 'discover-modpacks' || route.path.startsWith('/modpack/')
|
||||
? (route.name === 'discover-modpacks'
|
||||
? 'main-nav-primary'
|
||||
: 'main-nav-secondary') === 'main-nav-primary'
|
||||
? '!bg-[var(--color-button-bg-selected)] !text-[var(--color-button-text-selected)] [&>svg]:!text-[var(--color-button-text-selected)]'
|
||||
: '!bg-[var(--color-button-bg)] !text-contrast'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<PackageOpenIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonProjectTypeCategoryMessages.modpack) }}
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
type="quiet"
|
||||
to="/discover/plugins"
|
||||
@@ -1215,8 +1215,8 @@ const navRoutes = computed(() => [
|
||||
href: '/discover/mods',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('plugin', true)),
|
||||
href: '/discover/plugins',
|
||||
label: formatMessage(getProjectTypeMessage('resourcepack', true)),
|
||||
href: '/discover/resourcepacks',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('datapack', true)),
|
||||
@@ -1226,14 +1226,14 @@ const navRoutes = computed(() => [
|
||||
label: formatMessage(getProjectTypeMessage('shader', true)),
|
||||
href: '/discover/shaders',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('resourcepack', true)),
|
||||
href: '/discover/resourcepacks',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('modpack', true)),
|
||||
href: '/discover/modpacks',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('plugin', true)),
|
||||
href: '/discover/plugins',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('server', true)),
|
||||
href: '/discover/servers',
|
||||
|
||||
@@ -3929,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Hervorgehobene Tags"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Du kannst bis zu 3 deiner relevantesten Tags als hervorheben. Andere Tags werden möglicherweise hervorgehoben, falls du nicht alle 3 auswählst."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Wähle alle Funktionen aus, die dein {type} nutzt."
|
||||
},
|
||||
@@ -3947,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Wähle mindestens eine Kategorie aus, um eine Kategorie hervorzuheben."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Genaue Tags sind wichtig, damit Nutzer dein {type} leichter finden können. Achte darauf, alle zutreffenden Tags auszuwählen."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
|
||||
@@ -3929,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Hervorgehobene Tags"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Du kannst bis zu 3 deiner relevantesten Tags als hervorheben. Andere Tags werden möglicherweise hervorgehoben, falls du nicht alle 3 auswählst."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Wähle alle Funktionen aus, die dein {type} nutzt."
|
||||
},
|
||||
@@ -3947,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Wähle mindestens eine Kategorie aus, um eine Kategorie hervorzuheben."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Genaue Tags sind wichtig, damit Nutzer dein {type} leichter finden können. Achte darauf, alle zutreffenden Tags auszuwählen."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
|
||||
@@ -3929,12 +3929,27 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Featured tags"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "You can feature up to 3 of your most relevant tags. Other tags may be promoted to featured if you do not select all 3."
|
||||
"project.settings.tags.featured-tags-required": {
|
||||
"message": "You must have at least one featured tag."
|
||||
},
|
||||
"project.settings.tags.featured-tags-select-description": {
|
||||
"message": "Select your most relevant tags. These are displayed before the rest of your tags."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Select all of the features that your {type} makes use of."
|
||||
},
|
||||
"project.settings.tags.group-title.categories": {
|
||||
"message": "{showType, select, yes {{types} categories} other {Categories}}"
|
||||
},
|
||||
"project.settings.tags.group-title.features": {
|
||||
"message": "{showType, select, yes {{types} features} other {Features}}"
|
||||
},
|
||||
"project.settings.tags.group-title.performance-impact": {
|
||||
"message": "{showType, select, yes {{types} performance impact} other {Performance impact}}"
|
||||
},
|
||||
"project.settings.tags.group-title.resolutions": {
|
||||
"message": "{showType, select, yes {{types} resolutions} other {Resolutions}}"
|
||||
},
|
||||
"project.settings.tags.multiple-resolution-tags-warning": {
|
||||
"message": "You've selected {count} resolution tags ({tags}). Resource packs should typically only have one resolution tag."
|
||||
},
|
||||
@@ -3947,9 +3962,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Select at least one category in order to feature a category."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Accurate tagging is important to help people find your {type}. Make sure to select all tags that apply."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
|
||||
@@ -3929,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Etiquetas destacadas"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Puedes destacar hasta 3 de tus etiquetas más relevantes. Otras etiquetas pueden ser promovidas a destacadas si no seleccionas las 3 completas."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Selecciona todas las características que tu {type} usa."
|
||||
},
|
||||
@@ -3947,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Selecciona al menos una categoría para destacar una categoría."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Seleccionar etiquetas relevantes es importante para ayudar a la gente a encontrar tu {type}. Asegúrate de seleccionar todas las etiquetas que apliquen."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Etiquetas"
|
||||
},
|
||||
|
||||
@@ -3929,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Tags mis en avant"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Vous pouvez mettre en avant jusqu'à 3 de vos tags les plus pertinents. D'autres tags peuvent être mis en avant si vous ne sélectionnez pas les 3."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Sélectionnez toutes les fonctionnalités que votre {type} utilise."
|
||||
},
|
||||
@@ -3947,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Sélectionnez au moins une catégorie afin de pouvoir mettre en avant une catégorie."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Un étiquetage correct est important pour aider les gens à trouver votre {type}. Faites en sorte de sélectionner tous les tags qui s'appliquent."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
|
||||
@@ -3641,9 +3641,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Kiemelt címkék"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Legfeljebb 3 legrelevánsabb címkét jelölhetsz ki kiemeltként. Ha nem választod ki mind a 3-at, akkor a többi címke is kiemeltként jelenhet meg."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Jelöld ki az összes olyan funkciót, amelyet a {type} használ."
|
||||
},
|
||||
@@ -3659,9 +3656,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Válassz ki legalább egy kategóriát ahhoz, hogy kiemelj egy kategóriát."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "A pontos címkézés fontos ahhoz, hogy a {type} könnyebben me. Feltétlenül válaszd ki az összes releváns címkét."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Címkék"
|
||||
},
|
||||
|
||||
@@ -3917,9 +3917,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Tag in evidenza"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Puoi mettere in evidenza fino a 3 tag. Altri tag potrebbero aggiungersi se non bastassero."
|
||||
},
|
||||
"project.settings.tags.multiple-resolution-tags-warning": {
|
||||
"message": "Hai selezionato {count} tag di risoluzione ({tags}). I pacchetti di risorse dovrebbero averne solo uno."
|
||||
},
|
||||
|
||||
@@ -3929,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "추천 태그"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "가장 적합한 태그를 최대 3개까지 지정할 수 있습니다. 3개를 모두 선택하지 않으면 다른 태그가 추천 태그로 표시될 수 있습니다."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": " {type}에서 사용하는 모든 기능을 선택하세요."
|
||||
},
|
||||
@@ -3947,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "추천 카테고리를 지정하려면 적어도 하나의 카테고리를 선택해야 합니다."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "정확한 태그 선택은 {type}을 쉽게 찾을 수 있도록 돕습니다. 관련된 모든 태그를 선택해 주세요."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "태그"
|
||||
},
|
||||
|
||||
@@ -3926,9 +3926,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Uitgelichte tags"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Je kunt maximaal 3 van je meest relevante tags als uitgelichte tags selecteren. Als je niet alle 3 selecteert, kunnen andere tags als uitgelichte tags worden weergegeven."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Selecteer alle functies waarvan je {type} gebruikmaakt."
|
||||
},
|
||||
@@ -3944,9 +3941,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Selecteer minstens één categorie om een categorie uit te lichten."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Nauwkeurige tags zijn belangrijk om mensen te helpen je {type} te vinden. Zorg ervoor dat je alle relevante tags selecteert."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Tags"
|
||||
},
|
||||
|
||||
@@ -3875,9 +3875,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Wyróżnione tagi"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Możesz wyróżnić 3 z najlepiej pasujących do Twojego projektu tagów. Inne tagi mogą zostać pokazane jako wyróżnione, jeżeli nie wybierzesz wszystkich trzech."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Wszystkie funkcjonalności, których używa {type}"
|
||||
},
|
||||
|
||||
@@ -3929,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Etiquetas em destaque"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Você pode destacar até 3 das suas etiquetas mais relevantes. Outras etiquetas poderão ser promovidas a destaque caso você não selecione todas as 3."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Selecione todas as funcionalidades que o seu {type} utiliza."
|
||||
},
|
||||
@@ -3947,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Selecione pelo menos uma categoria para ela ser destacada."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Etiquetar corretamente é importante para ajudar as pessoas a encontrarem seu {type}. Certifique-se de selecionar todas as etiquetas aplicáveis."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Etiquetas"
|
||||
},
|
||||
|
||||
@@ -3920,9 +3920,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Ключевые теги"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Можно выбрать до 3 ключевых тегов. Если не выбрать все 3, то другие теги могут стать ключевыми."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Выберите все особенности, которые использует ваш {type}"
|
||||
},
|
||||
@@ -3935,9 +3932,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Выберите хотя бы одну категорию, чтобы отобразить ее."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Аккуратная маркировка важна, чтобы помочь людям найти ваш {type}"
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Теги"
|
||||
},
|
||||
|
||||
@@ -3926,9 +3926,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "Основні теґи"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "Ви можете обрати основними до 3 найбільш описових теґів. Інші теґи можуть стати основними, якщо не оберете всі 3."
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "Оберіть увесь функціонал, який присутній у {type}."
|
||||
},
|
||||
@@ -3944,9 +3941,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "Виберіть хоча б одну категорію, щоб рекомендувати її."
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "Указання правильних теґів необхідне, щоб люди могли легше знайти ваш {type}. Переконайтеся, що ви обрали підхожі теґи."
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "Теґи"
|
||||
},
|
||||
|
||||
@@ -3929,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "精选标签"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "你可以设置最多 3 个精选标签,标示出最相关的特点。如果没有选完 3 个标签,其他标签也可能提升为精选标签。"
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "选择你的{type}作用于哪类特性。"
|
||||
},
|
||||
@@ -3947,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "请先至少选择一个分类,再设置精选分类。"
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "准确选好标签,能极大帮助别人找到你的{type}。请确保选择所有合适的标签。"
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "标签"
|
||||
},
|
||||
|
||||
@@ -3929,9 +3929,6 @@
|
||||
"project.settings.tags.featured-tags": {
|
||||
"message": "精選標籤"
|
||||
},
|
||||
"project.settings.tags.featured-tags-description": {
|
||||
"message": "你可以選擇最多 3 個最相關的標籤作為精選標籤。如果沒有選完 3 個標籤,系統可能會自動將其他標籤選為精選標籤。"
|
||||
},
|
||||
"project.settings.tags.features-description": {
|
||||
"message": "請選擇你的{type}包含的功能。"
|
||||
},
|
||||
@@ -3947,9 +3944,6 @@
|
||||
"project.settings.tags.select-at-least-one-category": {
|
||||
"message": "請選擇至少一個類別再選擇精選標籤。"
|
||||
},
|
||||
"project.settings.tags.tagging-importance-description": {
|
||||
"message": "精確的標籤對於協助使用者找到你的{type}非常重要。請務必選擇所有合適的標籤。"
|
||||
},
|
||||
"project.settings.tags.title": {
|
||||
"message": "標籤"
|
||||
},
|
||||
|
||||
@@ -1,174 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<ConfirmLeaveModal ref="confirmLeaveModal" />
|
||||
<section class="universal-card">
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">{{ formatMessage(messages.tagsTitle) }}</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="tooManyTagsWarning && !allTagsSelectedWarning"
|
||||
class="my-2 flex items-center gap-1.5 text-orange"
|
||||
>
|
||||
<TriangleAlertIcon class="my-auto" />
|
||||
{{ tooManyTagsWarning }}
|
||||
</div>
|
||||
|
||||
<div v-if="multipleResolutionTagsWarning" class="my-2 flex items-center gap-1.5 text-orange">
|
||||
<TriangleAlertIcon class="my-auto" />
|
||||
{{ multipleResolutionTagsWarning }}
|
||||
</div>
|
||||
|
||||
<div v-if="allTagsSelectedWarning" class="my-2 flex items-center gap-1.5 text-red">
|
||||
<TriangleAlertIcon class="my-auto" />
|
||||
<span>{{ allTagsSelectedWarning }}</span>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{
|
||||
formatMessage(messages.taggingImportanceDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
|
||||
<p
|
||||
v-if="project.versions.length === 0 && projectV3?.minecraft_server == null"
|
||||
class="known-errors"
|
||||
>
|
||||
{{ formatMessage(messages.uploadVersionFirst) }}
|
||||
</p>
|
||||
<template v-else>
|
||||
<template v-for="header in Object.keys(categoryLists)" :key="`categories-${header}`">
|
||||
<div class="label mb-3">
|
||||
<h4>
|
||||
<span class="label__title">{{ formatCategoryHeader(formatMessage, header) }}</span>
|
||||
</h4>
|
||||
<span class="label__description">
|
||||
<template v-if="header === 'categories'">
|
||||
{{
|
||||
formatMessage(messages.categoriesDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="header === 'features'">
|
||||
{{
|
||||
formatMessage(messages.featuresDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="header === 'resolutions'">
|
||||
{{
|
||||
formatMessage(messages.resolutionsDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="header === 'performance impact'">
|
||||
{{
|
||||
formatMessage(messages.performanceImpactDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="category-list input-div">
|
||||
<Checkbox
|
||||
v-for="category in categoryLists[header]"
|
||||
:key="`category-${header}-${category.name}`"
|
||||
:model-value="current.selectedTags.includes(category)"
|
||||
:description="formatCategory(formatMessage, category.name)"
|
||||
class="category-selector"
|
||||
@update:model-value="toggleCategory(category)"
|
||||
>
|
||||
<div class="category-selector__label">
|
||||
<component
|
||||
:is="getTagIcon(category.name)"
|
||||
v-if="header !== 'resolutions' && getTagIcon(category.name)"
|
||||
aria-hidden="true"
|
||||
class="icon"
|
||||
/>
|
||||
<span aria-hidden="true">
|
||||
<FormattedTag :tag="category.name" enforce-type="category" />
|
||||
</span>
|
||||
</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</template>
|
||||
<div class="label">
|
||||
<h4>
|
||||
<span class="label__title"
|
||||
><StarIcon /> {{ formatMessage(messages.featuredTags) }}</span
|
||||
>
|
||||
</h4>
|
||||
<span class="label__description">{{
|
||||
formatMessage(messages.featuredTagsDescription)
|
||||
}}</span>
|
||||
</div>
|
||||
<p v-if="current.selectedTags.length < 1">
|
||||
{{ formatMessage(messages.selectAtLeastOneCategory) }}
|
||||
</p>
|
||||
<div class="category-list input-div">
|
||||
<Checkbox
|
||||
v-for="category in current.selectedTags"
|
||||
:key="`featured-category-${category.name}`"
|
||||
class="category-selector"
|
||||
:model-value="current.featuredTags.includes(category)"
|
||||
:description="formatCategory(formatMessage, category.name)"
|
||||
:disabled="current.featuredTags.length >= 3 && !current.featuredTags.includes(category)"
|
||||
@update:model-value="toggleFeaturedCategory(category)"
|
||||
>
|
||||
<div class="category-selector__label">
|
||||
<component
|
||||
:is="getTagIcon(category.name)"
|
||||
v-if="category.header !== 'resolutions' && getTagIcon(category.name)"
|
||||
aria-hidden="true"
|
||||
class="icon"
|
||||
/>
|
||||
<span aria-hidden="true">
|
||||
<FormattedTag :tag="category.name" enforce-type="category" />
|
||||
</span>
|
||||
</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
<UnsavedChangesPopup
|
||||
:original="saved"
|
||||
:modified="current"
|
||||
:saving="saving"
|
||||
@reset="reset"
|
||||
@save="save"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
getCategoryIcon,
|
||||
SERVER_CATEGORY_ICON_MAP,
|
||||
StarIcon,
|
||||
TriangleAlertIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Admonition,
|
||||
Checkbox,
|
||||
ConfirmLeaveModal,
|
||||
defineMessages,
|
||||
formatCategory,
|
||||
formatCategoryHeader,
|
||||
formatProjectTypeSentence,
|
||||
FormattedTag,
|
||||
injectProjectPageContext,
|
||||
type MessageDescriptor,
|
||||
sortProjectTypes,
|
||||
TagItem,
|
||||
UnsavedChangesPopup,
|
||||
usePageLeaveSafety,
|
||||
useSavable,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatProjectType, sortedCategories } from '@modrinth/utils'
|
||||
import { capitalizeString, sortedCategories } from '@modrinth/utils'
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Category {
|
||||
@@ -178,19 +27,30 @@ interface Category {
|
||||
project_type: string
|
||||
}
|
||||
|
||||
interface CategoryGroup {
|
||||
id: string
|
||||
title: string
|
||||
description?: string
|
||||
categories: Category[]
|
||||
}
|
||||
|
||||
const MAX_FEATURED_TAGS = 3
|
||||
|
||||
const RESOLUTION_TAGS = ['8x-', '16x', '32x', '48x', '64x', '128x', '256x', '512x+']
|
||||
|
||||
const SHARED_CATEGORY_PROJECT_TYPES: Record<string, string> = {
|
||||
plugin: 'mod',
|
||||
datapack: 'mod',
|
||||
}
|
||||
|
||||
const tags = useGeneratedState()
|
||||
const { formatMessage, locale } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
tagsTitle: {
|
||||
title: {
|
||||
id: 'project.settings.tags.title',
|
||||
defaultMessage: 'Tags',
|
||||
},
|
||||
taggingImportanceDescription: {
|
||||
id: 'project.settings.tags.tagging-importance-description',
|
||||
defaultMessage:
|
||||
'Accurate tagging is important to help people find your {type}. Make sure to select all tags that apply.',
|
||||
},
|
||||
uploadVersionFirst: {
|
||||
id: 'project.settings.tags.upload-version-first',
|
||||
defaultMessage: 'Please upload a version first in order to select tags!',
|
||||
@@ -217,14 +77,18 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Featured tags',
|
||||
},
|
||||
featuredTagsDescription: {
|
||||
id: 'project.settings.tags.featured-tags-description',
|
||||
id: 'project.settings.tags.featured-tags-select-description',
|
||||
defaultMessage:
|
||||
'You can feature up to 3 of your most relevant tags. Other tags may be promoted to featured if you do not select all 3.',
|
||||
'Select your most relevant tags. These are displayed before the rest of your tags.',
|
||||
},
|
||||
selectAtLeastOneCategory: {
|
||||
id: 'project.settings.tags.select-at-least-one-category',
|
||||
defaultMessage: 'Select at least one category in order to feature a category.',
|
||||
},
|
||||
featuredTagsRequired: {
|
||||
id: 'project.settings.tags.featured-tags-required',
|
||||
defaultMessage: 'You must have at least one featured tag.',
|
||||
},
|
||||
tooManyTagsServerHardWarning: {
|
||||
id: 'project.settings.tags.too-many-tags-server-hard-warning',
|
||||
defaultMessage:
|
||||
@@ -252,72 +116,183 @@ const messages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
const groupTitleMessages: Record<string, MessageDescriptor> = defineMessages({
|
||||
categories: {
|
||||
id: 'project.settings.tags.group-title.categories',
|
||||
defaultMessage: '{showType, select, yes {{types} categories} other {Categories}}',
|
||||
},
|
||||
features: {
|
||||
id: 'project.settings.tags.group-title.features',
|
||||
defaultMessage: '{showType, select, yes {{types} features} other {Features}}',
|
||||
},
|
||||
resolutions: {
|
||||
id: 'project.settings.tags.group-title.resolutions',
|
||||
defaultMessage: '{showType, select, yes {{types} resolutions} other {Resolutions}}',
|
||||
},
|
||||
'performance impact': {
|
||||
id: 'project.settings.tags.group-title.performance-impact',
|
||||
defaultMessage:
|
||||
'{showType, select, yes {{types} performance impact} other {Performance impact}}',
|
||||
},
|
||||
})
|
||||
|
||||
const groupDescriptionMessages: Record<string, MessageDescriptor> = {
|
||||
categories: messages.categoriesDescription,
|
||||
features: messages.featuresDescription,
|
||||
resolutions: messages.resolutionsDescription,
|
||||
'performance impact': messages.performanceImpactDescription,
|
||||
}
|
||||
|
||||
const { projectV2: project, projectV3, patchProject } = injectProjectPageContext()
|
||||
|
||||
const formatCategoryName = (categoryName: string) => {
|
||||
return formatCategory(formatMessage, categoryName)
|
||||
}
|
||||
const formatCategoryName = (categoryName: string) => formatCategory(formatMessage, categoryName)
|
||||
|
||||
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
||||
|
||||
const getTagIcon = (categoryName: string) => {
|
||||
const iconName = isServerProject.value
|
||||
? (SERVER_CATEGORY_ICON_MAP[categoryName] ?? categoryName)
|
||||
: categoryName
|
||||
return getCategoryIcon(iconName)
|
||||
const canSelectTags = computed(() => project.value.versions.length > 0 || isServerProject.value)
|
||||
|
||||
const projectTypes = computed(() => {
|
||||
if (isServerProject.value) {
|
||||
return ['minecraft_java_server']
|
||||
}
|
||||
|
||||
const types = projectV3.value?.project_types?.length
|
||||
? projectV3.value.project_types
|
||||
: [project.value.actualProjectType]
|
||||
|
||||
return sortProjectTypes(new Set(types))
|
||||
})
|
||||
|
||||
const allCategories = computed(
|
||||
() => sortedCategories(tags.value, formatCategoryName, locale.value) as Category[],
|
||||
)
|
||||
|
||||
const projectTypesByCategoryList = computed(() => {
|
||||
const lists = new Map<string, string[]>()
|
||||
for (const projectType of projectTypes.value) {
|
||||
const source = SHARED_CATEGORY_PROJECT_TYPES[projectType] ?? projectType
|
||||
lists.set(source, [...(lists.get(source) ?? []), projectType])
|
||||
}
|
||||
return lists
|
||||
})
|
||||
|
||||
const projectTypeListFormatter = computed(
|
||||
() => new Intl.ListFormat(locale.value, { style: 'long', type: 'conjunction' }),
|
||||
)
|
||||
|
||||
function formatProjectTypeName(type: string) {
|
||||
return formatProjectTypeSentence(
|
||||
formatMessage,
|
||||
type === 'minecraft_java_server' ? 'server' : type,
|
||||
)
|
||||
}
|
||||
|
||||
const matchesProjectType = (x: Category) => {
|
||||
if (isServerProject.value) {
|
||||
return x.project_type === 'minecraft_java_server'
|
||||
} else {
|
||||
return x.project_type === project.value.actualProjectType
|
||||
function formatGroupTitle(header: string, types: string[]) {
|
||||
const message = groupTitleMessages[header]
|
||||
if (!message) {
|
||||
return formatCategoryHeader(formatMessage, header)
|
||||
}
|
||||
|
||||
const showType = projectTypes.value.length > 1
|
||||
return formatMessage(message, {
|
||||
showType: showType ? 'yes' : 'other',
|
||||
types: showType
|
||||
? capitalizeString(projectTypeListFormatter.value.format(types.map(formatProjectTypeName)))
|
||||
: '',
|
||||
})
|
||||
}
|
||||
|
||||
function formatGroupDescription(header: string, types: string[]) {
|
||||
const message = groupDescriptionMessages[header]
|
||||
if (!message) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// listing out every type gets unwieldy, fallback to the generic term when there's more than one
|
||||
return formatMessage(message, {
|
||||
type: formatProjectTypeName(types.length > 1 ? 'project' : types[0]),
|
||||
})
|
||||
}
|
||||
|
||||
const categorySections = computed(() => {
|
||||
const sections: {
|
||||
categoryList: string
|
||||
header: string
|
||||
types: string[]
|
||||
categories: Category[]
|
||||
}[] = []
|
||||
|
||||
for (const [categoryList, types] of projectTypesByCategoryList.value) {
|
||||
const byHeader = new Map<string, Category[]>()
|
||||
for (const category of allCategories.value) {
|
||||
if (category.project_type !== categoryList) continue
|
||||
byHeader.set(category.header, [...(byHeader.get(category.header) ?? []), category])
|
||||
}
|
||||
|
||||
for (const [header, categories] of byHeader) {
|
||||
sections.push({
|
||||
categoryList,
|
||||
header,
|
||||
types,
|
||||
categories:
|
||||
header === 'minecraft_server_features' ? withPokemonFirst(categories) : categories,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return sections
|
||||
})
|
||||
|
||||
const categoryGroups = computed<CategoryGroup[]>(() =>
|
||||
categorySections.value.map((section) => ({
|
||||
id: `${section.categoryList}-${section.header}`,
|
||||
title: formatGroupTitle(section.header, section.types),
|
||||
description: formatGroupDescription(section.header, section.types),
|
||||
categories: section.categories,
|
||||
})),
|
||||
)
|
||||
|
||||
function withPokemonFirst(categories: Category[]) {
|
||||
return categories.slice().sort((a, b) => {
|
||||
if (a.name === 'pokemon') return -1
|
||||
if (b.name === 'pokemon') return 1
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
const availableTags = computed(() => [
|
||||
...new Set(categorySections.value.flatMap((section) => section.categories.map((x) => x.name))),
|
||||
])
|
||||
|
||||
function hasSameTags(a: string[], b: string[]) {
|
||||
return a.length === b.length && a.every((tag) => b.includes(tag))
|
||||
}
|
||||
|
||||
const { saved, current, saving, hasChanges, reset, save } = useSavable(
|
||||
() => ({
|
||||
selectedTags: sortedCategories(tags.value, formatCategoryName, locale.value).filter(
|
||||
(x: Category) =>
|
||||
matchesProjectType(x) &&
|
||||
(project.value.categories.includes(x.name) ||
|
||||
project.value.additional_categories.includes(x.name)),
|
||||
) as Category[],
|
||||
featuredTags: sortedCategories(tags.value, formatCategoryName, locale.value).filter(
|
||||
(x: Category) => matchesProjectType(x) && project.value.categories.includes(x.name),
|
||||
) as Category[],
|
||||
selectedTags: availableTags.value.filter(
|
||||
(tag) =>
|
||||
project.value.categories.includes(tag) || project.value.additional_categories.includes(tag),
|
||||
),
|
||||
featuredTags: availableTags.value.filter((tag) => project.value.categories.includes(tag)),
|
||||
}),
|
||||
async () => {
|
||||
// Promote selected categories to featured if there are less than 3 featured
|
||||
const newFeaturedTags = current.value.featuredTags.slice()
|
||||
if (newFeaturedTags.length < 1 && current.value.selectedTags.length > newFeaturedTags.length) {
|
||||
const nonFeaturedCategories = current.value.selectedTags.filter(
|
||||
(x) => !newFeaturedTags.includes(x),
|
||||
)
|
||||
nonFeaturedCategories
|
||||
.slice(0, Math.min(nonFeaturedCategories.length, 3 - newFeaturedTags.length))
|
||||
.forEach((x) => newFeaturedTags.push(x))
|
||||
if (!canSave.value) {
|
||||
throw new Error('At least one tag must be featured')
|
||||
}
|
||||
|
||||
// Convert selected and featured categories to backend-usable arrays
|
||||
const categories = newFeaturedTags.map((x) => x.name)
|
||||
const additionalCategories = current.value.selectedTags
|
||||
.filter((x) => !newFeaturedTags.includes(x))
|
||||
.map((x) => x.name)
|
||||
const featuredTags = current.value.featuredTags
|
||||
const additionalCategories = current.value.selectedTags.filter(
|
||||
(tag) => !featuredTags.includes(tag),
|
||||
)
|
||||
|
||||
const data: Record<string, string[]> = {}
|
||||
|
||||
if (
|
||||
categories.length !== project.value.categories.length ||
|
||||
categories.some((value) => !project.value.categories.includes(value))
|
||||
) {
|
||||
data.categories = categories
|
||||
if (!hasSameTags(featuredTags, project.value.categories)) {
|
||||
data.categories = featuredTags
|
||||
}
|
||||
|
||||
if (
|
||||
additionalCategories.length !== project.value.additional_categories.length ||
|
||||
additionalCategories.some((value) => !project.value.additional_categories.includes(value))
|
||||
) {
|
||||
if (!hasSameTags(additionalCategories, project.value.additional_categories)) {
|
||||
data.additional_categories = additionalCategories
|
||||
}
|
||||
|
||||
@@ -327,31 +302,15 @@ const { saved, current, saving, hasChanges, reset, save } = useSavable(
|
||||
|
||||
const { confirmLeaveModal } = usePageLeaveSafety(hasChanges)
|
||||
|
||||
const categoryLists = computed(() => {
|
||||
const lists: Record<string, Category[]> = {}
|
||||
sortedCategories(tags.value, formatCategoryName, locale.value).forEach((x: Category) => {
|
||||
if (matchesProjectType(x)) {
|
||||
const header = x.header
|
||||
if (!lists[header]) {
|
||||
lists[header] = []
|
||||
}
|
||||
lists[header].push(x)
|
||||
}
|
||||
})
|
||||
const featuresKey = 'minecraft_server_features'
|
||||
if (lists[featuresKey]) {
|
||||
lists[featuresKey].sort((a, b) => {
|
||||
if (a.name === 'pokemon') return -1
|
||||
if (b.name === 'pokemon') return 1
|
||||
return 0
|
||||
})
|
||||
}
|
||||
return lists
|
||||
})
|
||||
const isFeaturedLimitReached = computed(
|
||||
() => current.value.featuredTags.length >= MAX_FEATURED_TAGS,
|
||||
)
|
||||
|
||||
const canSave = computed(() => current.value.featuredTags.length > 0)
|
||||
|
||||
const tooManyTagsWarning = computed(() => {
|
||||
const tagCount = current.value.selectedTags.length
|
||||
if (projectV3?.value?.minecraft_server != null) {
|
||||
if (isServerProject.value) {
|
||||
if (tagCount > 18) {
|
||||
return formatMessage(messages.tooManyTagsServerHardWarning, { count: tagCount })
|
||||
} else if (tagCount > 12) {
|
||||
@@ -364,122 +323,140 @@ const tooManyTagsWarning = computed(() => {
|
||||
})
|
||||
|
||||
const multipleResolutionTagsWarning = computed(() => {
|
||||
if (project.value.actualProjectType !== 'resourcepack') return null
|
||||
if (!projectTypes.value.includes('resourcepack')) return null
|
||||
|
||||
const resolutionTags = current.value.selectedTags.filter((tag) =>
|
||||
['8x-', '16x', '32x', '48x', '64x', '128x', '256x', '512x+'].includes(tag.name),
|
||||
)
|
||||
const resolutionTags = current.value.selectedTags.filter((tag) => RESOLUTION_TAGS.includes(tag))
|
||||
if (resolutionTags.length < 2) return null
|
||||
|
||||
if (resolutionTags.length > 1) {
|
||||
const tagsList = resolutionTags
|
||||
.map((t) => t.name)
|
||||
return formatMessage(messages.multipleResolutionTagsWarning, {
|
||||
count: resolutionTags.length,
|
||||
tags: resolutionTags
|
||||
.join(', ')
|
||||
.replace('8x-', '8x or lower')
|
||||
.replace('512x+', '512x or higher')
|
||||
return formatMessage(messages.multipleResolutionTagsWarning, {
|
||||
count: resolutionTags.length,
|
||||
tags: tagsList,
|
||||
})
|
||||
}
|
||||
return null
|
||||
.replace('512x+', '512x or higher'),
|
||||
})
|
||||
})
|
||||
|
||||
const allTagsSelectedWarning = computed(() => {
|
||||
const categoriesForProjectType = sortedCategories(
|
||||
tags.value,
|
||||
formatCategoryName,
|
||||
locale.value,
|
||||
).filter((x: Category) => x.project_type === project.value.actualProjectType)
|
||||
const totalSelectedTags = current.value.selectedTags.length
|
||||
|
||||
if (
|
||||
totalSelectedTags === categoriesForProjectType.length &&
|
||||
categoriesForProjectType.length > 0
|
||||
availableTags.value.length < 1 ||
|
||||
availableTags.value.length < 1 ||
|
||||
current.value.selectedTags.length !== availableTags.value.length
|
||||
) {
|
||||
return formatMessage(messages.allTagsSelectedWarning, {
|
||||
count: categoriesForProjectType.length,
|
||||
})
|
||||
return null
|
||||
}
|
||||
return null
|
||||
|
||||
return formatMessage(messages.allTagsSelectedWarning, { count: availableTags.value.length })
|
||||
})
|
||||
|
||||
const toggleCategory = (category: Category) => {
|
||||
if (current.value.selectedTags.includes(category)) {
|
||||
current.value.selectedTags = current.value.selectedTags.filter((x) => x !== category)
|
||||
if (current.value.featuredTags.includes(category)) {
|
||||
current.value.featuredTags = current.value.featuredTags.filter((x) => x !== category)
|
||||
}
|
||||
} else {
|
||||
current.value.selectedTags = [...current.value.selectedTags, category]
|
||||
function toggleTagRaw(selection: string[], tag: string) {
|
||||
if (selection.includes(tag)) {
|
||||
return selection.filter((x) => x !== tag)
|
||||
}
|
||||
return availableTags.value.filter((x) => x === tag || selection.includes(x))
|
||||
}
|
||||
|
||||
const toggleTag = (tag: string) => {
|
||||
current.value.selectedTags = toggleTagRaw(current.value.selectedTags, tag)
|
||||
if (!current.value.selectedTags.includes(tag)) {
|
||||
current.value.featuredTags = current.value.featuredTags.filter((x) => x !== tag)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleFeaturedCategory = (category: Category) => {
|
||||
if (current.value.featuredTags.includes(category)) {
|
||||
current.value.featuredTags = current.value.featuredTags.filter((x) => x !== category)
|
||||
} else {
|
||||
current.value.featuredTags = [...current.value.featuredTags, category]
|
||||
}
|
||||
const toggleFeatured = (tag: string) => {
|
||||
current.value.featuredTags = toggleTagRaw(current.value.featuredTags, tag)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<ConfirmLeaveModal ref="confirmLeaveModal" />
|
||||
<h2 class="mb-4 mt-0 text-2xl font-semibold">
|
||||
{{ formatMessage(messages.title) }}
|
||||
</h2>
|
||||
<p v-if="!canSelectTags" class="known-errors">
|
||||
{{ formatMessage(messages.uploadVersionFirst) }}
|
||||
</p>
|
||||
<div v-else class="flex flex-col gap-4">
|
||||
<Admonition v-if="allTagsSelectedWarning" type="critical" :body="allTagsSelectedWarning" />
|
||||
<Admonition v-else-if="tooManyTagsWarning" type="warning" :body="tooManyTagsWarning" />
|
||||
<Admonition
|
||||
v-if="multipleResolutionTagsWarning"
|
||||
type="warning"
|
||||
:body="multipleResolutionTagsWarning"
|
||||
/>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.label__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-card-xs);
|
||||
margin-top: var(--spacing-card-bg);
|
||||
<div
|
||||
v-for="group in categoryGroups"
|
||||
:key="group.id"
|
||||
class="rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4"
|
||||
>
|
||||
<h3 class="mb-1 mt-0 text-base font-semibold text-contrast">
|
||||
{{ group.title }}
|
||||
</h3>
|
||||
<p v-if="group.description" class="mb-3 mt-0 text-sm">
|
||||
{{ group.description }}
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-4">
|
||||
<Checkbox
|
||||
v-for="category in group.categories"
|
||||
:key="`${group.id}-${category.name}`"
|
||||
:model-value="current.selectedTags.includes(category.name)"
|
||||
:description="formatCategoryName(category.name)"
|
||||
@update:model-value="toggleTag(category.name)"
|
||||
>
|
||||
<span aria-hidden="true">
|
||||
<FormattedTag :tag="category.name" enforce-type="category" />
|
||||
</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
svg {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
.category-list {
|
||||
column-count: 4;
|
||||
column-gap: var(--spacing-card-lg);
|
||||
margin-bottom: var(--spacing-card-md);
|
||||
|
||||
:deep(.category-selector) {
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
.category-selector__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
|
||||
.icon {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
margin-right: 0.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1250px) {
|
||||
column-count: 3;
|
||||
}
|
||||
@media only screen and (max-width: 1024px) {
|
||||
column-count: 4;
|
||||
}
|
||||
@media only screen and (max-width: 960px) {
|
||||
column-count: 3;
|
||||
}
|
||||
@media only screen and (max-width: 750px) {
|
||||
column-count: 2;
|
||||
}
|
||||
@media only screen and (max-width: 530px) {
|
||||
column-count: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4">
|
||||
<div class="mb-1 flex items-center gap-2">
|
||||
<h3 class="m-0 text-base font-semibold text-contrast">
|
||||
{{ formatMessage(messages.featuredTags) }}
|
||||
</h3>
|
||||
<TagItem
|
||||
v-tooltip="
|
||||
current.featuredTags.length === 0
|
||||
? formatMessage(messages.featuredTagsRequired)
|
||||
: undefined
|
||||
"
|
||||
:style="canSave ? undefined : { '--_color': 'var(--color-red)' }"
|
||||
>
|
||||
{{ current.featuredTags.length }}/{{ MAX_FEATURED_TAGS }}
|
||||
</TagItem>
|
||||
</div>
|
||||
<p class="mb-3 mt-0 text-sm">
|
||||
{{ formatMessage(messages.featuredTagsDescription) }}
|
||||
</p>
|
||||
<p v-if="current.selectedTags.length < 1" class="m-0 text-sm text-secondary">
|
||||
{{ formatMessage(messages.selectAtLeastOneCategory) }}
|
||||
</p>
|
||||
<div v-else class="grid grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-4">
|
||||
<Checkbox
|
||||
v-for="name in current.selectedTags"
|
||||
:key="`featured-${name}`"
|
||||
:model-value="current.featuredTags.includes(name)"
|
||||
:description="formatCategoryName(name)"
|
||||
:disabled="isFeaturedLimitReached && !current.featuredTags.includes(name)"
|
||||
@update:model-value="toggleFeatured(name)"
|
||||
>
|
||||
<span aria-hidden="true">
|
||||
<FormattedTag :tag="name" enforce-type="category" />
|
||||
</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UnsavedChangesPopup
|
||||
:original="saved"
|
||||
:modified="current"
|
||||
:saving="saving"
|
||||
:can-save="canSave"
|
||||
:save-disabled-reason="messages.featuredTagsRequired"
|
||||
@reset="reset"
|
||||
@save="save"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -425,6 +425,7 @@ import {
|
||||
ProjectCardList,
|
||||
RadioButtons,
|
||||
SidebarCard,
|
||||
sortProjectTypes,
|
||||
StyledInput,
|
||||
TeleportOverflowMenu,
|
||||
useCompactNumber,
|
||||
@@ -769,7 +770,7 @@ const projectTypes = computed(() => {
|
||||
projects.value?.map((project) => project?.project_type).filter((x) => x !== undefined) || [],
|
||||
)
|
||||
projectSet.delete('project')
|
||||
return Array.from(projectSet)
|
||||
return sortProjectTypes(projectSet)
|
||||
})
|
||||
|
||||
function getProjectTypeSentenceMessage(type) {
|
||||
|
||||
@@ -236,6 +236,7 @@ import {
|
||||
ProjectCard,
|
||||
ProjectCardList,
|
||||
SidebarCard,
|
||||
sortProjectTypes,
|
||||
useCompactNumber,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
@@ -376,15 +377,14 @@ const isInvited = computed(() => {
|
||||
})
|
||||
|
||||
const projectTypes = computed(() => {
|
||||
const obj: Record<string, boolean> = {}
|
||||
const types = new Set<string>()
|
||||
|
||||
for (const project of projects.value ?? []) {
|
||||
obj[project.project_types[0] ?? 'project'] = true
|
||||
const type = project.project_types[0] ?? 'project'
|
||||
if (type !== 'project') types.add(type)
|
||||
}
|
||||
|
||||
delete obj.project
|
||||
|
||||
return Object.keys(obj)
|
||||
return sortProjectTypes(types)
|
||||
})
|
||||
function isProjectServer(project: ProjectV3): boolean {
|
||||
return project.minecraft_server != null
|
||||
@@ -494,15 +494,12 @@ const navLinks = computed(() => [
|
||||
label: formatMessage(commonMessages.allProjectType),
|
||||
href: `/organization/${organization.value?.slug}`,
|
||||
},
|
||||
...projectTypes.value
|
||||
.map((x) => {
|
||||
return {
|
||||
label: formatMessage(getProjectTypeMessage(x as ProjectType, true)),
|
||||
href: `/organization/${organization.value?.slug}/${x}s`,
|
||||
}
|
||||
})
|
||||
.slice()
|
||||
.sort((a, b) => a.label.localeCompare(b.label)),
|
||||
...projectTypes.value.map((x) => {
|
||||
return {
|
||||
label: formatMessage(getProjectTypeMessage(x as ProjectType, true)),
|
||||
href: `/organization/${organization.value?.slug}/${x}s`,
|
||||
}
|
||||
}),
|
||||
])
|
||||
|
||||
async function copyId() {
|
||||
|
||||
Reference in New Issue
Block a user