mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70c79f43ba | ||
|
|
d322a2b0c8 | ||
|
|
7ac1d17571 | ||
|
|
074699be45 | ||
|
|
7b2e5deb83 | ||
|
|
d0bdeb1bc6 | ||
|
|
832240a45f | ||
|
|
fc1aa28aa4 | ||
|
|
84b4f8e474 | ||
|
|
b14fad4237 | ||
|
|
ff1f86a0f4 | ||
|
|
e8f8722b70 | ||
|
|
518bcd414a |
@@ -66,6 +66,8 @@ jobs:
|
||||
|
||||
- name: Build Storybook
|
||||
working-directory: ./packages/ui
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
run: pnpm run build-storybook
|
||||
|
||||
- name: Configure short SHA
|
||||
@@ -76,8 +78,6 @@ jobs:
|
||||
|
||||
- name: Deploy Storybook preview
|
||||
uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3.15.0
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
@@ -76,6 +76,7 @@ import FriendsList from '@/components/ui/friends/FriendsList.vue'
|
||||
import AddServerToInstanceModal from '@/components/ui/install_flow/AddServerToInstanceModal.vue'
|
||||
import UnknownPackWarningModal from '@/components/ui/install_flow/UnknownPackWarningModal.vue'
|
||||
import MinecraftAuthErrorModal from '@/components/ui/minecraft-auth-error-modal/MinecraftAuthErrorModal.vue'
|
||||
import MinecraftRequiredModal from '@/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue'
|
||||
import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue'
|
||||
import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue'
|
||||
import InstallToPlayModal from '@/components/ui/modal/InstallToPlayModal.vue'
|
||||
@@ -684,6 +685,7 @@ watch(stateInitialized, (ready) => {
|
||||
const error = useError()
|
||||
const errorModal = ref()
|
||||
const minecraftAuthErrorModal = ref()
|
||||
const minecraftRequiredModal = ref()
|
||||
|
||||
const contentInstall = createContentInstall({ router, handleError })
|
||||
provideContentInstall(contentInstall)
|
||||
@@ -846,6 +848,7 @@ onMounted(() => {
|
||||
|
||||
error.setErrorModal(errorModal.value)
|
||||
error.setMinecraftAuthErrorModal(minecraftAuthErrorModal.value)
|
||||
error.setMinecraftRequiredModal(minecraftRequiredModal.value)
|
||||
|
||||
setContentIncompatibilityWarningModal(incompatibilityWarningModal.value)
|
||||
setContentInstallModal(modInstallModal.value)
|
||||
@@ -1787,6 +1790,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
<PopupNotificationPanel :has-sidebar="sidebarVisible" />
|
||||
<ErrorModal ref="errorModal" />
|
||||
<MinecraftAuthErrorModal ref="minecraftAuthErrorModal" />
|
||||
<MinecraftRequiredModal ref="minecraftRequiredModal" />
|
||||
<ContentInstallModal
|
||||
ref="modInstallModal"
|
||||
:instances="contentInstallInstances"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -52,10 +52,6 @@ defineExpose({
|
||||
if (errorVal.message.includes('because the target machine actively refused it')) {
|
||||
metadata.value.hostsFile = true
|
||||
}
|
||||
} else if (errorVal.message && errorVal.message.includes('User is not logged in')) {
|
||||
title.value = 'Sign in to Minecraft'
|
||||
errorType.value = 'minecraft_sign_in'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
} else if (errorVal.message && errorVal.message.includes('Move directory error:')) {
|
||||
title.value = 'Could not change app directory'
|
||||
errorType.value = 'directory_move'
|
||||
@@ -243,20 +239,6 @@ async function copyToClipboard(text) {
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="errorType === 'minecraft_sign_in'">
|
||||
<p>
|
||||
To play this instance, you must sign in through Microsoft below. If you don't have a
|
||||
Minecraft account, you can purchase the game on the
|
||||
<a href="https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc"
|
||||
>Minecraft website</a
|
||||
>.
|
||||
</p>
|
||||
<div class="cta-button">
|
||||
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
|
||||
<LogInIcon /> Sign in to Minecraft
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else-if="errorType === 'state_init'">
|
||||
<p>
|
||||
Modrinth App failed to load correctly. This may be because of a corrupted file, or
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<NewModal ref="modal" :header="formatMessage(messages.header)" max-width="544px" no-padding>
|
||||
<div class="grid grid-cols-[1fr_auto] gap-2.5 h-[154px] px-7 pt-4 pb-1 pr-9">
|
||||
<div class="flex flex-col gap-2.5 items-start justify-center h-min mt-5">
|
||||
<div class="font-semibold text-xl text-contrast">
|
||||
{{ formatMessage(messages.descriptionHeader) }}
|
||||
</div>
|
||||
<div class="text-secondary leading-6">
|
||||
{{ formatMessage(messages.description) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative h-full w-[96px] overflow-hidden mx-3">
|
||||
<div class="absolute top-0 left-0 z-0 w-full flex grow-0 flex-col items-end p-0">
|
||||
<img :src="steveImage" alt="" class="self-stretch" />
|
||||
</div>
|
||||
<div
|
||||
class="absolute left-0 bottom-0 z-10 order-1 h-6 w-[120px] shrink-0 grow-0 bg-[linear-gradient(180deg,rgba(39,41,46,0)_0%,#27292E_80%,#27292E_100%)]"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-6 px-6 pb-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<ButtonStyled>
|
||||
<a class="w-full !shadow-none" href="https://support.modrinth.com" @click="modal?.hide()">
|
||||
<MessagesSquareIcon />
|
||||
{{ formatMessage(messages.getSupport) }}
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button class="w-full !shadow-none" :disabled="loadingSignIn" @click="signIn">
|
||||
<SpinnerIcon v-if="loadingSignIn" class="animate-spin" />
|
||||
<svg
|
||||
v-else
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
|
||||
<rect x="10.75" width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
|
||||
<rect y="10.75" width="9.25" height="9.25" fill="black" fill-opacity="0.9" />
|
||||
<rect
|
||||
x="10.75"
|
||||
y="10.75"
|
||||
width="9.25"
|
||||
height="9.25"
|
||||
fill="black"
|
||||
fill-opacity="0.9"
|
||||
/>
|
||||
</svg>
|
||||
{{ formatMessage(messages.signIn) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p class="m-0 text-center text-sm text-secondary">
|
||||
{{ formatMessage(messages.dontHaveAccount) }}
|
||||
<a
|
||||
class="text-blue font-medium hover:underline"
|
||||
href="https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc"
|
||||
>
|
||||
{{ formatMessage(messages.getMinecraft) }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</NewModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { MessagesSquareIcon, SpinnerIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, defineMessages, NewModal, useVIntl } from '@modrinth/ui'
|
||||
import { inject, type Ref, ref } from 'vue'
|
||||
|
||||
import steveImage from '@/assets/steve-look-up-left.webp'
|
||||
import type AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { login as loginFlow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const accountsCard = inject('accountsCard') as Ref<InstanceType<typeof AccountsCard> | null>
|
||||
|
||||
const messages = defineMessages({
|
||||
header: {
|
||||
id: 'minecraft-required.header',
|
||||
defaultMessage: 'Minecraft required',
|
||||
},
|
||||
descriptionHeader: {
|
||||
id: 'minecraft-required.description-header',
|
||||
defaultMessage: 'Sign in to a Microsoft account',
|
||||
},
|
||||
description: {
|
||||
id: 'minecraft-required.description',
|
||||
defaultMessage:
|
||||
'You need a Microsoft account that owns Minecraft before you can launch and play.',
|
||||
},
|
||||
getSupport: {
|
||||
id: 'minecraft-required.get-support',
|
||||
defaultMessage: 'Get support',
|
||||
},
|
||||
signIn: {
|
||||
id: 'minecraft-required.sign-in',
|
||||
defaultMessage: 'Sign in to Microsoft',
|
||||
},
|
||||
dontHaveAccount: {
|
||||
id: 'minecraft-required.dont-have-account',
|
||||
defaultMessage: 'Don’t have an account?',
|
||||
},
|
||||
getMinecraft: {
|
||||
id: 'minecraft-required.get-minecraft',
|
||||
defaultMessage: 'Get Minecraft',
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>()
|
||||
const loadingSignIn = ref(false)
|
||||
|
||||
function show() {
|
||||
modal.value?.show()
|
||||
}
|
||||
|
||||
async function signIn() {
|
||||
loadingSignIn.value = true
|
||||
|
||||
try {
|
||||
const loggedIn = await loginFlow()
|
||||
if (!loggedIn) return
|
||||
|
||||
await set_default_user(loggedIn.profile.id)
|
||||
await accountsCard.value?.refreshValues()
|
||||
await trackEvent('AccountLogIn', { source: 'MinecraftRequiredModal' })
|
||||
modal.value?.hide()
|
||||
} catch (error) {
|
||||
handleSevereError(error)
|
||||
} finally {
|
||||
loadingSignIn.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
})
|
||||
</script>
|
||||
@@ -455,9 +455,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "العودة إلى الاستكشاف"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "تثبيت المحتويات الى النموذج"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "جميع الإصدارات"
|
||||
},
|
||||
|
||||
@@ -392,9 +392,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Zpět k Objevování"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Nainstalovat obsah do instance"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Všechny verze"
|
||||
},
|
||||
|
||||
@@ -296,9 +296,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Tilbage til udforsk"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Installer indhold til instance"
|
||||
},
|
||||
"app.project.versions.already-installed": {
|
||||
"message": "Allerede installeret"
|
||||
},
|
||||
|
||||
@@ -479,9 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Zurück zu Entdecken"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Inhalt in Instanz installieren"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Alle Versionen"
|
||||
},
|
||||
|
||||
@@ -479,9 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Zurück zum Entdecken"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Inhalt in Instanz installieren"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Alle Versionen"
|
||||
},
|
||||
|
||||
@@ -494,9 +494,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Back to discover"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Install content to instance"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "All versions"
|
||||
},
|
||||
@@ -1145,6 +1142,27 @@
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Sign in to Minecraft"
|
||||
},
|
||||
"minecraft-required.description": {
|
||||
"message": "You need a Microsoft account that owns Minecraft before you can launch and play."
|
||||
},
|
||||
"minecraft-required.description-header": {
|
||||
"message": "Sign in to a Microsoft account"
|
||||
},
|
||||
"minecraft-required.dont-have-account": {
|
||||
"message": "Don’t have an account?"
|
||||
},
|
||||
"minecraft-required.get-minecraft": {
|
||||
"message": "Get Minecraft"
|
||||
},
|
||||
"minecraft-required.get-support": {
|
||||
"message": "Get support"
|
||||
},
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft required"
|
||||
},
|
||||
"minecraft-required.sign-in": {
|
||||
"message": "Sign in to Microsoft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Provided by the instance"
|
||||
},
|
||||
|
||||
@@ -479,9 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Volver al explorador"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Instalar contenido a la instancia"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Todas las versiones"
|
||||
},
|
||||
|
||||
@@ -296,9 +296,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Volver a descubrir"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Instalar contenido"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Modo desarrollador activado."
|
||||
},
|
||||
|
||||
@@ -377,9 +377,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Takaisin sisällön löytöön"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Asenna sisältöä instanssiin"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Kehittäjätila käytössä."
|
||||
},
|
||||
|
||||
@@ -479,9 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Retour vers la page découvrir"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Installer du contenu à l'instance"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Toutes les versions"
|
||||
},
|
||||
|
||||
@@ -479,9 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Vissza a böngészéshez"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Tartalom telepítése a profilba"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Összes verzió"
|
||||
},
|
||||
|
||||
@@ -272,9 +272,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Kembali ke halaman pencarian"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Pasang konten ke instans"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Mode pengembang dihidupkan."
|
||||
},
|
||||
|
||||
@@ -476,9 +476,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Torna a Esplora"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Installa il contenuto nell'istanza"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Tutte le versioni"
|
||||
},
|
||||
|
||||
@@ -479,21 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "탐색으로 돌아가기"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "인스턴스에 콘텐츠 설치"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "모든 버전"
|
||||
},
|
||||
"app.project.version.download-in-browser": {
|
||||
"message": "브라우저에서 다운로드"
|
||||
},
|
||||
"app.project.version.installing": {
|
||||
"message": "설치 중"
|
||||
},
|
||||
"app.project.versions.already-installed": {
|
||||
"message": "이미 설치됨"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "개발자 모드가 활성화되었습니다."
|
||||
},
|
||||
|
||||
@@ -287,9 +287,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Kembali menemukan"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Pasang kandungan ke dalam pemasangan"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Mod pembangun didayakan."
|
||||
},
|
||||
|
||||
@@ -479,21 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Terug naar Ontdekken"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Inhoud op instantie installeren"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Alle versies"
|
||||
},
|
||||
"app.project.version.download-in-browser": {
|
||||
"message": "Downloaden in de browser"
|
||||
},
|
||||
"app.project.version.installing": {
|
||||
"message": "Wordt geïnstalleerd"
|
||||
},
|
||||
"app.project.versions.already-installed": {
|
||||
"message": "Al geïnstalleerd"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Ontwikkelaarsmodus ingeschakeld."
|
||||
},
|
||||
|
||||
@@ -476,9 +476,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Powróć do odkrywania"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Zainstaluj zasoby do instancji"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Wszystkie wersje"
|
||||
},
|
||||
|
||||
@@ -479,9 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Voltar a descobrir"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Instalar conteúdo para a instância"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Todas as versões"
|
||||
},
|
||||
|
||||
@@ -473,9 +473,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Вернуться к поиску"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Установка контента в сборку"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Все версии"
|
||||
},
|
||||
|
||||
@@ -479,9 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Nazad da otkrivanje"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Instaliraj sadržaj na instancu"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Sve verzije"
|
||||
},
|
||||
|
||||
@@ -446,9 +446,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Tillbaka till upptäck"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Installera innehåll till instans"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Alla versioner"
|
||||
},
|
||||
|
||||
@@ -272,9 +272,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "กลับไปที่หน้าค้นหา"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "ติดตั้งเนื้อหาลงสู่อินสแตนซ์"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "โหมดนักพัฒนาเปิดอยู่"
|
||||
},
|
||||
|
||||
@@ -479,9 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Keşfete geri dön"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "İçeriği kuruluma yükle"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Tüm sürümler"
|
||||
},
|
||||
|
||||
@@ -479,21 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Повернутися до дослідження"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Установити вміст до профілю"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Усі версії"
|
||||
},
|
||||
"app.project.version.download-in-browser": {
|
||||
"message": "Завантажити в браузері"
|
||||
},
|
||||
"app.project.version.installing": {
|
||||
"message": "Установлення"
|
||||
},
|
||||
"app.project.versions.already-installed": {
|
||||
"message": "Уже встановлено"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Увімкнено режим розробника."
|
||||
},
|
||||
|
||||
@@ -329,21 +329,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Quay lại trang Khám Phá"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "Cài đặt nội dung vào phiên bản"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Tất cả phiên bản"
|
||||
},
|
||||
"app.project.version.download-in-browser": {
|
||||
"message": "Tải trong trình duyệt"
|
||||
},
|
||||
"app.project.version.installing": {
|
||||
"message": "Đang tải"
|
||||
},
|
||||
"app.project.versions.already-installed": {
|
||||
"message": "Đã có sẵn"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Chế độ nhà phát triển đã được bật."
|
||||
},
|
||||
|
||||
@@ -479,9 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "返回发现页"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "将内容安装到实例中"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "所有版本"
|
||||
},
|
||||
|
||||
@@ -479,9 +479,6 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "返回瀏覽"
|
||||
},
|
||||
"app.project.install-context.install-content-to-instance": {
|
||||
"message": "將內容安裝至實例"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "所有版本"
|
||||
},
|
||||
|
||||
@@ -72,6 +72,7 @@ const debugLog = useDebugLogger('Browse')
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const themeStore = useTheming()
|
||||
const browseRouteActive = computed(() => route.path.startsWith('/browse/'))
|
||||
const serverSetupModalRef = ref<InstanceType<typeof CreationFlowModal> | null>(null)
|
||||
const serverInstallContent = createServerInstallContent({ serverSetupModalRef })
|
||||
provideServerInstallContent(serverInstallContent)
|
||||
@@ -490,6 +491,9 @@ function resetInstanceContext() {
|
||||
watch(
|
||||
() => route.params.projectType as ProjectType,
|
||||
async (newType) => {
|
||||
if (!browseRouteActive.value) {
|
||||
return
|
||||
}
|
||||
if (isSetupServerContext.value) {
|
||||
enforceSetupModpackRoute(newType)
|
||||
if (newType !== 'modpack') return
|
||||
@@ -960,6 +964,7 @@ const lockedFilterMessages = computed(() => ({
|
||||
const searchState = useBrowseSearch({
|
||||
projectType,
|
||||
tags,
|
||||
active: browseRouteActive,
|
||||
providedFilters: combinedProvidedFilters,
|
||||
search,
|
||||
persistentQueryParams: ['i', 'ai', 'shi', 'sid', 'wid', 'from'],
|
||||
@@ -1035,6 +1040,9 @@ onUnmounted(() => {
|
||||
})
|
||||
|
||||
function getProjectBrowseQuery() {
|
||||
if (!browseRouteActive.value) {
|
||||
return undefined
|
||||
}
|
||||
if (!installContext.value) return undefined
|
||||
return {
|
||||
...route.query,
|
||||
@@ -1139,7 +1147,7 @@ provideBrowseManager({
|
||||
@browse-modpacks="() => {}"
|
||||
@create="handleServerModpackFlowCreate"
|
||||
/>
|
||||
<Teleport to="#sidebar-teleport-target">
|
||||
<Teleport v-if="browseRouteActive" to="#sidebar-teleport-target">
|
||||
<BrowseSidebar />
|
||||
</Teleport>
|
||||
</div>
|
||||
|
||||
@@ -320,10 +320,6 @@ const messages = defineMessages({
|
||||
id: 'app.project.install-button.already-installed',
|
||||
defaultMessage: 'This project is already installed',
|
||||
},
|
||||
installContentToInstance: {
|
||||
id: 'app.project.install-context.install-content-to-instance',
|
||||
defaultMessage: 'Install content to instance',
|
||||
},
|
||||
switchVersion: {
|
||||
id: 'app.project.install-button.switch-version',
|
||||
defaultMessage: 'Switch version',
|
||||
@@ -450,7 +446,7 @@ const projectInstallContext = computed(() => {
|
||||
iconSrc: instance.value.icon_path ? convertFileSrc(instance.value.icon_path) : null,
|
||||
backUrl: projectBrowseBackUrl.value,
|
||||
backLabel: formatMessage(messages.backToBrowse),
|
||||
heading: formatMessage(messages.installContentToInstance),
|
||||
heading: formatMessage(commonMessages.installingContentLabel),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ export const useError = defineStore('errorsStore', {
|
||||
state: () => ({
|
||||
errorModal: null,
|
||||
minecraftAuthErrorModal: null,
|
||||
minecraftRequiredModal: null,
|
||||
}),
|
||||
actions: {
|
||||
setErrorModal(ref) {
|
||||
@@ -14,7 +15,19 @@ export const useError = defineStore('errorsStore', {
|
||||
setMinecraftAuthErrorModal(ref) {
|
||||
this.minecraftAuthErrorModal = ref
|
||||
},
|
||||
setMinecraftRequiredModal(ref) {
|
||||
this.minecraftRequiredModal = ref
|
||||
},
|
||||
showError(error, context, closable = true, source = null) {
|
||||
const errorMessage = error.message?.toLowerCase()
|
||||
if (
|
||||
(errorMessage?.includes('user is not logged in') ||
|
||||
errorMessage?.includes('cannot play instance since minecraft is required')) &&
|
||||
this.minecraftRequiredModal
|
||||
) {
|
||||
this.minecraftRequiredModal.show()
|
||||
return
|
||||
}
|
||||
if (
|
||||
error.message &&
|
||||
(error.message.includes('Minecraft authentication error:') ||
|
||||
|
||||
@@ -582,8 +582,6 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
}
|
||||
})?;
|
||||
|
||||
webview.open_devtools();
|
||||
|
||||
Some(webview)
|
||||
} else {
|
||||
None
|
||||
|
||||
+11
-8
@@ -775,19 +775,22 @@ function suppressUpcomingChartClick() {
|
||||
}, 350)
|
||||
}
|
||||
|
||||
function clearUnpinnedTouchInteraction(ignoreClick: boolean) {
|
||||
function clearUnpinnedTouchInteraction(ignoreClick: boolean, pinHover = false) {
|
||||
if (props.pinnedSliceIndex !== null) return
|
||||
|
||||
if (ignoreClick) {
|
||||
suppressUpcomingChartClick()
|
||||
}
|
||||
if (pinHover) {
|
||||
emit('touch-drag')
|
||||
}
|
||||
|
||||
clearChartActiveState()
|
||||
updateChartWithoutGeometry()
|
||||
|
||||
if (ignoreClick) {
|
||||
suppressUpcomingChartClick()
|
||||
emit('touch-drag')
|
||||
return
|
||||
if (!pinHover) {
|
||||
emit('hover', { visible: false, x: 0, y: 0, sliceIndex: null })
|
||||
}
|
||||
|
||||
emit('hover', { visible: false, x: 0, y: 0, sliceIndex: null })
|
||||
}
|
||||
|
||||
function handleCanvasClickCapture(event: MouseEvent) {
|
||||
@@ -918,7 +921,7 @@ function handleRangePointerEnd(event: PointerEvent) {
|
||||
|
||||
if (isRangeSelecting && rangeSelectPointerType === 'touch') {
|
||||
event.preventDefault()
|
||||
clearUnpinnedTouchInteraction(true)
|
||||
clearUnpinnedTouchInteraction(true, true)
|
||||
} else if (isRangeSelecting && startSliceIndex !== null && endSliceIndex !== null) {
|
||||
event.preventDefault()
|
||||
emit('range-select', { startSliceIndex, endSliceIndex })
|
||||
|
||||
+2
@@ -110,6 +110,8 @@ export function useAnalyticsChartInteractions({
|
||||
|
||||
function onTouchDragEnd() {
|
||||
ignoreUpcomingChartClick()
|
||||
if (!hoverState.visible || hoverState.sliceIndex === null) return
|
||||
isHoverPinned.value = true
|
||||
}
|
||||
|
||||
function onChartGeometry(payload: AnalyticsChartGeometryPayload) {
|
||||
|
||||
@@ -213,6 +213,10 @@ export const analyticsMessages = defineMessages({
|
||||
id: 'analytics.filter.game-version-type.all',
|
||||
defaultMessage: 'All',
|
||||
},
|
||||
dependentProjectSearchFailedTitle: {
|
||||
id: 'analytics.filter.dependent-project-search-failed-title',
|
||||
defaultMessage: 'Dependent projects failed to load',
|
||||
},
|
||||
})
|
||||
|
||||
export const analyticsStatMessages = defineMessages({
|
||||
|
||||
@@ -492,7 +492,7 @@ watch(dependentProjectSearchError, (error) => {
|
||||
if (!error) return
|
||||
|
||||
addNotification({
|
||||
title: 'Dependent projects failed to load',
|
||||
title: formatMessage(analyticsMessages.dependentProjectSearchFailedTitle),
|
||||
text: getDependentProjectSearchErrorMessage(error),
|
||||
type: 'error',
|
||||
})
|
||||
|
||||
@@ -40,6 +40,30 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
'NOT AN OFFICIAL MINECRAFT SERVICE. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.',
|
||||
},
|
||||
basedOnLabel: {
|
||||
id: 'layout.footer.based-on-label',
|
||||
defaultMessage: 'Based on',
|
||||
},
|
||||
unknownCommit: {
|
||||
id: 'layout.footer.unknown-commit',
|
||||
defaultMessage: 'unknown',
|
||||
},
|
||||
developerModeActivatedTitle: {
|
||||
id: 'layout.footer.developer-mode-activated.title',
|
||||
defaultMessage: 'Developer mode activated',
|
||||
},
|
||||
developerModeActivatedText: {
|
||||
id: 'layout.footer.developer-mode-activated.text',
|
||||
defaultMessage: 'Developer mode has been enabled',
|
||||
},
|
||||
developerModeDeactivatedTitle: {
|
||||
id: 'layout.footer.developer-mode-deactivated.title',
|
||||
defaultMessage: 'Developer mode deactivated',
|
||||
},
|
||||
developerModeDeactivatedText: {
|
||||
id: 'layout.footer.developer-mode-deactivated.text',
|
||||
defaultMessage: 'Developer mode has been disabled',
|
||||
},
|
||||
})
|
||||
|
||||
const socialLinks: {
|
||||
@@ -215,14 +239,14 @@ function developerModeIncrement() {
|
||||
saveFeatureFlags()
|
||||
if (flags.value.developerMode) {
|
||||
addNotification({
|
||||
title: 'Developer mode activated',
|
||||
text: 'Developer mode has been enabled',
|
||||
title: formatMessage(messages.developerModeActivatedTitle),
|
||||
text: formatMessage(messages.developerModeActivatedText),
|
||||
type: 'success',
|
||||
})
|
||||
} else {
|
||||
addNotification({
|
||||
title: 'Developer mode deactivated',
|
||||
text: 'Developer mode has been disabled',
|
||||
title: formatMessage(messages.developerModeDeactivatedTitle),
|
||||
text: formatMessage(messages.developerModeDeactivatedText),
|
||||
type: 'success',
|
||||
})
|
||||
}
|
||||
@@ -320,7 +344,7 @@ function developerModeIncrement() {
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="flags.developerMode" class="m-0 text-sm text-secondary">
|
||||
Based on
|
||||
{{ formatMessage(messages.basedOnLabel) }}
|
||||
<a
|
||||
v-if="config.public.owner && config.public.branch"
|
||||
class="hover:underline"
|
||||
@@ -330,7 +354,9 @@ function developerModeIncrement() {
|
||||
{{ config.public.owner }}/{{ config.public.branch }}
|
||||
</a>
|
||||
@
|
||||
<span v-if="config.public.hash === 'unknown'">unknown</span>
|
||||
<span v-if="config.public.hash === 'unknown'">{{
|
||||
formatMessage(messages.unknownCommit)
|
||||
}}</span>
|
||||
<AutoLink
|
||||
v-else
|
||||
class="text-link"
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<NewModal ref="modalOpen" header="Transfer projects" no-padding width="60rem">
|
||||
<NewModal
|
||||
ref="modalOpen"
|
||||
:header="formatMessage(messages.transferProjectsTitle)"
|
||||
no-padding
|
||||
width="60rem"
|
||||
>
|
||||
<div class="max-h-[70vh] overflow-y-auto">
|
||||
<Table
|
||||
class="!rounded-none !border-0"
|
||||
@@ -12,7 +17,7 @@
|
||||
>
|
||||
<template #empty-state>
|
||||
<div class="flex h-48 items-center justify-center text-secondary">
|
||||
No projects available to transfer.
|
||||
{{ formatMessage(messages.noProjectsAvailable) }}
|
||||
</div>
|
||||
</template>
|
||||
<template #header-select>
|
||||
@@ -38,7 +43,7 @@
|
||||
<img
|
||||
v-if="project.icon_url"
|
||||
:src="project.icon_url"
|
||||
:alt="'Icon for ' + project.title"
|
||||
:alt="formatMessage(messages.projectIconAlt, { name: project.title })"
|
||||
class="h-full w-full rounded object-cover"
|
||||
/>
|
||||
<BoxIcon v-else class="h-full w-full" />
|
||||
@@ -69,8 +74,11 @@
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="selectedProjects.length === 0" @click="submitTransfer()">
|
||||
<TransferIcon />
|
||||
Transfer {{ selectedProjects.length }}
|
||||
{{ selectedProjects.length === 1 ? 'project' : 'projects' }}
|
||||
{{
|
||||
formatMessage(messages.transferSelectedProjects, {
|
||||
count: selectedProjects.length,
|
||||
})
|
||||
}}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -79,7 +87,7 @@
|
||||
<ButtonStyled>
|
||||
<button @click="show($event)">
|
||||
<TransferIcon />
|
||||
<span>Transfer projects</span>
|
||||
<span>{{ formatMessage(messages.transferProjectsTitle) }}</span>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -92,6 +100,7 @@ import {
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
CopyCode,
|
||||
defineMessages,
|
||||
NewModal,
|
||||
Table,
|
||||
useVIntl,
|
||||
@@ -112,10 +121,41 @@ const props = defineProps({
|
||||
const emit = defineEmits(['submit'])
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
transferProjectsTitle: {
|
||||
id: 'organization.project-transfer.title',
|
||||
defaultMessage: 'Transfer projects',
|
||||
},
|
||||
noProjectsAvailable: {
|
||||
id: 'organization.project-transfer.no-projects-available',
|
||||
defaultMessage: 'No projects available to transfer.',
|
||||
},
|
||||
projectIconAlt: {
|
||||
id: 'organization.project-transfer.project-icon-alt',
|
||||
defaultMessage: 'Icon for {name}',
|
||||
},
|
||||
transferSelectedProjects: {
|
||||
id: 'organization.project-transfer.transfer-selected-projects',
|
||||
defaultMessage: 'Transfer {count, plural, one {# project} other {# projects}}',
|
||||
},
|
||||
nameColumn: {
|
||||
id: 'organization.project-transfer.name-column',
|
||||
defaultMessage: 'Name',
|
||||
},
|
||||
idColumn: {
|
||||
id: 'organization.project-transfer.id-column',
|
||||
defaultMessage: 'ID',
|
||||
},
|
||||
typeColumn: {
|
||||
id: 'organization.project-transfer.type-column',
|
||||
defaultMessage: 'Type',
|
||||
},
|
||||
})
|
||||
|
||||
const modalOpen = ref(null)
|
||||
const selectedProjectIds = ref([])
|
||||
|
||||
const projectTableColumns = [
|
||||
const projectTableColumns = computed(() => [
|
||||
{
|
||||
key: 'select',
|
||||
width: '3rem',
|
||||
@@ -124,21 +164,21 @@ const projectTableColumns = [
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Name',
|
||||
label: formatMessage(messages.nameColumn),
|
||||
width: '22rem',
|
||||
},
|
||||
{
|
||||
key: 'id',
|
||||
label: 'ID',
|
||||
label: formatMessage(messages.idColumn),
|
||||
width: '13rem',
|
||||
cellClass: '!overflow-visible',
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
label: 'Type',
|
||||
label: formatMessage(messages.typeColumn),
|
||||
width: '10rem',
|
||||
},
|
||||
]
|
||||
])
|
||||
|
||||
const transferableProjects = computed(() =>
|
||||
props.projects.filter((project) => !isProjectTransferDisabled(project)),
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
@update:selection="updateProjectDownloadSelection"
|
||||
@download="onDownload"
|
||||
/>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-4 empty:hidden">
|
||||
<DownloadDependencies @download="onDownload" />
|
||||
</div>
|
||||
<ServersPromo
|
||||
|
||||
@@ -11,19 +11,21 @@
|
||||
v-if="handlingNewFiles || !(filesToAdd.length || draftVersion.existing_files?.length)"
|
||||
>
|
||||
<DropzoneFileInput
|
||||
aria-label="Upload file"
|
||||
:aria-label="formatMessage(messages.uploadFileAriaLabel)"
|
||||
multiple
|
||||
:accept="acceptFileFromProjectType(projectV2.project_type)"
|
||||
:max-size="524288000"
|
||||
primary-prompt="Upload primary and supporting files"
|
||||
secondary-prompt="Drag and drop files or click to browse"
|
||||
:primary-prompt="formatMessage(messages.uploadPrimaryPrompt)"
|
||||
:secondary-prompt="formatMessage(messages.uploadSecondaryPrompt)"
|
||||
@change="handleNewFiles"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-base font-semibold text-contrast">Primary file</span>
|
||||
<span class="text-base font-semibold text-contrast">{{
|
||||
formatMessage(messages.primaryFileLabel)
|
||||
}}</span>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<VersionFileRow
|
||||
v-if="primaryFile"
|
||||
@@ -36,7 +38,7 @@
|
||||
/>
|
||||
</div>
|
||||
<span>
|
||||
The primary file is the default file a user downloads when installing the project.
|
||||
{{ formatMessage(messages.primaryFileDescription) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -46,16 +48,18 @@
|
||||
{{ formatMessage(messages.addFilesAdmonition) }}
|
||||
</Admonition>
|
||||
|
||||
<span class="text-base font-semibold text-contrast">Supplementary files</span>
|
||||
<span class="text-base font-semibold text-contrast">{{
|
||||
formatMessage(messages.supplementaryFilesLabel)
|
||||
}}</span>
|
||||
|
||||
<DropzoneFileInput
|
||||
aria-label="Upload additional file"
|
||||
:aria-label="formatMessage(messages.uploadAdditionalFileAriaLabel)"
|
||||
multiple
|
||||
:accept="acceptFileFromProjectType(projectV2.project_type)"
|
||||
:max-size="524288000"
|
||||
size="small"
|
||||
:primary-prompt="null"
|
||||
secondary-prompt="Drag and drop files or click to browse"
|
||||
:secondary-prompt="formatMessage(messages.uploadSecondaryPrompt)"
|
||||
@change="handleNewFiles"
|
||||
/>
|
||||
|
||||
@@ -84,8 +88,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
You can optionally add supplementary files such as source code, documentation, or required
|
||||
resource packs.
|
||||
{{ formatMessage(messages.supplementaryFilesDescription) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -124,11 +127,11 @@ const {
|
||||
handleNewFiles,
|
||||
} = injectManageVersionContext()
|
||||
|
||||
const editTabs: TabsTab[] = [
|
||||
{ label: 'Metadata', value: 'metadata' },
|
||||
{ label: 'Details', value: 'add-details' },
|
||||
{ label: 'Files', value: 'add-files' },
|
||||
]
|
||||
const editTabs = computed<TabsTab[]>(() => [
|
||||
{ label: formatMessage(messages.metadataTab), value: 'metadata' },
|
||||
{ label: formatMessage(messages.detailsTab), value: 'add-details' },
|
||||
{ label: formatMessage(messages.filesTab), value: 'add-files' },
|
||||
])
|
||||
|
||||
function setEditTab(tab: TabsTab) {
|
||||
modal.value?.setStage(tab.value)
|
||||
@@ -171,5 +174,51 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
'Supplementary files are for supporting resources like source code, not for alternative versions or variants.',
|
||||
},
|
||||
uploadFileAriaLabel: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.upload-file-aria-label',
|
||||
defaultMessage: 'Upload file',
|
||||
},
|
||||
uploadAdditionalFileAriaLabel: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.upload-additional-file-aria-label',
|
||||
defaultMessage: 'Upload additional file',
|
||||
},
|
||||
uploadPrimaryPrompt: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.upload-primary-prompt',
|
||||
defaultMessage: 'Upload primary and supporting files',
|
||||
},
|
||||
uploadSecondaryPrompt: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.upload-secondary-prompt',
|
||||
defaultMessage: 'Drag and drop files or click to browse',
|
||||
},
|
||||
primaryFileLabel: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.primary-file-label',
|
||||
defaultMessage: 'Primary file',
|
||||
},
|
||||
primaryFileDescription: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.primary-file-description',
|
||||
defaultMessage:
|
||||
'The primary file is the default file a user downloads when installing the project.',
|
||||
},
|
||||
supplementaryFilesLabel: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.supplementary-files-label',
|
||||
defaultMessage: 'Supplementary files',
|
||||
},
|
||||
supplementaryFilesDescription: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.supplementary-files-description',
|
||||
defaultMessage:
|
||||
'You can optionally add supplementary files such as source code, documentation, or required resource packs.',
|
||||
},
|
||||
metadataTab: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.metadata-tab',
|
||||
defaultMessage: 'Metadata',
|
||||
},
|
||||
detailsTab: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.details-tab',
|
||||
defaultMessage: 'Details',
|
||||
},
|
||||
filesTab: {
|
||||
id: 'create-project-version.create-modal.stage.add-files.files-tab',
|
||||
defaultMessage: 'Files',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="flex w-full flex-col gap-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast">
|
||||
Version type <span class="text-red">*</span>
|
||||
{{ formatMessage(messages.versionType) }} <span class="text-red">*</span>
|
||||
</span>
|
||||
<Chips
|
||||
v-model="draftVersion.version_type"
|
||||
@@ -22,31 +22,33 @@
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast">
|
||||
Version number <span class="text-red">*</span>
|
||||
{{ formatMessage(messages.versionNumber) }} <span class="text-red">*</span>
|
||||
</span>
|
||||
<StyledInput
|
||||
id="version-number"
|
||||
v-model="draftVersion.version_number"
|
||||
:disabled="isUploading"
|
||||
placeholder="Enter version number, e.g. 1.2.3-alpha.1"
|
||||
:placeholder="formatMessage(messages.versionNumberPlaceholder)"
|
||||
autocomplete="off"
|
||||
:maxlength="32"
|
||||
/>
|
||||
<span> The version number differentiates this specific version from others. </span>
|
||||
<span>{{ formatMessage(messages.versionNumberDescription) }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast"> Version subtitle </span>
|
||||
<span class="font-semibold text-contrast">{{ formatMessage(messages.versionSubtitle) }}</span>
|
||||
<StyledInput
|
||||
id="version-number"
|
||||
v-model="draftVersion.name"
|
||||
placeholder="Enter subtitle..."
|
||||
:placeholder="formatMessage(messages.versionSubtitlePlaceholder)"
|
||||
autocomplete="off"
|
||||
:maxlength="256"
|
||||
:disabled="isUploading"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast"> Version changelog </span>
|
||||
<span class="font-semibold text-contrast">{{
|
||||
formatMessage(messages.versionChangelog)
|
||||
}}</span>
|
||||
|
||||
<div class="w-full">
|
||||
<MarkdownEditor
|
||||
@@ -61,18 +63,70 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Chips, MarkdownEditor, StyledInput, Tabs, type TabsTab } from '@modrinth/ui'
|
||||
import {
|
||||
Chips,
|
||||
defineMessages,
|
||||
MarkdownEditor,
|
||||
StyledInput,
|
||||
Tabs,
|
||||
type TabsTab,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
const { draftVersion, isUploading, editingVersion, modal } = injectManageVersionContext()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const editTabs: TabsTab[] = [
|
||||
{ label: 'Metadata', value: 'metadata' },
|
||||
{ label: 'Details', value: 'add-details' },
|
||||
{ label: 'Files', value: 'add-files' },
|
||||
]
|
||||
const messages = defineMessages({
|
||||
versionType: {
|
||||
id: 'create-project-version.create-modal.stage.details.version-type',
|
||||
defaultMessage: 'Version type',
|
||||
},
|
||||
versionNumber: {
|
||||
id: 'create-project-version.create-modal.stage.details.version-number',
|
||||
defaultMessage: 'Version number',
|
||||
},
|
||||
versionNumberPlaceholder: {
|
||||
id: 'create-project-version.create-modal.stage.details.version-number-placeholder',
|
||||
defaultMessage: 'Enter version number, e.g. 1.2.3-alpha.1',
|
||||
},
|
||||
versionNumberDescription: {
|
||||
id: 'create-project-version.create-modal.stage.details.version-number-description',
|
||||
defaultMessage: 'The version number differentiates this specific version from others.',
|
||||
},
|
||||
versionSubtitle: {
|
||||
id: 'create-project-version.create-modal.stage.details.version-subtitle',
|
||||
defaultMessage: 'Version subtitle',
|
||||
},
|
||||
versionSubtitlePlaceholder: {
|
||||
id: 'create-project-version.create-modal.stage.details.version-subtitle-placeholder',
|
||||
defaultMessage: 'Enter subtitle...',
|
||||
},
|
||||
versionChangelog: {
|
||||
id: 'create-project-version.create-modal.stage.details.version-changelog',
|
||||
defaultMessage: 'Version changelog',
|
||||
},
|
||||
metadataTab: {
|
||||
id: 'create-project-version.create-modal.stage.details.metadata-tab',
|
||||
defaultMessage: 'Metadata',
|
||||
},
|
||||
detailsTab: {
|
||||
id: 'create-project-version.create-modal.stage.details.details-tab',
|
||||
defaultMessage: 'Details',
|
||||
},
|
||||
filesTab: {
|
||||
id: 'create-project-version.create-modal.stage.details.files-tab',
|
||||
defaultMessage: 'Files',
|
||||
},
|
||||
})
|
||||
|
||||
const editTabs = computed<TabsTab[]>(() => [
|
||||
{ label: formatMessage(messages.metadataTab), value: 'metadata' },
|
||||
{ label: formatMessage(messages.detailsTab), value: 'add-details' },
|
||||
{ label: formatMessage(messages.filesTab), value: 'add-files' },
|
||||
])
|
||||
|
||||
function setEditTab(tab: TabsTab) {
|
||||
modal.value?.setStage(tab.value)
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
<div class="flex flex-col gap-6">
|
||||
<div v-if="!editingVersion" class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast"> Uploaded files </span>
|
||||
<span class="font-semibold text-contrast">{{ formatMessage(messages.uploadedFiles) }}</span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="editFiles">
|
||||
<EditIcon />
|
||||
Edit
|
||||
{{ formatMessage(messages.editButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -43,23 +43,27 @@
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ usingDetectedLoaders ? 'Detected loaders' : 'Loaders' }}
|
||||
{{
|
||||
usingDetectedLoaders
|
||||
? formatMessage(messages.detectedLoaders)
|
||||
: formatMessage(messages.loaders)
|
||||
}}
|
||||
</span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button
|
||||
v-tooltip="
|
||||
isModpack
|
||||
? 'Modpack loaders cannot be edited'
|
||||
? formatMessage(messages.modpackLoadersTooltip)
|
||||
: isResourcePack
|
||||
? 'Resource pack loaders cannot be edited'
|
||||
? formatMessage(messages.resourcePackLoadersTooltip)
|
||||
: undefined
|
||||
"
|
||||
:disabled="isModpack || isResourcePack"
|
||||
@click="editLoaders"
|
||||
>
|
||||
<EditIcon />
|
||||
Edit
|
||||
{{ formatMessage(messages.editButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -88,9 +92,11 @@
|
||||
v-if="!draftVersionLoaders.length && projectType === 'modpack'"
|
||||
class="border !border-solid border-surface-5 hover:no-underline"
|
||||
>
|
||||
No mod loader
|
||||
{{ formatMessage(messages.noModLoader) }}
|
||||
</TagItem>
|
||||
<span v-else-if="!draftVersionLoaders.length">No loaders selected.</span>
|
||||
<span v-else-if="!draftVersionLoaders.length">{{
|
||||
formatMessage(messages.noLoadersSelected)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,17 +104,21 @@
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ usingDetectedVersions ? 'Detected versions' : 'Versions' }}
|
||||
{{
|
||||
usingDetectedVersions
|
||||
? formatMessage(messages.detectedVersions)
|
||||
: formatMessage(messages.versions)
|
||||
}}
|
||||
</span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button
|
||||
v-tooltip="isModpack ? 'Modpack versions cannot be edited' : undefined"
|
||||
v-tooltip="isModpack ? formatMessage(messages.modpackVersionsTooltip) : undefined"
|
||||
:disabled="isModpack"
|
||||
@click="editVersions"
|
||||
>
|
||||
<EditIcon />
|
||||
Edit
|
||||
{{ formatMessage(messages.editButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -125,7 +135,9 @@
|
||||
{{ version }}
|
||||
</TagItem>
|
||||
|
||||
<span v-if="!draftVersion.game_versions.length">No versions selected.</span>
|
||||
<span v-if="!draftVersion.game_versions.length">{{
|
||||
formatMessage(messages.noVersionsSelected)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,14 +146,16 @@
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-semibold text-contrast"> Environment </span>
|
||||
<UnknownIcon v-tooltip="'Pre-filled from a previous similar version'" />
|
||||
<span class="font-semibold text-contrast">{{
|
||||
formatMessage(messages.environment)
|
||||
}}</span>
|
||||
<UnknownIcon v-tooltip="formatMessage(messages.prefilledEnvironmentTooltip)" />
|
||||
</div>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="editEnvironment">
|
||||
<EditIcon />
|
||||
Edit
|
||||
{{ formatMessage(messages.editButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -154,7 +168,9 @@
|
||||
<div class="text-sm font-medium">{{ environmentCopy.description }}</div>
|
||||
</div>
|
||||
|
||||
<span v-else class="text-sm font-medium">No environment has been set.</span>
|
||||
<span v-else class="text-sm font-medium">{{
|
||||
formatMessage(messages.noEnvironmentSet)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -163,12 +179,14 @@
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-semibold text-contrast"> Dependencies </span>
|
||||
<span class="font-semibold text-contrast">{{
|
||||
formatMessage(messages.dependencies)
|
||||
}}</span>
|
||||
|
||||
<ButtonStyled type="transparent" size="standard">
|
||||
<button @click="addDependency">
|
||||
<PlusIcon />
|
||||
Add dependency
|
||||
{{ formatMessage(messages.addDependency) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -177,13 +195,15 @@
|
||||
<DependenciesList />
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-1.5 gap-y-4 rounded-xl bg-surface-2 p-3 py-4">
|
||||
<span class="text-sm font-medium">No dependencies added.</span>
|
||||
<span class="text-sm font-medium">{{
|
||||
formatMessage(messages.noDependenciesAdded)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="visibleSuggestedDependencies.length" class="flex flex-col gap-2.5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-medium"> Suggested </span>
|
||||
<span class="font-medium">{{ formatMessage(messages.suggested) }}</span>
|
||||
</div>
|
||||
<SuggestedDependencies @on-add-suggestion="handleAddSuggestedDependency" />
|
||||
</div>
|
||||
@@ -231,11 +251,11 @@ const { projectV2 } = injectProjectPageContext()
|
||||
const generatedState = useGeneratedState()
|
||||
const loaders = computed(() => generatedState.value.loaders)
|
||||
|
||||
const editTabs: TabsTab[] = [
|
||||
{ label: 'Metadata', value: 'metadata' },
|
||||
{ label: 'Details', value: 'add-details' },
|
||||
{ label: 'Files', value: 'add-files' },
|
||||
]
|
||||
const editTabs = computed<TabsTab[]>(() => [
|
||||
{ label: formatMessage(messages.metadataTab), value: 'metadata' },
|
||||
{ label: formatMessage(messages.detailsTab), value: 'add-details' },
|
||||
{ label: formatMessage(messages.filesTab), value: 'add-files' },
|
||||
])
|
||||
|
||||
function setEditTab(tab: TabsTab) {
|
||||
modal.value?.setStage(tab.value)
|
||||
@@ -341,6 +361,97 @@ const supplementaryExistingFiles = computed(() => {
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
uploadedFiles: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.uploaded-files',
|
||||
defaultMessage: 'Uploaded files',
|
||||
},
|
||||
editButton: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.edit-button',
|
||||
defaultMessage: 'Edit',
|
||||
},
|
||||
detectedLoaders: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.detected-loaders',
|
||||
defaultMessage: 'Detected loaders',
|
||||
},
|
||||
loaders: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.loaders',
|
||||
defaultMessage: 'Loaders',
|
||||
},
|
||||
modpackLoadersTooltip: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.modpack-loaders-tooltip',
|
||||
defaultMessage: 'Modpack loaders cannot be edited',
|
||||
},
|
||||
resourcePackLoadersTooltip: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.resource-pack-loaders-tooltip',
|
||||
defaultMessage: 'Resource pack loaders cannot be edited',
|
||||
},
|
||||
noModLoader: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.no-mod-loader',
|
||||
defaultMessage: 'No mod loader',
|
||||
},
|
||||
noLoadersSelected: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.no-loaders-selected',
|
||||
defaultMessage: 'No loaders selected.',
|
||||
},
|
||||
detectedVersions: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.detected-versions',
|
||||
defaultMessage: 'Detected versions',
|
||||
},
|
||||
versions: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.versions',
|
||||
defaultMessage: 'Versions',
|
||||
},
|
||||
modpackVersionsTooltip: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.modpack-versions-tooltip',
|
||||
defaultMessage: 'Modpack versions cannot be edited',
|
||||
},
|
||||
noVersionsSelected: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.no-versions-selected',
|
||||
defaultMessage: 'No versions selected.',
|
||||
},
|
||||
environment: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.environment',
|
||||
defaultMessage: 'Environment',
|
||||
},
|
||||
prefilledEnvironmentTooltip: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.prefilled-environment-tooltip',
|
||||
defaultMessage: 'Pre-filled from a previous similar version',
|
||||
},
|
||||
noEnvironmentSet: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.no-environment-set',
|
||||
defaultMessage: 'No environment has been set.',
|
||||
},
|
||||
dependencies: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.dependencies',
|
||||
defaultMessage: 'Dependencies',
|
||||
},
|
||||
addDependency: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.add-dependency',
|
||||
defaultMessage: 'Add dependency',
|
||||
},
|
||||
noDependenciesAdded: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.no-dependencies-added',
|
||||
defaultMessage: 'No dependencies added.',
|
||||
},
|
||||
suggested: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.suggested',
|
||||
defaultMessage: 'Suggested',
|
||||
},
|
||||
metadataTab: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.metadata-tab',
|
||||
defaultMessage: 'Metadata',
|
||||
},
|
||||
detailsTab: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.details-tab',
|
||||
defaultMessage: 'Details',
|
||||
},
|
||||
filesTab: {
|
||||
id: 'create-project-version.create-modal.stage.metadata.files-tab',
|
||||
defaultMessage: 'Files',
|
||||
},
|
||||
})
|
||||
|
||||
const noEnvironmentMessage = defineMessages({
|
||||
title: {
|
||||
id: 'version.environment.none.title',
|
||||
|
||||
@@ -246,6 +246,18 @@ const messages = defineMessages({
|
||||
id: 'create.project.missing-fields-tooltip',
|
||||
defaultMessage: 'Missing fields: {fields}',
|
||||
},
|
||||
unknownUser: {
|
||||
id: 'create.project.unknown-user',
|
||||
defaultMessage: 'Unknown user',
|
||||
},
|
||||
userAvatarAlt: {
|
||||
id: 'create.project.user-avatar-alt',
|
||||
defaultMessage: 'User Avatar',
|
||||
},
|
||||
organizationIconAlt: {
|
||||
id: 'create.project.organization-icon-alt',
|
||||
defaultMessage: '{name} Icon',
|
||||
},
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -318,7 +330,7 @@ const cancel = () => {
|
||||
|
||||
const userOption = computed(() => ({
|
||||
value: 'self',
|
||||
label: auth.value.user?.username || 'Unknown user',
|
||||
label: auth.value.user?.username || formatMessage(messages.unknownUser),
|
||||
icon: auth.value.user?.avatar_url
|
||||
? markRaw(
|
||||
defineAsyncComponent(() =>
|
||||
@@ -326,7 +338,7 @@ const userOption = computed(() => ({
|
||||
setup: () => () =>
|
||||
h('img', {
|
||||
src: auth.value.user?.avatar_url,
|
||||
alt: 'User Avatar',
|
||||
alt: formatMessage(messages.userAvatarAlt),
|
||||
class: 'h-5 w-5 rounded-full',
|
||||
}),
|
||||
}),
|
||||
@@ -357,7 +369,7 @@ async function fetchOrganizations() {
|
||||
setup: () => () =>
|
||||
h('img', {
|
||||
src: org.icon_url,
|
||||
alt: `${org.name} Icon`,
|
||||
alt: formatMessage(messages.organizationIconAlt, { name: org.name }),
|
||||
class: 'h-5 w-5 rounded',
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -137,7 +137,9 @@
|
||||
</div>
|
||||
<div class="flex w-full flex-row justify-stretch gap-2">
|
||||
<ButtonStyled>
|
||||
<button class="w-full text-contrast" @click="handleClose">{{ closeButtonText }}</button>
|
||||
<button class="w-full text-contrast" @click="handleClose">
|
||||
{{ props.closeButtonText ?? formatMessage(messages.closeButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="green">
|
||||
<button class="w-full text-contrast" @click="downloadTaxForm">
|
||||
@@ -182,7 +184,7 @@ const props = withDefaults(
|
||||
emitSuccessOnClose?: boolean
|
||||
}>(),
|
||||
{
|
||||
closeButtonText: 'Close',
|
||||
closeButtonText: undefined,
|
||||
emitSuccessOnClose: true,
|
||||
},
|
||||
)
|
||||
@@ -268,6 +270,18 @@ const messages = defineMessages({
|
||||
id: 'dashboard.creator-tax-form-modal.confirmation.download-button',
|
||||
defaultMessage: 'Download {formType}',
|
||||
},
|
||||
closeButton: {
|
||||
id: 'dashboard.creator-tax-form-modal.close-button',
|
||||
defaultMessage: 'Close',
|
||||
},
|
||||
incompleteTitle: {
|
||||
id: 'dashboard.creator-tax-form-modal.incomplete.title',
|
||||
defaultMessage: 'Tax form incomplete',
|
||||
},
|
||||
incompleteText: {
|
||||
id: 'dashboard.creator-tax-form-modal.incomplete.text',
|
||||
defaultMessage: 'You have not completed the tax form. Please try again.',
|
||||
},
|
||||
})
|
||||
|
||||
const isUSCitizen = ref<'yes' | 'no' | null>(null)
|
||||
@@ -371,8 +385,8 @@ async function continueForm() {
|
||||
}
|
||||
|
||||
addNotification({
|
||||
title: 'Tax form incomplete',
|
||||
text: 'You have not completed the tax form. Please try again.',
|
||||
title: formatMessage(messages.incompleteTitle),
|
||||
text: formatMessage(messages.incompleteText),
|
||||
type: 'warning',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<MuralpayDetailsStage v-else-if="currentStage === 'muralpay-details'" />
|
||||
<LegacyPaypalDetailsStage v-else-if="currentStage === 'paypal-details'" />
|
||||
<CompletionStage v-else-if="currentStage === 'completion'" />
|
||||
<div v-else>Something went wrong</div>
|
||||
<div v-else>{{ formatMessage(commonMessages.errorNotificationTitle) }}</div>
|
||||
</div>
|
||||
<template #actions>
|
||||
<div v-if="currentStage === 'completion'" class="mt-4 flex w-full gap-3">
|
||||
@@ -96,7 +96,7 @@
|
||||
</NewModal>
|
||||
<CreatorTaxFormModal
|
||||
ref="taxFormModal"
|
||||
close-button-text="Continue"
|
||||
:close-button-text="formatMessage(commonMessages.continueButton)"
|
||||
@success="onTaxFormSuccess"
|
||||
@cancelled="onTaxFormCancelled"
|
||||
/>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
v-model="formData.dateOfBirth"
|
||||
:max-date="maxDate"
|
||||
autocomplete="bday"
|
||||
placeholder="Select date of birth"
|
||||
:placeholder="formatMessage(messages.dateOfBirthPlaceholder)"
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
</div>
|
||||
@@ -162,7 +162,7 @@
|
||||
:options="subdivisionOptions"
|
||||
:placeholder="formatMessage(formFieldPlaceholders.statePlaceholder)"
|
||||
searchable
|
||||
search-placeholder="Search subdivisions..."
|
||||
:search-placeholder="formatMessage(messages.searchSubdivisionsPlaceholder)"
|
||||
/>
|
||||
<StyledInput
|
||||
v-else
|
||||
@@ -201,7 +201,7 @@
|
||||
:options="countryOptions"
|
||||
:placeholder="formatMessage(formFieldPlaceholders.countryPlaceholder)"
|
||||
searchable
|
||||
search-placeholder="Search countries..."
|
||||
:search-placeholder="formatMessage(messages.searchCountriesPlaceholder)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -361,5 +361,17 @@ const messages = defineMessages({
|
||||
id: 'dashboard.creator-withdraw-modal.kyc.business-entity',
|
||||
defaultMessage: 'Business entity',
|
||||
},
|
||||
dateOfBirthPlaceholder: {
|
||||
id: 'dashboard.creator-withdraw-modal.kyc.date-of-birth-placeholder',
|
||||
defaultMessage: 'Select date of birth',
|
||||
},
|
||||
searchSubdivisionsPlaceholder: {
|
||||
id: 'dashboard.creator-withdraw-modal.kyc.search-subdivisions-placeholder',
|
||||
defaultMessage: 'Search subdivisions...',
|
||||
},
|
||||
searchCountriesPlaceholder: {
|
||||
id: 'dashboard.creator-withdraw-modal.kyc.search-countries-placeholder',
|
||||
defaultMessage: 'Search countries...',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
+102
-53
@@ -58,9 +58,17 @@
|
||||
<Combobox
|
||||
v-model="selectedGiftCardId"
|
||||
:options="rewardOptions"
|
||||
:placeholder="`Select ${categoryLabel.toLowerCase()}`"
|
||||
:placeholder="
|
||||
formatMessage(messages.selectCategoryPlaceholder, {
|
||||
category: categoryLabel.toLowerCase(),
|
||||
})
|
||||
"
|
||||
searchable
|
||||
:search-placeholder="`Search ${categoryLabelPlural.toLowerCase()}...`"
|
||||
:search-placeholder="
|
||||
formatMessage(messages.searchCategoryPlaceholder, {
|
||||
category: categoryLabelPlural.toLowerCase(),
|
||||
})
|
||||
"
|
||||
class="h-10"
|
||||
>
|
||||
<template #selected>
|
||||
@@ -90,47 +98,21 @@
|
||||
</Combobox>
|
||||
</div>
|
||||
<span v-if="selectedMethodDetails" class="text-secondary">
|
||||
{{ formatMoney(displayMinUsd)
|
||||
}}<template v-if="selectedMethodCurrencyCode && selectedMethodCurrencyCode !== 'USD'"
|
||||
>({{
|
||||
formatAmountForDisplay(
|
||||
displayMinLocal,
|
||||
selectedMethodCurrencyCode,
|
||||
selectedMethodExchangeRate,
|
||||
)
|
||||
}})</template
|
||||
>
|
||||
min<template v-if="displayMinUsd <= roundedMaxAmount"
|
||||
>, {{ formatMoney(displayMaxUsd)
|
||||
}}<template v-if="selectedMethodCurrencyCode && selectedMethodCurrencyCode !== 'USD'"
|
||||
>({{
|
||||
formatAmountForDisplay(
|
||||
displayMaxLocal,
|
||||
selectedMethodCurrencyCode,
|
||||
selectedMethodExchangeRate,
|
||||
)
|
||||
}})</template
|
||||
>
|
||||
max</template
|
||||
>
|
||||
withdrawal amount.
|
||||
<template v-if="displayMinUsd <= roundedMaxAmount">{{
|
||||
formatMessage(messages.withdrawalAmountMinMax, {
|
||||
min: minAmountDisplay,
|
||||
max: maxAmountDisplay,
|
||||
})
|
||||
}}</template>
|
||||
<template v-else>{{
|
||||
formatMessage(messages.withdrawalAmountMinOnly, { min: minAmountDisplay })
|
||||
}}</template>
|
||||
</span>
|
||||
<span
|
||||
v-if="selectedMethodDetails && effectiveMinAmount > roundedMaxAmount"
|
||||
class="text-sm text-red"
|
||||
>
|
||||
You need at least
|
||||
{{ formatMoney(displayMinUsd)
|
||||
}}<template v-if="selectedMethodCurrencyCode && selectedMethodCurrencyCode !== 'USD'"
|
||||
>({{
|
||||
formatAmountForDisplay(
|
||||
displayMinLocal,
|
||||
selectedMethodCurrencyCode,
|
||||
selectedMethodExchangeRate,
|
||||
)
|
||||
}})</template
|
||||
>
|
||||
to use this gift card.
|
||||
{{ formatMessage(messages.needAtLeastGiftCard, { min: minAmountDisplay }) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -280,19 +262,13 @@
|
||||
v-if="!useDenominationSuggestions && denominationOptions.length === 0"
|
||||
class="text-error text-sm"
|
||||
>
|
||||
<template v-if="rawFixedDenominationMin !== null">
|
||||
The minimum denomination is
|
||||
{{
|
||||
formatAmountForDisplay(
|
||||
rawFixedDenominationMin,
|
||||
selectedMethodCurrencyCode,
|
||||
selectedMethodExchangeRate,
|
||||
)
|
||||
}}<template v-if="selectedMethodCurrencyCode && selectedMethodCurrencyCode !== 'USD'">
|
||||
({{ formatMoney(convertToUsd(rawFixedDenominationMin)) }})</template
|
||||
>, which exceeds your balance of {{ formatMoney(roundedMaxAmount) }}.
|
||||
</template>
|
||||
<template v-else>No denominations available for your current balance</template>
|
||||
<template v-if="rawFixedDenominationMin !== null">{{
|
||||
formatMessage(messages.minimumDenominationExceedsBalance, {
|
||||
min: minDenominationDisplay,
|
||||
balance: formatMoney(roundedMaxAmount),
|
||||
})
|
||||
}}</template>
|
||||
<template v-else>{{ formatMessage(messages.noDenominationsAvailable) }}</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -683,13 +659,50 @@ const noSuggestionsMessage = computed(() => {
|
||||
selectedMethodCurrencyCode.value,
|
||||
selectedMethodExchangeRate.value,
|
||||
)
|
||||
return `No denominations near this amount. The highest available is ${maxInLocal}.`
|
||||
return formatMessage(messages.noSuggestionsWithMax, { max: maxInLocal })
|
||||
}
|
||||
return 'No denominations near this amount'
|
||||
return formatMessage(messages.noSuggestionsNoMax)
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const minAmountDisplay = computed(() => {
|
||||
const base = formatMoney(displayMinUsd.value)
|
||||
if (selectedMethodCurrencyCode.value && selectedMethodCurrencyCode.value !== 'USD') {
|
||||
return `${base} (${formatAmountForDisplay(
|
||||
displayMinLocal.value,
|
||||
selectedMethodCurrencyCode.value,
|
||||
selectedMethodExchangeRate.value,
|
||||
)})`
|
||||
}
|
||||
return base
|
||||
})
|
||||
|
||||
const maxAmountDisplay = computed(() => {
|
||||
const base = formatMoney(displayMaxUsd.value)
|
||||
if (selectedMethodCurrencyCode.value && selectedMethodCurrencyCode.value !== 'USD') {
|
||||
return `${base} (${formatAmountForDisplay(
|
||||
displayMaxLocal.value,
|
||||
selectedMethodCurrencyCode.value,
|
||||
selectedMethodExchangeRate.value,
|
||||
)})`
|
||||
}
|
||||
return base
|
||||
})
|
||||
|
||||
const minDenominationDisplay = computed(() => {
|
||||
if (rawFixedDenominationMin.value === null) return ''
|
||||
const local = formatAmountForDisplay(
|
||||
rawFixedDenominationMin.value,
|
||||
selectedMethodCurrencyCode.value,
|
||||
selectedMethodExchangeRate.value,
|
||||
)
|
||||
if (selectedMethodCurrencyCode.value && selectedMethodCurrencyCode.value !== 'USD') {
|
||||
return `${local} (${formatMoney(convertToUsd(rawFixedDenominationMin.value))})`
|
||||
}
|
||||
return local
|
||||
})
|
||||
|
||||
const hasSelectedDenomination = computed(() => {
|
||||
return (
|
||||
formData.value.amount !== undefined &&
|
||||
@@ -1124,5 +1137,41 @@ const messages = defineMessages({
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.select-denomination-required',
|
||||
defaultMessage: 'Please select a denomination to continue',
|
||||
},
|
||||
selectCategoryPlaceholder: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.select-category-placeholder',
|
||||
defaultMessage: 'Select {category}',
|
||||
},
|
||||
searchCategoryPlaceholder: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.search-category-placeholder',
|
||||
defaultMessage: 'Search {category}...',
|
||||
},
|
||||
withdrawalAmountMinMax: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.withdrawal-amount-min-max',
|
||||
defaultMessage: '{min} min, {max} max withdrawal amount.',
|
||||
},
|
||||
withdrawalAmountMinOnly: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.withdrawal-amount-min-only',
|
||||
defaultMessage: '{min} min withdrawal amount.',
|
||||
},
|
||||
needAtLeastGiftCard: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.need-at-least-gift-card',
|
||||
defaultMessage: 'You need at least {min} to use this gift card.',
|
||||
},
|
||||
minimumDenominationExceedsBalance: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.minimum-denomination-exceeds-balance',
|
||||
defaultMessage: 'The minimum denomination is {min}, which exceeds your balance of {balance}.',
|
||||
},
|
||||
noDenominationsAvailable: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.no-denominations-available',
|
||||
defaultMessage: 'No denominations available for your current balance',
|
||||
},
|
||||
noSuggestionsWithMax: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.no-suggestions-with-max',
|
||||
defaultMessage: 'No denominations near this amount. The highest available is {max}.',
|
||||
},
|
||||
noSuggestionsNoMax: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.no-suggestions-no-max',
|
||||
defaultMessage: 'No denominations near this amount',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -27,10 +27,14 @@ const messages = defineMessages({
|
||||
id: 'hosting.plan.select-plan',
|
||||
defaultMessage: 'Select plan',
|
||||
},
|
||||
billedInterval: {
|
||||
id: 'servers.purchase.step.plan.billed',
|
||||
perMonthBilling: {
|
||||
id: 'servers.purchase.step.plan.per-month-billing',
|
||||
defaultMessage:
|
||||
'billed {interval, select, monthly {monthly} quarterly {quarterly} yearly {yearly} other {{interval}}}',
|
||||
'/ month{interval, select, monthly {} other {, billed {interval, select, quarterly {quarterly} yearly {yearly} other {{interval}}}}}',
|
||||
},
|
||||
mostPopularBadge: {
|
||||
id: 'servers.purchase.step.plan.most-popular',
|
||||
defaultMessage: 'Most popular',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -131,15 +135,13 @@ const billingMonths = computed(() => {
|
||||
v-if="plans[plan].mostPopular"
|
||||
class="rounded-full bg-brand-highlight px-2 py-1 text-xs font-bold text-brand"
|
||||
>
|
||||
Most popular
|
||||
{{ formatMessage(messages.mostPopularBadge) }}
|
||||
</div>
|
||||
</div>
|
||||
<span class="m-0 text-2xl font-bold text-contrast">
|
||||
{{ formatPrice(price / billingMonths, currency, true) }}
|
||||
<span class="text-lg font-semibold text-secondary">
|
||||
/ month<template v-if="interval !== 'monthly'"
|
||||
>, {{ formatMessage(messages.billedInterval, { interval }) }}</template
|
||||
>
|
||||
{{ formatMessage(messages.perMonthBilling, { interval }) }}
|
||||
</span>
|
||||
</span>
|
||||
<p class="m-0 max-w-[18rem]">{{ formatMessage(plans[plan].description) }}</p>
|
||||
|
||||
@@ -4067,9 +4067,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Perfekt für 1 - 5 Freunde mit ein par leichten Mods."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "abgerechnet {interval, select, monthly {monthly} quarterly {quarterly} yearly {yearly} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Einrichtung abschliessen"
|
||||
},
|
||||
|
||||
@@ -4067,9 +4067,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Perfekt für 1–5 Freunde mit ein paar kleinen Mods."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "{interval, select, monthly {monatlich} quarterly {vierteljährlich} yearly {jährlich} other {{interval}}} abgerechnet"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Einrichtung abschließen"
|
||||
},
|
||||
|
||||
@@ -251,6 +251,9 @@
|
||||
"analytics.empty.select-project": {
|
||||
"message": "Select at least one project to view data"
|
||||
},
|
||||
"analytics.filter.dependent-project-search-failed-title": {
|
||||
"message": "Dependent projects failed to load"
|
||||
},
|
||||
"analytics.filter.game-version-type": {
|
||||
"message": "Game version type"
|
||||
},
|
||||
@@ -947,6 +950,12 @@
|
||||
"auth.verify-email.title": {
|
||||
"message": "Verify Email"
|
||||
},
|
||||
"collection.aria-label.upload-icon": {
|
||||
"message": "Upload icon"
|
||||
},
|
||||
"collection.button.discover-mods": {
|
||||
"message": "Discover mods"
|
||||
},
|
||||
"collection.button.edit-icon": {
|
||||
"message": "Edit icon"
|
||||
},
|
||||
@@ -1121,6 +1130,135 @@
|
||||
"create-project-version.create-modal.stage.add-files.admonition": {
|
||||
"message": "Supplementary files are for supporting resources like source code, not for alternative versions or variants."
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.details-tab": {
|
||||
"message": "Details"
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.files-tab": {
|
||||
"message": "Files"
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.metadata-tab": {
|
||||
"message": "Metadata"
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.primary-file-description": {
|
||||
"message": "The primary file is the default file a user downloads when installing the project."
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.primary-file-label": {
|
||||
"message": "Primary file"
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.supplementary-files-description": {
|
||||
"message": "You can optionally add supplementary files such as source code, documentation, or required resource packs."
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.supplementary-files-label": {
|
||||
"message": "Supplementary files"
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.upload-additional-file-aria-label": {
|
||||
"message": "Upload additional file"
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.upload-file-aria-label": {
|
||||
"message": "Upload file"
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.upload-primary-prompt": {
|
||||
"message": "Upload primary and supporting files"
|
||||
},
|
||||
"create-project-version.create-modal.stage.add-files.upload-secondary-prompt": {
|
||||
"message": "Drag and drop files or click to browse"
|
||||
},
|
||||
"create-project-version.create-modal.stage.details.details-tab": {
|
||||
"message": "Details"
|
||||
},
|
||||
"create-project-version.create-modal.stage.details.files-tab": {
|
||||
"message": "Files"
|
||||
},
|
||||
"create-project-version.create-modal.stage.details.metadata-tab": {
|
||||
"message": "Metadata"
|
||||
},
|
||||
"create-project-version.create-modal.stage.details.version-changelog": {
|
||||
"message": "Version changelog"
|
||||
},
|
||||
"create-project-version.create-modal.stage.details.version-number": {
|
||||
"message": "Version number"
|
||||
},
|
||||
"create-project-version.create-modal.stage.details.version-number-description": {
|
||||
"message": "The version number differentiates this specific version from others."
|
||||
},
|
||||
"create-project-version.create-modal.stage.details.version-number-placeholder": {
|
||||
"message": "Enter version number, e.g. 1.2.3-alpha.1"
|
||||
},
|
||||
"create-project-version.create-modal.stage.details.version-subtitle": {
|
||||
"message": "Version subtitle"
|
||||
},
|
||||
"create-project-version.create-modal.stage.details.version-subtitle-placeholder": {
|
||||
"message": "Enter subtitle..."
|
||||
},
|
||||
"create-project-version.create-modal.stage.details.version-type": {
|
||||
"message": "Version type"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.add-dependency": {
|
||||
"message": "Add dependency"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.dependencies": {
|
||||
"message": "Dependencies"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.details-tab": {
|
||||
"message": "Details"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.detected-loaders": {
|
||||
"message": "Detected loaders"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.detected-versions": {
|
||||
"message": "Detected versions"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.edit-button": {
|
||||
"message": "Edit"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.environment": {
|
||||
"message": "Environment"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.files-tab": {
|
||||
"message": "Files"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.loaders": {
|
||||
"message": "Loaders"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.metadata-tab": {
|
||||
"message": "Metadata"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.modpack-loaders-tooltip": {
|
||||
"message": "Modpack loaders cannot be edited"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.modpack-versions-tooltip": {
|
||||
"message": "Modpack versions cannot be edited"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.no-dependencies-added": {
|
||||
"message": "No dependencies added."
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.no-environment-set": {
|
||||
"message": "No environment has been set."
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.no-loaders-selected": {
|
||||
"message": "No loaders selected."
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.no-mod-loader": {
|
||||
"message": "No mod loader"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.no-versions-selected": {
|
||||
"message": "No versions selected."
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.prefilled-environment-tooltip": {
|
||||
"message": "Pre-filled from a previous similar version"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.resource-pack-loaders-tooltip": {
|
||||
"message": "Resource pack loaders cannot be edited"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.suggested": {
|
||||
"message": "Suggested"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.uploaded-files": {
|
||||
"message": "Uploaded files"
|
||||
},
|
||||
"create-project-version.create-modal.stage.metadata.versions": {
|
||||
"message": "Versions"
|
||||
},
|
||||
"create.collection.collection-info": {
|
||||
"message": "Your new collection will be created as a public collection with {count, plural, =0 {no projects} one {# project} other {# projects}}."
|
||||
},
|
||||
@@ -1217,6 +1355,9 @@
|
||||
"create.project.name-placeholder": {
|
||||
"message": "Enter project name..."
|
||||
},
|
||||
"create.project.organization-icon-alt": {
|
||||
"message": "{name} Icon"
|
||||
},
|
||||
"create.project.owner-description": {
|
||||
"message": "Set the project owner as yourself or an organization you're a member of."
|
||||
},
|
||||
@@ -1247,9 +1388,15 @@
|
||||
"create.project.type-server": {
|
||||
"message": "Server"
|
||||
},
|
||||
"create.project.unknown-user": {
|
||||
"message": "Unknown user"
|
||||
},
|
||||
"create.project.url-label": {
|
||||
"message": "URL"
|
||||
},
|
||||
"create.project.user-avatar-alt": {
|
||||
"message": "User Avatar"
|
||||
},
|
||||
"create.project.visibility-description": {
|
||||
"message": "The visibility of your project after it has been approved."
|
||||
},
|
||||
@@ -1331,6 +1478,9 @@
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Recently Updated"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.close-button": {
|
||||
"message": "Close"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Download {formType}"
|
||||
},
|
||||
@@ -1358,6 +1508,12 @@
|
||||
"dashboard.creator-tax-form-modal.header": {
|
||||
"message": "Tax form"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.incomplete.text": {
|
||||
"message": "You have not completed the tax form. Please try again."
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.incomplete.title": {
|
||||
"message": "Tax form incomplete"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.security.description": {
|
||||
"message": "Modrinth uses third-party provider Track1099 to securely collect and store your tax forms. <security-link>Learn more here.</security-link>"
|
||||
},
|
||||
@@ -1397,6 +1553,9 @@
|
||||
"dashboard.creator-withdraw-modal.kyc.business-entity": {
|
||||
"message": "Business entity"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.kyc.date-of-birth-placeholder": {
|
||||
"message": "Select date of birth"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.kyc.entity-description": {
|
||||
"message": "A business entity refers to a registered organization such as a corporation, partnership, or LLC."
|
||||
},
|
||||
@@ -1406,6 +1565,12 @@
|
||||
"dashboard.creator-withdraw-modal.kyc.private-individual": {
|
||||
"message": "Private individual"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.kyc.search-countries-placeholder": {
|
||||
"message": "Search countries..."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.kyc.search-subdivisions-placeholder": {
|
||||
"message": "Search subdivisions..."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.method-selection.country-placeholder": {
|
||||
"message": "Select your country"
|
||||
},
|
||||
@@ -1541,6 +1706,21 @@
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.enter-denomination-placeholder": {
|
||||
"message": "Enter amount"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.minimum-denomination-exceeds-balance": {
|
||||
"message": "The minimum denomination is {min}, which exceeds your balance of {balance}."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.need-at-least-gift-card": {
|
||||
"message": "You need at least {min} to use this gift card."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.no-denominations-available": {
|
||||
"message": "No denominations available for your current balance"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.no-suggestions-no-max": {
|
||||
"message": "No denominations near this amount"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.no-suggestions-with-max": {
|
||||
"message": "No denominations near this amount. The highest available is {max}."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.payment-method": {
|
||||
"message": "Payment method"
|
||||
},
|
||||
@@ -1556,6 +1736,12 @@
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.search-amount-label": {
|
||||
"message": "Search amount"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.search-category-placeholder": {
|
||||
"message": "Search {category}..."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.select-category-placeholder": {
|
||||
"message": "Select {category}"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.select-denomination-hint": {
|
||||
"message": "Select a denomination:"
|
||||
},
|
||||
@@ -1574,6 +1760,12 @@
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.usd-paypal-warning-message": {
|
||||
"message": "You selected USD for PayPal International. <direct-paypal-link>Switch to direct PayPal</direct-paypal-link> for better fees (≈2% instead of ≈6%)."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.withdrawal-amount-min-max": {
|
||||
"message": "{min} min, {max} max withdrawal amount."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.withdrawal-amount-min-only": {
|
||||
"message": "{min} min withdrawal amount."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.withdraw-button": {
|
||||
"message": "Withdraw"
|
||||
},
|
||||
@@ -2237,6 +2429,24 @@
|
||||
"hosting-marketing.medal.text-secondary": {
|
||||
"message": "Limited-time offer. No credit card required. Available for US servers."
|
||||
},
|
||||
"hosting-marketing.notification.error-fetching-payment-data-title": {
|
||||
"message": "Error fetching payment data"
|
||||
},
|
||||
"hosting-marketing.notification.invalid-product-text": {
|
||||
"message": "The selected product was found but lacks necessary data. Please contact support."
|
||||
},
|
||||
"hosting-marketing.notification.invalid-product-title": {
|
||||
"message": "Invalid product"
|
||||
},
|
||||
"hosting-marketing.notification.server-capacity-full-text": {
|
||||
"message": "We are currently at capacity. Please try again later."
|
||||
},
|
||||
"hosting-marketing.notification.server-capacity-full-title": {
|
||||
"message": "Server Capacity Full"
|
||||
},
|
||||
"hosting-marketing.notification.unexpected-error-text": {
|
||||
"message": "An unexpected error occurred"
|
||||
},
|
||||
"hosting-marketing.pick-customized-plan": {
|
||||
"message": "Pick a customized plan with just the specs you need."
|
||||
},
|
||||
@@ -2555,6 +2765,21 @@
|
||||
"layout.footer.about.status": {
|
||||
"message": "Status"
|
||||
},
|
||||
"layout.footer.based-on-label": {
|
||||
"message": "Based on"
|
||||
},
|
||||
"layout.footer.developer-mode-activated.text": {
|
||||
"message": "Developer mode has been enabled"
|
||||
},
|
||||
"layout.footer.developer-mode-activated.title": {
|
||||
"message": "Developer mode activated"
|
||||
},
|
||||
"layout.footer.developer-mode-deactivated.text": {
|
||||
"message": "Developer mode has been disabled"
|
||||
},
|
||||
"layout.footer.developer-mode-deactivated.title": {
|
||||
"message": "Developer mode deactivated"
|
||||
},
|
||||
"layout.footer.legal": {
|
||||
"message": "Legal"
|
||||
},
|
||||
@@ -2624,6 +2849,9 @@
|
||||
"layout.footer.social.x": {
|
||||
"message": "X"
|
||||
},
|
||||
"layout.footer.unknown-commit": {
|
||||
"message": "unknown"
|
||||
},
|
||||
"layout.menu-toggle.action": {
|
||||
"message": "Toggle menu"
|
||||
},
|
||||
@@ -3041,6 +3269,33 @@
|
||||
"organization.label.projects": {
|
||||
"message": "projects"
|
||||
},
|
||||
"organization.project-transfer.id-column": {
|
||||
"message": "ID"
|
||||
},
|
||||
"organization.project-transfer.name-column": {
|
||||
"message": "Name"
|
||||
},
|
||||
"organization.project-transfer.no-projects-available": {
|
||||
"message": "No projects available to transfer."
|
||||
},
|
||||
"organization.project-transfer.project-icon-alt": {
|
||||
"message": "Icon for {name}"
|
||||
},
|
||||
"organization.project-transfer.title": {
|
||||
"message": "Transfer projects"
|
||||
},
|
||||
"organization.project-transfer.transfer-selected-projects": {
|
||||
"message": "Transfer {count, plural, one {# project} other {# projects}}"
|
||||
},
|
||||
"organization.project-transfer.type-column": {
|
||||
"message": "Type"
|
||||
},
|
||||
"organization.projects.none": {
|
||||
"message": "This organization doesn't have any projects yet."
|
||||
},
|
||||
"organization.projects.none-with-create-prompt": {
|
||||
"message": "This organization doesn't have any projects yet. Would you like to <create-link>create one</create-link>?"
|
||||
},
|
||||
"profile.bio.fallback.creator": {
|
||||
"message": "A Modrinth creator."
|
||||
},
|
||||
@@ -3476,6 +3731,9 @@
|
||||
"project.gallery.title": {
|
||||
"message": "Gallery"
|
||||
},
|
||||
"project.install-context.back-to-discover": {
|
||||
"message": "Back to discover"
|
||||
},
|
||||
"project.license.error": {
|
||||
"message": "License text could not be retrieved."
|
||||
},
|
||||
@@ -3695,6 +3953,102 @@
|
||||
"project.settings.permissions.sort.status": {
|
||||
"message": "Status"
|
||||
},
|
||||
"project.settings.server.bedrock-address-label": {
|
||||
"message": "Bedrock address"
|
||||
},
|
||||
"project.settings.server.cannot-save-text": {
|
||||
"message": "The Java server must be reachable before saving. Please ensure the ping succeeds."
|
||||
},
|
||||
"project.settings.server.cannot-save-title": {
|
||||
"message": "Cannot save"
|
||||
},
|
||||
"project.settings.server.details-heading": {
|
||||
"message": "Server details"
|
||||
},
|
||||
"project.settings.server.enter-address-placeholder": {
|
||||
"message": "Enter address"
|
||||
},
|
||||
"project.settings.server.java-address-label": {
|
||||
"message": "Java address"
|
||||
},
|
||||
"project.settings.server.languages-label": {
|
||||
"message": "Languages"
|
||||
},
|
||||
"project.settings.server.latency-label": {
|
||||
"message": "Latency: {latency}ms"
|
||||
},
|
||||
"project.settings.server.optional-label": {
|
||||
"message": "optional"
|
||||
},
|
||||
"project.settings.server.ping-failed-message": {
|
||||
"message": "We couldn't ping this server. It may be blocked by your host so try refreshing a few times. If it still doesn't respond please <support-link>contact support</support-link>."
|
||||
},
|
||||
"project.settings.server.refresh-ping-tooltip": {
|
||||
"message": "Refresh ping"
|
||||
},
|
||||
"project.settings.server.region-label": {
|
||||
"message": "Region"
|
||||
},
|
||||
"project.settings.server.select-languages-placeholder": {
|
||||
"message": "Select languages"
|
||||
},
|
||||
"project.settings.server.select-region-placeholder": {
|
||||
"message": "Select region"
|
||||
},
|
||||
"project.settings.server.server-online": {
|
||||
"message": "Server is online!"
|
||||
},
|
||||
"project.settings.server.srv-records-hint": {
|
||||
"message": "If you have <srv-tooltip>[SRV records]</srv-tooltip>, you do not need to add a port. Otherwise if you have a port which isn't 25565, you can include it as :12345"
|
||||
},
|
||||
"project.settings.server.srv-records-tooltip": {
|
||||
"message": "The address you enter here may have DNS SRV records _minecraft._tcp.(your domain) which point to your Minecraft server address and port."
|
||||
},
|
||||
"project.settings.tags.all-tags-selected-warning": {
|
||||
"message": "You've selected all {count} available tags. Please select only the tags that truly apply to your project."
|
||||
},
|
||||
"project.settings.tags.categories-description": {
|
||||
"message": "Select all categories that reflect the themes or function of your {type}."
|
||||
},
|
||||
"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.features-description": {
|
||||
"message": "Select all of the features that your {type} makes use of."
|
||||
},
|
||||
"project.settings.tags.multiple-resolution-tags-warning": {
|
||||
"message": "You've selected {count} resolution tags ({tags}). Resource packs should typically only have one resolution tag."
|
||||
},
|
||||
"project.settings.tags.performance-impact-description": {
|
||||
"message": "Select the realistic performance impact of your {type}. Select multiple if the {type} is configurable to different levels of performance impact."
|
||||
},
|
||||
"project.settings.tags.resolutions-description": {
|
||||
"message": "Select the resolution(s) of textures in your {type}."
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"project.settings.tags.too-many-tags-project-warning": {
|
||||
"message": "You've selected {count} tags. Consider reducing to 8 or fewer to keep your project focused and easier to discover."
|
||||
},
|
||||
"project.settings.tags.too-many-tags-server-hard-warning": {
|
||||
"message": "You've selected {count} tags. Please reduce to 18 or fewer to keep your server focused and easier to discover."
|
||||
},
|
||||
"project.settings.tags.too-many-tags-server-soft-warning": {
|
||||
"message": "You've selected {count} tags. Consider reducing to 12 or fewer to keep your server focused and easier to discover."
|
||||
},
|
||||
"project.settings.tags.upload-version-first": {
|
||||
"message": "Please upload a version first in order to select tags!"
|
||||
},
|
||||
"project.settings.title": {
|
||||
"message": "Settings"
|
||||
},
|
||||
@@ -3704,9 +4058,51 @@
|
||||
"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.versions.copy-id-option": {
|
||||
"message": "Copy ID"
|
||||
},
|
||||
"project.versions.copy-link-option": {
|
||||
"message": "Copy link"
|
||||
},
|
||||
"project.versions.copy-maven-coordinates-option": {
|
||||
"message": "Copy Maven coordinates"
|
||||
},
|
||||
"project.versions.delete-button": {
|
||||
"message": "Delete"
|
||||
},
|
||||
"project.versions.delete-confirm.description": {
|
||||
"message": "This will remove this version forever (like really forever)."
|
||||
},
|
||||
"project.versions.delete-confirm.title": {
|
||||
"message": "Are you sure you want to delete this version?"
|
||||
},
|
||||
"project.versions.edit-details-option": {
|
||||
"message": "Edit details"
|
||||
},
|
||||
"project.versions.edit-files-option": {
|
||||
"message": "Edit files"
|
||||
},
|
||||
"project.versions.edit-metadata-option": {
|
||||
"message": "Edit metadata"
|
||||
},
|
||||
"project.versions.edit-version-tooltip": {
|
||||
"message": "Edit version"
|
||||
},
|
||||
"project.versions.open-in-new-tab-option": {
|
||||
"message": "Open in new tab"
|
||||
},
|
||||
"project.versions.share-option": {
|
||||
"message": "Share"
|
||||
},
|
||||
"project.versions.title": {
|
||||
"message": "Versions"
|
||||
},
|
||||
"project.versions.version-deleted-text": {
|
||||
"message": "The version has been successfully deleted."
|
||||
},
|
||||
"project.versions.version-deleted-title": {
|
||||
"message": "Version deleted"
|
||||
},
|
||||
"project.versions.withheld-versions-warning.description": {
|
||||
"message": "{count, plural, one {This version is} other {These versions are}} currently withheld and not publicly listed. Please provide proof that you have permission to redistribute certain files included in the modpack {count, plural, one {version} other {versions}}."
|
||||
},
|
||||
@@ -4121,8 +4517,11 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Perfect for 1–5 friends with a few light mods."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "billed {interval, select, monthly {monthly} quarterly {quarterly} yearly {yearly} other {{interval}}}"
|
||||
"servers.purchase.step.plan.most-popular": {
|
||||
"message": "Most popular"
|
||||
},
|
||||
"servers.purchase.step.plan.per-month-billing": {
|
||||
"message": "/ month{interval, select, monthly {} other {, billed {interval, select, quarterly {quarterly} yearly {yearly} other {{interval}}}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Complete setup"
|
||||
@@ -4967,6 +5366,18 @@
|
||||
"version.environment.unknown.title": {
|
||||
"message": "Unknown environment"
|
||||
},
|
||||
"version.notification.deleted-text": {
|
||||
"message": "The version has been successfully deleted."
|
||||
},
|
||||
"version.notification.deleted-title": {
|
||||
"message": "Version deleted"
|
||||
},
|
||||
"version.notification.packaging-success-text": {
|
||||
"message": "Your data pack was successfully packaged as a mod! Make sure to playtest to check for errors."
|
||||
},
|
||||
"version.notification.packaging-success-title": {
|
||||
"message": "Packaging Success"
|
||||
},
|
||||
"version.package-as-mod.button": {
|
||||
"message": "Package as mod"
|
||||
},
|
||||
|
||||
@@ -4067,9 +4067,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Perfecto para 1–5 amigos con algunos mods ligeros."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "facturado {interval, select, monthly {mensual} quarterly {trimestral} yearly {anual} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Finalizar configuración"
|
||||
},
|
||||
|
||||
@@ -3788,9 +3788,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Perfecto para 1–5 amigos con algunas modificaciones ligeras."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "facturado {interval, select, monthly {mensual} quarterly {trimestral} yearly {anual} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Configuración completa"
|
||||
},
|
||||
|
||||
@@ -4073,9 +4073,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Parfait pour 1–5 amis et quelques mods légers."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "facturé {interval, select, monthly {mensuellement} quarterly {trimestriellement} yearly {annuellement} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Terminer la configuration"
|
||||
},
|
||||
|
||||
@@ -4040,9 +4040,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Perfetto per 1–5 giocatori e qualche mod leggera."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "addebitato {interval, select, monthly {mensilmente} quarterly {trimerstralmente} yearly {annualmente} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Completa la configurazione"
|
||||
},
|
||||
|
||||
@@ -4067,9 +4067,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "1–5명의 친구와 적은 수의 가벼운 모드에 적합합니다."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "{interval, select, monthly {월간 결제} quarterly {분기별 결제} yearly {연간 결제} other {{interval} 결제}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "설정 완료"
|
||||
},
|
||||
|
||||
@@ -3572,9 +3572,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Sesuai untuk 1–5 rakan dengan beberapa mod ringan."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "dibilkan {interval, select, monthly {monthly} quarterly {quarterly} yearly {yearly} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Lengkapkan persediaan"
|
||||
},
|
||||
|
||||
@@ -4058,9 +4058,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Idealny dla 1–5 przyjaciół z kilkoma lekkimi modami."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "naliczane {interval, select, monthly {miesięcznie} quarterly {co kwartał} yearly {rocznie} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Zakończ ustawianie"
|
||||
},
|
||||
|
||||
@@ -4073,9 +4073,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Perfeito para 1–5 amigos e alguns mods leves."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "cobrado {interval, select, monthly {mensalmente} quarterly {trimestralmente} yearly {anualmente} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Terminar configuração"
|
||||
},
|
||||
|
||||
@@ -4046,9 +4046,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Для 1–5 друзей и пары лёгких модов."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "{interval, select, monthly {на месяц} quarterly {на 3 месяца} yearly {на год} other {оплата {interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Завершить настройку"
|
||||
},
|
||||
|
||||
@@ -1730,9 +1730,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Savršeno za 1–5 prijatelja sa nekoliko lakih modova."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "naplaćuje se {interval, select, monthly {mesečno} quarterly {kvartalno} yearly {godišnje} other {{interval}}}"
|
||||
},
|
||||
"settings.account.delete.section.description": {
|
||||
"message": "Kad obrišeš svoj račun, povratka nema. Brisanjem računa sa naših servera biće uklonjeni svi povezani podaci, izuzev projekata."
|
||||
},
|
||||
|
||||
@@ -3425,9 +3425,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Perfekt för 1–5 vänner med några få lätta moddar."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "fakturerad {interval, select, monthly {månadsvis} quarterly {kvartalvis} yearly {årligen} other {{interval}}}"
|
||||
},
|
||||
"settings.account.delete.confirm.proceed": {
|
||||
"message": "Radera kontot"
|
||||
},
|
||||
|
||||
@@ -4013,9 +4013,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "1–5 arkadaş ve birkaç hafif mod için mükemmel."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "{interval, select, monthly {monthly} quarterly {quarterly} yearly {yearly} other {{interval}}} faturalandırıldı"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Kurulumu tamamla"
|
||||
},
|
||||
|
||||
@@ -4064,9 +4064,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Прекрасно підходить для 1–5 друзів із кількома легкими модами."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "оплата {interval, select, monthly {щомісяця} quarterly {щокварталу} yearly {щороку} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Завершити налаштування"
|
||||
},
|
||||
|
||||
@@ -3695,9 +3695,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "Hoàn hảo cho 1–5 người bạn với một vài mod nhẹ."
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "trả tiền hàng {interval, select, monthly {tháng} quarterly {quý (3 tháng)} yearly {năm} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "Hoàn tất thiết lập"
|
||||
},
|
||||
|
||||
@@ -4067,9 +4067,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "适合 1–5 位朋友使用,加少量轻量模组。"
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "计费{interval, select, monthly {按月} quarterly {按季度} yearly {按年} other {{interval}}}"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "完成设置"
|
||||
},
|
||||
|
||||
@@ -4073,9 +4073,6 @@
|
||||
"servers.plan.small.description": {
|
||||
"message": "適合 1 到 5 位好友及少量輕量模組。"
|
||||
},
|
||||
"servers.purchase.step.plan.billed": {
|
||||
"message": "按{interval, select, monthly {月} quarterly {季} yearly {年} other {{interval}}}計費"
|
||||
},
|
||||
"settings.account.button.complete-setup": {
|
||||
"message": "完成設定"
|
||||
},
|
||||
|
||||
@@ -97,6 +97,22 @@
|
||||
<CollectionCreateModal ref="modal_collection" :project-ids="[project.id]" />
|
||||
<ModpackScanModal ref="scanModal" :project_id="project.id" />
|
||||
|
||||
<div
|
||||
v-if="projectInstallContext && !isSettings"
|
||||
ref="stickyInstallHeaderRef"
|
||||
class="sticky top-0 z-20 mx-auto max-w-[80rem] border-0 border-solid border-divider bg-surface-1 px-6 pt-4"
|
||||
:class="[isInstallHeaderStuck ? 'border-t' : '']"
|
||||
>
|
||||
<BrowseInstallHeader
|
||||
:install-context="projectHeaderInstallContext"
|
||||
divider
|
||||
bottom-padding
|
||||
/>
|
||||
</div>
|
||||
<SelectedProjectsFloatingBar
|
||||
v-if="projectInstallContext && !isSettings"
|
||||
:install-context="projectInstallContext"
|
||||
/>
|
||||
<div
|
||||
class="new-page sidebar"
|
||||
:class="{
|
||||
@@ -111,7 +127,10 @@
|
||||
!flags.alwaysShowChecklistAsPopup,
|
||||
}"
|
||||
>
|
||||
<div class="normal-page__header relative my-4">
|
||||
<div
|
||||
class="normal-page__header relative mb-4"
|
||||
:class="projectInstallContext && !isSettings ? 'mt-0' : 'mt-4'"
|
||||
>
|
||||
<div class="mb-6">
|
||||
<ModerationProjectNags
|
||||
v-if="
|
||||
@@ -616,6 +635,7 @@ import {
|
||||
import {
|
||||
Admonition,
|
||||
Avatar,
|
||||
BrowseInstallHeader,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
@@ -636,11 +656,13 @@ import {
|
||||
ProjectSidebarServerInfo,
|
||||
ProjectSidebarTags,
|
||||
provideProjectPageContext,
|
||||
SelectedProjectsFloatingBar,
|
||||
TeleportOverflowMenu,
|
||||
useDebugLogger,
|
||||
useFormatDateTime,
|
||||
useFormatPrice,
|
||||
useRelativeTime,
|
||||
useStickyObserver,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { capitalizeString, formatProjectType, renderString } from '@modrinth/utils'
|
||||
@@ -663,6 +685,7 @@ import { getSignInRouteObj } from '~/composables/auth.ts'
|
||||
import { saveFeatureFlags } from '~/composables/featureFlags.ts'
|
||||
import { STALE_TIME, STALE_TIME_LONG } from '~/composables/queries/project'
|
||||
import { versionQueryOptions } from '~/composables/queries/version'
|
||||
import { useServerInstallContent } from '~/composables/use-server-install-content'
|
||||
import { userCollectProject, userFollowProject } from '~/composables/user.js'
|
||||
import { injectCurrentProjectId } from '~/providers/current-project.ts'
|
||||
import { loadChecklistState } from '~/services/moderation-checklist-storage.ts'
|
||||
@@ -725,6 +748,11 @@ const scanModal = ref()
|
||||
|
||||
const projectV3Loaded = computed(() => !projectV3Pending.value || projectV3.value != null)
|
||||
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
||||
const stickyInstallHeaderRef = ref(null)
|
||||
const { isStuck: isInstallHeaderStuck } = useStickyObserver(
|
||||
stickyInstallHeaderRef,
|
||||
'ProjectInstallHeader',
|
||||
)
|
||||
|
||||
const projectEnvironmentModal = useTemplateRef('projectEnvironmentModal')
|
||||
|
||||
@@ -780,6 +808,10 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
'{title} has been archived. {title} will not receive any further updates unless the author decides to unarchive the project.',
|
||||
},
|
||||
backToDiscover: {
|
||||
id: 'project.install-context.back-to-discover',
|
||||
defaultMessage: 'Back to discover',
|
||||
},
|
||||
changelogTab: {
|
||||
id: 'project.navigation.changelog',
|
||||
defaultMessage: 'Changelog',
|
||||
@@ -1029,6 +1061,51 @@ const project = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const routeProjectType = computed(() =>
|
||||
Array.isArray(route.params.type) ? route.params.type[0] : route.params.type,
|
||||
)
|
||||
const projectInstallType = computed(() => ({
|
||||
id: project.value?.actualProjectType ?? routeProjectType.value,
|
||||
}))
|
||||
const serverInstallModalRef = ref(null)
|
||||
const serverInstallDebug = useDebugLogger('ProjectServerInstall')
|
||||
const { installContext: serverBrowseInstallContext } = useServerInstallContent({
|
||||
projectType: projectInstallType,
|
||||
onboardingModalRef: serverInstallModalRef,
|
||||
debug: serverInstallDebug,
|
||||
})
|
||||
const projectDiscoverBackUrl = computed(() => {
|
||||
const discoverType =
|
||||
routeProjectType.value === 'project'
|
||||
? (project.value?.actualProjectType ?? project.value?.project_type ?? 'mod')
|
||||
: (routeProjectType.value ?? project.value?.actualProjectType ?? 'mod')
|
||||
|
||||
return `/discover/${discoverType}s${getInstallContextQueryString(['sid', 'wid', 'from', 'shi'])}`
|
||||
})
|
||||
const projectInstallContext = computed(() => {
|
||||
const context = serverBrowseInstallContext.value
|
||||
if (!context) return null
|
||||
return {
|
||||
...context,
|
||||
backUrl: projectDiscoverBackUrl.value,
|
||||
backLabel: formatMessage(messages.backToDiscover),
|
||||
discardSelectedAndBack: async () => {
|
||||
await (context.clearSelected ?? context.clearQueued)?.()
|
||||
await navigateTo(projectDiscoverBackUrl.value)
|
||||
},
|
||||
}
|
||||
})
|
||||
const projectHeaderInstallContext = computed(() => {
|
||||
const context = projectInstallContext.value
|
||||
if (!context) return null
|
||||
return {
|
||||
...context,
|
||||
onBack: undefined,
|
||||
selectedProjects: [],
|
||||
isInstallingSelected: false,
|
||||
}
|
||||
})
|
||||
|
||||
// Use actual project ID for dependent queries (ensures cache consistency)
|
||||
const projectId = computed(() => projectRaw.value?.id)
|
||||
|
||||
@@ -2067,6 +2144,32 @@ function triggerDownloadAnimation() {
|
||||
setTimeout(() => (overTheTopDownloadAnimation.value = false), 500)
|
||||
}
|
||||
|
||||
const INSTALL_CONTEXT_QUERY_KEYS = ['sid', 'wid', 'from', 'shi']
|
||||
|
||||
function getInstallContextQueryString(keys = INSTALL_CONTEXT_QUERY_KEYS) {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
for (const key of keys) {
|
||||
const value = route.query[key]
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
if (item != null) {
|
||||
params.append(key, item)
|
||||
}
|
||||
}
|
||||
} else if (value != null) {
|
||||
params.append(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
const queryString = params.toString()
|
||||
return queryString ? `?${queryString}` : ''
|
||||
}
|
||||
|
||||
function withInstallContextQuery(path) {
|
||||
return `${path}${getInstallContextQueryString()}`
|
||||
}
|
||||
|
||||
async function deleteVersion(id) {
|
||||
if (!id) return
|
||||
|
||||
@@ -2091,16 +2194,16 @@ const navLinks = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: formatMessage(messages.descriptionTab),
|
||||
href: projectUrl,
|
||||
href: withInstallContextQuery(projectUrl),
|
||||
},
|
||||
{
|
||||
label: formatMessage(messages.galleryTab),
|
||||
href: `${projectUrl}/gallery`,
|
||||
href: withInstallContextQuery(`${projectUrl}/gallery`),
|
||||
shown: galleryCount > 0 || !!currentMember.value,
|
||||
},
|
||||
{
|
||||
label: formatMessage(messages.changelogTab),
|
||||
href: `${projectUrl}/changelog`,
|
||||
href: withInstallContextQuery(`${projectUrl}/changelog`),
|
||||
shown:
|
||||
hasVersions.value &&
|
||||
projectV3Loaded.value &&
|
||||
@@ -2109,7 +2212,7 @@ const navLinks = computed(() => {
|
||||
},
|
||||
{
|
||||
label: formatMessage(messages.versionsTab),
|
||||
href: `${projectUrl}/versions`,
|
||||
href: withInstallContextQuery(`${projectUrl}/versions`),
|
||||
shown:
|
||||
(hasVersions.value || !!currentMember.value) &&
|
||||
projectV3Loaded.value &&
|
||||
@@ -2119,7 +2222,7 @@ const navLinks = computed(() => {
|
||||
},
|
||||
{
|
||||
label: formatMessage(messages.moderationTab),
|
||||
href: `${projectUrl}/moderation`,
|
||||
href: withInstallContextQuery(`${projectUrl}/moderation`),
|
||||
shown: !!currentMember.value,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -3,19 +3,21 @@
|
||||
<ConfirmLeaveModal ref="confirmLeaveModal" />
|
||||
<section class="universal-card">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="text-2xl font-semibold text-contrast">Server details</div>
|
||||
<div class="text-2xl font-semibold text-contrast">
|
||||
{{ formatMessage(messages.serverDetailsHeading) }}
|
||||
</div>
|
||||
|
||||
<!-- Region -->
|
||||
<div class="max-w-[600px]">
|
||||
<label for="server-region">
|
||||
<span class="label__title">Region</span>
|
||||
<span class="label__title">{{ formatMessage(messages.regionLabel) }}</span>
|
||||
</label>
|
||||
<Combobox
|
||||
id="server-region"
|
||||
v-model="region"
|
||||
:options="regionOptions"
|
||||
searchable
|
||||
placeholder="Select region"
|
||||
:placeholder="formatMessage(messages.selectRegionPlaceholder)"
|
||||
:disabled="!hasPermission"
|
||||
/>
|
||||
</div>
|
||||
@@ -24,7 +26,10 @@
|
||||
<div class="max-w-[600px]">
|
||||
<label for="server-language">
|
||||
<span class="label__title"
|
||||
>Languages <span class="font-normal text-secondary">(optional)</span></span
|
||||
>{{ formatMessage(messages.languagesLabel) }}
|
||||
<span class="font-normal text-secondary"
|
||||
>({{ formatMessage(messages.optionalLabel) }})</span
|
||||
></span
|
||||
>
|
||||
</label>
|
||||
<MultiSelect
|
||||
@@ -34,7 +39,7 @@
|
||||
searchable
|
||||
include-select-all-option
|
||||
:max-tag-rows="2"
|
||||
placeholder="Select languages"
|
||||
:placeholder="formatMessage(messages.selectLanguagesPlaceholder)"
|
||||
:disabled="!hasPermission"
|
||||
/>
|
||||
</div>
|
||||
@@ -43,7 +48,9 @@
|
||||
<div class="max-w-[600px]">
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="java-address">
|
||||
<span class="label__title !m-0 !text-contrast">Java address</span>
|
||||
<span class="label__title !m-0 !text-contrast">{{
|
||||
formatMessage(messages.javaAddressLabel)
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@@ -58,7 +65,7 @@
|
||||
<StyledInput
|
||||
id="java-address"
|
||||
v-model="javaAddress"
|
||||
placeholder="Enter address"
|
||||
:placeholder="formatMessage(messages.enterAddressPlaceholder)"
|
||||
:disabled="!hasPermission"
|
||||
wrapper-class="flex-grow"
|
||||
autocomplete="off"
|
||||
@@ -80,7 +87,7 @@
|
||||
color="oranges"
|
||||
>
|
||||
<button
|
||||
v-tooltip="'Refresh ping'"
|
||||
v-tooltip="formatMessage(messages.refreshPingTooltip)"
|
||||
:disabled="javaPingLoading"
|
||||
@click="pingJavaServer"
|
||||
>
|
||||
@@ -92,34 +99,35 @@
|
||||
v-if="javaPingResult !== null && !javaPingLoading && javaPingResult.online"
|
||||
class="mt-0.5 flex items-center gap-1.5 text-green"
|
||||
>
|
||||
Server is online!
|
||||
{{ formatMessage(messages.serverOnline) }}
|
||||
<template v-if="javaPingResult.latency">
|
||||
Latency: {{ javaPingResult.latency }}ms
|
||||
{{ formatMessage(messages.latencyLabel, { latency: javaPingResult.latency }) }}
|
||||
</template>
|
||||
</div>
|
||||
<div v-else-if="javaPingResult !== null && !javaPingLoading" class="mt-0.5 text-orange">
|
||||
We couldn’t ping this server. It may be blocked by your host so try refreshing a few
|
||||
times. If it still doesn’t respond please
|
||||
<a
|
||||
class="inline underline"
|
||||
href="https://support.modrinth.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
contact support</a
|
||||
>.
|
||||
<IntlFormatted :message-id="messages.pingFailedMessage">
|
||||
<template #support-link="{ children }">
|
||||
<a
|
||||
class="inline underline"
|
||||
href="https://support.modrinth.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
><component :is="() => normalizeChildren(children)"
|
||||
/></a>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="mt-2 text-sm">
|
||||
If you have [SRV records]
|
||||
<InfoIcon
|
||||
v-tooltip="{
|
||||
content:
|
||||
'The address you enter here may have DNS SRV records _minecraft._tcp.{your domain} which point to your Minecraft server address and port.',
|
||||
popperClass: 'max-w-xs',
|
||||
}"
|
||||
/>, you do not need to add a port. Otherwise if you have a port which isn't 25565, you
|
||||
can include it as :12345
|
||||
<IntlFormatted :message-id="messages.srvRecordsHint">
|
||||
<template #srv-tooltip="{ children }"
|
||||
><component :is="() => normalizeChildren(children)" /><InfoIcon
|
||||
v-tooltip="{
|
||||
content: formatMessage(messages.srvRecordsTooltip),
|
||||
popperClass: 'max-w-xs',
|
||||
}"
|
||||
/></template>
|
||||
</IntlFormatted>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -127,15 +135,17 @@
|
||||
<div class="max-w-[600px]">
|
||||
<label for="bedrock-address">
|
||||
<span class="label__title !text-contrast"
|
||||
>Bedrock address
|
||||
<span class="font-normal text-secondary">(optional)</span>
|
||||
>{{ formatMessage(messages.bedrockAddressLabel) }}
|
||||
<span class="font-normal text-secondary"
|
||||
>({{ formatMessage(messages.optionalLabel) }})</span
|
||||
>
|
||||
</span>
|
||||
</label>
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<StyledInput
|
||||
id="bedrock-address"
|
||||
v-model="bedrockAddress"
|
||||
placeholder="Enter address"
|
||||
:placeholder="formatMessage(messages.enterAddressPlaceholder)"
|
||||
:disabled="!hasPermission"
|
||||
wrapper-class="flex-grow"
|
||||
autocomplete="off"
|
||||
@@ -163,10 +173,13 @@ import {
|
||||
ButtonStyled,
|
||||
Combobox,
|
||||
ConfirmLeaveModal,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
injectProjectPageContext,
|
||||
IntlFormatted,
|
||||
MultiSelect,
|
||||
normalizeChildren,
|
||||
SERVER_LANGUAGES,
|
||||
SERVER_REGIONS,
|
||||
StyledInput,
|
||||
@@ -181,6 +194,81 @@ const PING_TIMEOUT_MS = 5000
|
||||
|
||||
const { formatMessage, locale } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
serverDetailsHeading: {
|
||||
id: 'project.settings.server.details-heading',
|
||||
defaultMessage: 'Server details',
|
||||
},
|
||||
regionLabel: {
|
||||
id: 'project.settings.server.region-label',
|
||||
defaultMessage: 'Region',
|
||||
},
|
||||
selectRegionPlaceholder: {
|
||||
id: 'project.settings.server.select-region-placeholder',
|
||||
defaultMessage: 'Select region',
|
||||
},
|
||||
languagesLabel: {
|
||||
id: 'project.settings.server.languages-label',
|
||||
defaultMessage: 'Languages',
|
||||
},
|
||||
optionalLabel: {
|
||||
id: 'project.settings.server.optional-label',
|
||||
defaultMessage: 'optional',
|
||||
},
|
||||
selectLanguagesPlaceholder: {
|
||||
id: 'project.settings.server.select-languages-placeholder',
|
||||
defaultMessage: 'Select languages',
|
||||
},
|
||||
javaAddressLabel: {
|
||||
id: 'project.settings.server.java-address-label',
|
||||
defaultMessage: 'Java address',
|
||||
},
|
||||
enterAddressPlaceholder: {
|
||||
id: 'project.settings.server.enter-address-placeholder',
|
||||
defaultMessage: 'Enter address',
|
||||
},
|
||||
refreshPingTooltip: {
|
||||
id: 'project.settings.server.refresh-ping-tooltip',
|
||||
defaultMessage: 'Refresh ping',
|
||||
},
|
||||
serverOnline: {
|
||||
id: 'project.settings.server.server-online',
|
||||
defaultMessage: 'Server is online!',
|
||||
},
|
||||
latencyLabel: {
|
||||
id: 'project.settings.server.latency-label',
|
||||
defaultMessage: 'Latency: {latency}ms',
|
||||
},
|
||||
pingFailedMessage: {
|
||||
id: 'project.settings.server.ping-failed-message',
|
||||
defaultMessage:
|
||||
"We couldn't ping this server. It may be blocked by your host so try refreshing a few times. If it still doesn't respond please <support-link>contact support</support-link>.",
|
||||
},
|
||||
srvRecordsHint: {
|
||||
id: 'project.settings.server.srv-records-hint',
|
||||
defaultMessage:
|
||||
"If you have <srv-tooltip>[SRV records]</srv-tooltip>, you do not need to add a port. Otherwise if you have a port which isn't 25565, you can include it as :12345",
|
||||
},
|
||||
srvRecordsTooltip: {
|
||||
id: 'project.settings.server.srv-records-tooltip',
|
||||
defaultMessage:
|
||||
'The address you enter here may have DNS SRV records _minecraft._tcp.(your domain) which point to your Minecraft server address and port.',
|
||||
},
|
||||
bedrockAddressLabel: {
|
||||
id: 'project.settings.server.bedrock-address-label',
|
||||
defaultMessage: 'Bedrock address',
|
||||
},
|
||||
cannotSaveTitle: {
|
||||
id: 'project.settings.server.cannot-save-title',
|
||||
defaultMessage: 'Cannot save',
|
||||
},
|
||||
cannotSaveText: {
|
||||
id: 'project.settings.server.cannot-save-text',
|
||||
defaultMessage:
|
||||
'The Java server must be reachable before saving. Please ensure the ping succeeds.',
|
||||
},
|
||||
})
|
||||
|
||||
const client = injectModrinthClient()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { projectV3, currentMember, patchProjectV3 } = injectProjectPageContext()
|
||||
@@ -391,8 +479,8 @@ function resetChanges() {
|
||||
async function handleSave() {
|
||||
if (javaAddress.value.trim() && !javaPingResult.value?.online) {
|
||||
addNotification({
|
||||
title: 'Cannot save',
|
||||
text: 'The Java server must be reachable before saving. Please ensure the ping succeeds.',
|
||||
title: formatMessage(messages.cannotSaveTitle),
|
||||
text: formatMessage(messages.cannotSaveText),
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<section class="universal-card">
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Tags</span>
|
||||
<span class="label__title size-card-header">{{ formatMessage(messages.tagsTitle) }}</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@@ -27,16 +27,18 @@
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Accurate tagging is important to help people find your
|
||||
{{ formatProjectType(project.project_type).toLowerCase() }}. Make sure to select all tags
|
||||
that apply.
|
||||
{{
|
||||
formatMessage(messages.taggingImportanceDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
|
||||
<p
|
||||
v-if="project.versions.length === 0 && projectV3?.minecraft_server == null"
|
||||
class="known-errors"
|
||||
>
|
||||
Please upload a version first in order to select tags!
|
||||
{{ formatMessage(messages.uploadVersionFirst) }}
|
||||
</p>
|
||||
<template v-else>
|
||||
<template v-for="header in Object.keys(categoryLists)" :key="`categories-${header}`">
|
||||
@@ -46,22 +48,32 @@
|
||||
</h4>
|
||||
<span class="label__description">
|
||||
<template v-if="header === 'categories'">
|
||||
Select all categories that reflect the themes or function of your
|
||||
{{ formatProjectType(project.project_type).toLowerCase() }}.
|
||||
{{
|
||||
formatMessage(messages.categoriesDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="header === 'features'">
|
||||
Select all of the features that your
|
||||
{{ formatProjectType(project.project_type).toLowerCase() }} makes use of.
|
||||
{{
|
||||
formatMessage(messages.featuresDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="header === 'resolutions'">
|
||||
Select the resolution(s) of textures in your
|
||||
{{ formatProjectType(project.project_type).toLowerCase() }}.
|
||||
{{
|
||||
formatMessage(messages.resolutionsDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="header === 'performance impact'">
|
||||
Select the realistic performance impact of your
|
||||
{{ formatProjectType(project.project_type).toLowerCase() }}. Select multiple if the
|
||||
{{ formatProjectType(project.project_type).toLowerCase() }} is configurable to
|
||||
different levels of performance impact.
|
||||
{{
|
||||
formatMessage(messages.performanceImpactDescription, {
|
||||
type: formatProjectType(project.project_type).toLowerCase(),
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
@@ -90,15 +102,16 @@
|
||||
</template>
|
||||
<div class="label">
|
||||
<h4>
|
||||
<span class="label__title"><StarIcon /> Featured tags</span>
|
||||
<span class="label__title"
|
||||
><StarIcon /> {{ formatMessage(messages.featuredTags) }}</span
|
||||
>
|
||||
</h4>
|
||||
<span class="label__description">
|
||||
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.
|
||||
</span>
|
||||
<span class="label__description">{{
|
||||
formatMessage(messages.featuredTagsDescription)
|
||||
}}</span>
|
||||
</div>
|
||||
<p v-if="current.selectedTags.length < 1">
|
||||
Select at least one category in order to feature a category.
|
||||
{{ formatMessage(messages.selectAtLeastOneCategory) }}
|
||||
</p>
|
||||
<div class="category-list input-div">
|
||||
<Checkbox
|
||||
@@ -145,6 +158,7 @@ import {
|
||||
import {
|
||||
Checkbox,
|
||||
ConfirmLeaveModal,
|
||||
defineMessages,
|
||||
formatCategory,
|
||||
formatCategoryHeader,
|
||||
FormattedTag,
|
||||
@@ -167,6 +181,77 @@ interface Category {
|
||||
const tags = useGeneratedState()
|
||||
const { formatMessage, locale } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
tagsTitle: {
|
||||
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!',
|
||||
},
|
||||
categoriesDescription: {
|
||||
id: 'project.settings.tags.categories-description',
|
||||
defaultMessage: 'Select all categories that reflect the themes or function of your {type}.',
|
||||
},
|
||||
featuresDescription: {
|
||||
id: 'project.settings.tags.features-description',
|
||||
defaultMessage: 'Select all of the features that your {type} makes use of.',
|
||||
},
|
||||
resolutionsDescription: {
|
||||
id: 'project.settings.tags.resolutions-description',
|
||||
defaultMessage: 'Select the resolution(s) of textures in your {type}.',
|
||||
},
|
||||
performanceImpactDescription: {
|
||||
id: 'project.settings.tags.performance-impact-description',
|
||||
defaultMessage:
|
||||
'Select the realistic performance impact of your {type}. Select multiple if the {type} is configurable to different levels of performance impact.',
|
||||
},
|
||||
featuredTags: {
|
||||
id: 'project.settings.tags.featured-tags',
|
||||
defaultMessage: 'Featured tags',
|
||||
},
|
||||
featuredTagsDescription: {
|
||||
id: 'project.settings.tags.featured-tags-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.',
|
||||
},
|
||||
selectAtLeastOneCategory: {
|
||||
id: 'project.settings.tags.select-at-least-one-category',
|
||||
defaultMessage: 'Select at least one category in order to feature a category.',
|
||||
},
|
||||
tooManyTagsServerHardWarning: {
|
||||
id: 'project.settings.tags.too-many-tags-server-hard-warning',
|
||||
defaultMessage:
|
||||
"You've selected {count} tags. Please reduce to 18 or fewer to keep your server focused and easier to discover.",
|
||||
},
|
||||
tooManyTagsServerSoftWarning: {
|
||||
id: 'project.settings.tags.too-many-tags-server-soft-warning',
|
||||
defaultMessage:
|
||||
"You've selected {count} tags. Consider reducing to 12 or fewer to keep your server focused and easier to discover.",
|
||||
},
|
||||
tooManyTagsProjectWarning: {
|
||||
id: 'project.settings.tags.too-many-tags-project-warning',
|
||||
defaultMessage:
|
||||
"You've selected {count} tags. Consider reducing to 8 or fewer to keep your project focused and easier to discover.",
|
||||
},
|
||||
multipleResolutionTagsWarning: {
|
||||
id: 'project.settings.tags.multiple-resolution-tags-warning',
|
||||
defaultMessage:
|
||||
"You've selected {count} resolution tags ({tags}). Resource packs should typically only have one resolution tag.",
|
||||
},
|
||||
allTagsSelectedWarning: {
|
||||
id: 'project.settings.tags.all-tags-selected-warning',
|
||||
defaultMessage:
|
||||
"You've selected all {count} available tags. Please select only the tags that truly apply to your project.",
|
||||
},
|
||||
})
|
||||
|
||||
const { projectV2: project, projectV3, patchProject } = injectProjectPageContext()
|
||||
|
||||
const formatCategoryName = (categoryName: string) => {
|
||||
@@ -268,12 +353,12 @@ const tooManyTagsWarning = computed(() => {
|
||||
const tagCount = current.value.selectedTags.length
|
||||
if (projectV3?.value?.minecraft_server != null) {
|
||||
if (tagCount > 18) {
|
||||
return `You've selected ${tagCount} tags. Please reduce to 18 or fewer to keep your server focused and easier to discover.`
|
||||
return formatMessage(messages.tooManyTagsServerHardWarning, { count: tagCount })
|
||||
} else if (tagCount > 12) {
|
||||
return `You've selected ${tagCount} tags. Consider reducing to 12 or fewer to keep your server focused and easier to discover.`
|
||||
return formatMessage(messages.tooManyTagsServerSoftWarning, { count: tagCount })
|
||||
}
|
||||
} else if (tagCount > 8) {
|
||||
return `You've selected ${tagCount} tags. Consider reducing to 8 or fewer to keep your project focused and easier to discover.`
|
||||
return formatMessage(messages.tooManyTagsProjectWarning, { count: tagCount })
|
||||
}
|
||||
return null
|
||||
})
|
||||
@@ -286,14 +371,15 @@ const multipleResolutionTagsWarning = computed(() => {
|
||||
)
|
||||
|
||||
if (resolutionTags.length > 1) {
|
||||
return `You've selected ${resolutionTags.length} resolution tags (${resolutionTags
|
||||
const tagsList = resolutionTags
|
||||
.map((t) => t.name)
|
||||
.join(', ')
|
||||
.replace('8x-', '8x or lower')
|
||||
.replace(
|
||||
'512x+',
|
||||
'512x or higher',
|
||||
)}). Resource packs should typically only have one resolution tag.`
|
||||
.replace('512x+', '512x or higher')
|
||||
return formatMessage(messages.multipleResolutionTagsWarning, {
|
||||
count: resolutionTags.length,
|
||||
tags: tagsList,
|
||||
})
|
||||
}
|
||||
return null
|
||||
})
|
||||
@@ -310,7 +396,9 @@ const allTagsSelectedWarning = computed(() => {
|
||||
totalSelectedTags === categoriesForProjectType.length &&
|
||||
categoriesForProjectType.length > 0
|
||||
) {
|
||||
return `You've selected all ${categoriesForProjectType.length} available tags. Please select only the tags that truly apply to your project.`
|
||||
return formatMessage(messages.allTagsSelectedWarning, {
|
||||
count: categoriesForProjectType.length,
|
||||
})
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<ConfirmModal
|
||||
v-if="currentMember"
|
||||
ref="deleteVersionModal"
|
||||
title="Are you sure you want to delete this version?"
|
||||
description="This will remove this version forever (like really forever)."
|
||||
:title="formatMessage(messages.deleteVersionConfirmTitle)"
|
||||
:description="formatMessage(messages.deleteVersionConfirmDescription)"
|
||||
:has-to-type="false"
|
||||
proceed-label="Delete"
|
||||
:proceed-label="formatMessage(messages.deleteButton)"
|
||||
@proceed="deleteVersion()"
|
||||
/>
|
||||
<Admonition
|
||||
@@ -69,7 +69,7 @@
|
||||
<template #actions="{ version }">
|
||||
<ButtonStyled circular type="transparent">
|
||||
<OverflowMenu
|
||||
v-tooltip="'Edit version'"
|
||||
v-tooltip="formatMessage(messages.editVersionTooltip)"
|
||||
class="hover:!bg-button-bg [&>svg]:!text-green"
|
||||
:dropdown-id="`${baseDropdownId}-edit-${version.id}`"
|
||||
:options="[
|
||||
@@ -86,26 +86,26 @@
|
||||
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-files'),
|
||||
},
|
||||
]"
|
||||
aria-label="Edit version"
|
||||
:aria-label="formatMessage(messages.editVersionTooltip)"
|
||||
>
|
||||
<EditIcon aria-hidden="true" />
|
||||
<template #edit-files>
|
||||
<FileIcon aria-hidden="true" />
|
||||
Edit files
|
||||
{{ formatMessage(messages.editFilesOption) }}
|
||||
</template>
|
||||
<template #edit-details>
|
||||
<InfoIcon aria-hidden="true" />
|
||||
Edit details
|
||||
{{ formatMessage(messages.editDetailsOption) }}
|
||||
</template>
|
||||
<template #edit-metadata>
|
||||
<BoxIcon aria-hidden="true" />
|
||||
Edit metadata
|
||||
{{ formatMessage(messages.editMetadataOption) }}
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular type="transparent">
|
||||
<OverflowMenu
|
||||
v-tooltip="'More options'"
|
||||
v-tooltip="formatMessage(commonMessages.moreOptionsButton)"
|
||||
class="hover:!bg-button-bg"
|
||||
:dropdown-id="`${baseDropdownId}-${version.id}`"
|
||||
:options="[
|
||||
@@ -192,52 +192,52 @@
|
||||
shown: !!currentMember,
|
||||
},
|
||||
]"
|
||||
aria-label="More options"
|
||||
:aria-label="formatMessage(commonMessages.moreOptionsButton)"
|
||||
>
|
||||
<MoreVerticalIcon aria-hidden="true" />
|
||||
<template #download>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
Download
|
||||
{{ formatMessage(commonMessages.downloadButton) }}
|
||||
</template>
|
||||
<template #new-tab>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
Open in new tab
|
||||
{{ formatMessage(messages.openInNewTabOption) }}
|
||||
</template>
|
||||
<template #copy-link>
|
||||
<LinkIcon aria-hidden="true" />
|
||||
Copy link
|
||||
{{ formatMessage(messages.copyLinkOption) }}
|
||||
</template>
|
||||
<template #share>
|
||||
<ShareIcon aria-hidden="true" />
|
||||
Share
|
||||
{{ formatMessage(messages.shareOption) }}
|
||||
</template>
|
||||
<template #report>
|
||||
<ReportIcon aria-hidden="true" />
|
||||
Report
|
||||
{{ formatMessage(commonMessages.reportButton) }}
|
||||
</template>
|
||||
<template #edit-files>
|
||||
<FileIcon aria-hidden="true" />
|
||||
Edit files
|
||||
{{ formatMessage(messages.editFilesOption) }}
|
||||
</template>
|
||||
<template #edit-details>
|
||||
<InfoIcon aria-hidden="true" />
|
||||
Edit details
|
||||
{{ formatMessage(messages.editDetailsOption) }}
|
||||
</template>
|
||||
<template #edit-metadata>
|
||||
<BoxIcon aria-hidden="true" />
|
||||
Edit metadata
|
||||
{{ formatMessage(messages.editMetadataOption) }}
|
||||
</template>
|
||||
<template #delete>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Delete
|
||||
{{ formatMessage(commonMessages.deleteLabel) }}
|
||||
</template>
|
||||
<template #copy-id>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
Copy ID
|
||||
{{ formatMessage(messages.copyIdOption) }}
|
||||
</template>
|
||||
<template #copy-maven>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
Copy Maven coordinates
|
||||
{{ formatMessage(messages.copyMavenCoordinatesOption) }}
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
@@ -336,6 +336,7 @@ import {
|
||||
import {
|
||||
Admonition,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
commonProjectSettingsMessages,
|
||||
ConfirmModal,
|
||||
defineMessages,
|
||||
@@ -444,13 +445,13 @@ async function deleteVersion() {
|
||||
await client.labrinth.versions_v3.deleteVersion(id)
|
||||
|
||||
addNotification({
|
||||
title: 'Version deleted',
|
||||
text: 'The version has been successfully deleted.',
|
||||
title: formatMessage(messages.versionDeletedTitle),
|
||||
text: formatMessage(messages.versionDeletedText),
|
||||
type: 'success',
|
||||
})
|
||||
} catch (err: any) {
|
||||
addNotification({
|
||||
title: 'An error occurred',
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
text: err.data ? err.data.description : err,
|
||||
type: 'error',
|
||||
})
|
||||
@@ -477,5 +478,61 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
'{count, plural, one {This version is} other {These versions are}} currently withheld and not publicly listed. Please provide proof that you have permission to redistribute certain files included in the modpack {count, plural, one {version} other {versions}}.',
|
||||
},
|
||||
deleteVersionConfirmTitle: {
|
||||
id: 'project.versions.delete-confirm.title',
|
||||
defaultMessage: 'Are you sure you want to delete this version?',
|
||||
},
|
||||
deleteVersionConfirmDescription: {
|
||||
id: 'project.versions.delete-confirm.description',
|
||||
defaultMessage: 'This will remove this version forever (like really forever).',
|
||||
},
|
||||
deleteButton: {
|
||||
id: 'project.versions.delete-button',
|
||||
defaultMessage: 'Delete',
|
||||
},
|
||||
editVersionTooltip: {
|
||||
id: 'project.versions.edit-version-tooltip',
|
||||
defaultMessage: 'Edit version',
|
||||
},
|
||||
editFilesOption: {
|
||||
id: 'project.versions.edit-files-option',
|
||||
defaultMessage: 'Edit files',
|
||||
},
|
||||
editDetailsOption: {
|
||||
id: 'project.versions.edit-details-option',
|
||||
defaultMessage: 'Edit details',
|
||||
},
|
||||
editMetadataOption: {
|
||||
id: 'project.versions.edit-metadata-option',
|
||||
defaultMessage: 'Edit metadata',
|
||||
},
|
||||
openInNewTabOption: {
|
||||
id: 'project.versions.open-in-new-tab-option',
|
||||
defaultMessage: 'Open in new tab',
|
||||
},
|
||||
copyLinkOption: {
|
||||
id: 'project.versions.copy-link-option',
|
||||
defaultMessage: 'Copy link',
|
||||
},
|
||||
shareOption: {
|
||||
id: 'project.versions.share-option',
|
||||
defaultMessage: 'Share',
|
||||
},
|
||||
copyIdOption: {
|
||||
id: 'project.versions.copy-id-option',
|
||||
defaultMessage: 'Copy ID',
|
||||
},
|
||||
copyMavenCoordinatesOption: {
|
||||
id: 'project.versions.copy-maven-coordinates-option',
|
||||
defaultMessage: 'Copy Maven coordinates',
|
||||
},
|
||||
versionDeletedTitle: {
|
||||
id: 'project.versions.version-deleted-title',
|
||||
defaultMessage: 'Version deleted',
|
||||
},
|
||||
versionDeletedText: {
|
||||
id: 'project.versions.version-deleted-text',
|
||||
defaultMessage: 'The version has been successfully deleted.',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -388,7 +388,7 @@
|
||||
projectV3.project_types.includes('mod') ||
|
||||
projectV3.project_types.includes('plugin')
|
||||
"
|
||||
class="flex flex-col overflow-hidden rounded-2xl border-[1px] border-solid border-surface-4 bg-surface-2 p-0"
|
||||
class="mb-4 flex flex-col overflow-hidden rounded-2xl border-[1px] border-solid border-surface-4 bg-surface-2 p-0"
|
||||
>
|
||||
<button
|
||||
class="group m-0 flex w-full min-w-0 appearance-none items-center gap-3 rounded-2xl rounded-b-none bg-surface-3 p-4 text-left outline-offset-[-3px]"
|
||||
@@ -832,8 +832,8 @@ const deleteVersionMutation = useMutation({
|
||||
mutationFn: () => client.labrinth.versions_v3.deleteVersion(version.value!.id),
|
||||
onSuccess: async () => {
|
||||
addNotification({
|
||||
title: 'Version deleted',
|
||||
text: 'The version has been successfully deleted.',
|
||||
title: formatMessage(messages.versionDeletedTitle),
|
||||
text: formatMessage(messages.versionDeletedText),
|
||||
type: 'success',
|
||||
})
|
||||
await invalidate()
|
||||
@@ -841,7 +841,7 @@ const deleteVersionMutation = useMutation({
|
||||
},
|
||||
onError: (err: { data?: { description?: string } }) => {
|
||||
addNotification({
|
||||
title: 'An error occurred',
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
text: err.data?.description ?? String(err),
|
||||
type: 'error',
|
||||
})
|
||||
@@ -894,8 +894,8 @@ const createDataPackVersionMutation = useMutation({
|
||||
packageModal.value?.hide()
|
||||
|
||||
addNotification({
|
||||
title: 'Packaging Success',
|
||||
text: 'Your data pack was successfully packaged as a mod! Make sure to playtest to check for errors.',
|
||||
title: formatMessage(messages.packagingSuccessTitle),
|
||||
text: formatMessage(messages.packagingSuccessText),
|
||||
type: 'success',
|
||||
})
|
||||
|
||||
@@ -906,7 +906,7 @@ const createDataPackVersionMutation = useMutation({
|
||||
},
|
||||
onError: (err: { data?: { description?: string } }) => {
|
||||
addNotification({
|
||||
title: 'An error occurred',
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
text: err.data?.description ?? String(err),
|
||||
type: 'error',
|
||||
})
|
||||
@@ -939,6 +939,23 @@ const messages = defineMessages({
|
||||
id: 'version.download.no-primary-file',
|
||||
defaultMessage: 'Error: No primary file found',
|
||||
},
|
||||
versionDeletedTitle: {
|
||||
id: 'version.notification.deleted-title',
|
||||
defaultMessage: 'Version deleted',
|
||||
},
|
||||
versionDeletedText: {
|
||||
id: 'version.notification.deleted-text',
|
||||
defaultMessage: 'The version has been successfully deleted.',
|
||||
},
|
||||
packagingSuccessTitle: {
|
||||
id: 'version.notification.packaging-success-title',
|
||||
defaultMessage: 'Packaging Success',
|
||||
},
|
||||
packagingSuccessText: {
|
||||
id: 'version.notification.packaging-success-text',
|
||||
defaultMessage:
|
||||
'Your data pack was successfully packaged as a mod! Make sure to playtest to check for errors.',
|
||||
},
|
||||
downloadVersion: {
|
||||
id: 'version.download.version',
|
||||
defaultMessage: 'Download {version} ({size})',
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
:show-icon="false"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
class="hidden"
|
||||
aria-label="Upload icon"
|
||||
:aria-label="formatMessage(messages.uploadIconAriaLabel)"
|
||||
@change="showPreviewImage"
|
||||
/>
|
||||
</div>
|
||||
@@ -371,7 +371,7 @@
|
||||
<ButtonStyled v-if="auth.user && auth.user.id === creator.id" color="brand">
|
||||
<nuxt-link class="mx-auto w-min" to="/discover/mods">
|
||||
<CompassIcon class="size-5" />
|
||||
Discover mods
|
||||
{{ formatMessage(messages.discoverModsButton) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
@@ -534,6 +534,14 @@ const messages = defineMessages({
|
||||
id: 'collection.label.no-projects',
|
||||
defaultMessage: 'No projects in collection yet',
|
||||
},
|
||||
discoverModsButton: {
|
||||
id: 'collection.button.discover-mods',
|
||||
defaultMessage: 'Discover mods',
|
||||
},
|
||||
uploadIconAriaLabel: {
|
||||
id: 'collection.aria-label.upload-icon',
|
||||
defaultMessage: 'Upload icon',
|
||||
},
|
||||
projectsCountLabel: {
|
||||
id: 'collection.label.projects-count',
|
||||
defaultMessage: '{count, plural, =0 {No projects yet} other {<stat>{count}</stat> {type}}}',
|
||||
|
||||
@@ -4,7 +4,6 @@ import { commonProjectTypeCategoryMessages, NavTabs, useVIntl } from '@modrinth/
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const flags = useFeatureFlags()
|
||||
const cosmetics = useCosmetics()
|
||||
const route = useRoute()
|
||||
|
||||
const allowTabChanging = computed(() => !route.query.sid)
|
||||
@@ -48,15 +47,13 @@ const selectableProjectTypes = [
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<div class="new-page sidebar" :class="{ 'alt-layout': !cosmetics.rightSearchLayout }">
|
||||
<section class="normal-page__header mb-4 flex flex-col gap-4">
|
||||
<NavTabs
|
||||
v-if="!flags.projectTypesPrimaryNav && allowTabChanging"
|
||||
:links="selectableProjectTypes"
|
||||
replace
|
||||
class="hidden md:flex"
|
||||
/>
|
||||
</section>
|
||||
<div class="mx-auto box-border flex w-full max-w-[1280px] flex-col gap-4 px-6 pb-6">
|
||||
<NavTabs
|
||||
v-if="!flags.projectTypesPrimaryNav && allowTabChanging"
|
||||
:links="selectableProjectTypes"
|
||||
replace
|
||||
class="hidden md:flex"
|
||||
/>
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -33,6 +33,7 @@ import { cycleValue } from '@modrinth/utils'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { useTimeoutFn } from '@vueuse/core'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { LocationQueryRaw } from 'vue-router'
|
||||
|
||||
import LogoAnimated from '~/components/brand/LogoAnimated.vue'
|
||||
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
||||
@@ -55,10 +56,6 @@ const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const filtersMenuOpen = ref(false)
|
||||
const stickyInstallHeaderRef = ref<HTMLElement | null>(null)
|
||||
|
||||
useStickyObserver(stickyInstallHeaderRef, 'DiscoverInstallHeader')
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const cosmetics = useCosmetics()
|
||||
@@ -202,49 +199,83 @@ function getServerModpackContent(project: Labrinth.Search.v3.ResultSearchProject
|
||||
return undefined
|
||||
}
|
||||
|
||||
async function search(requestParams: string) {
|
||||
type DiscoverProjectSearchHit = Labrinth.Search.v2.ResultSearchProject & {
|
||||
version_id?: string | null
|
||||
}
|
||||
|
||||
function mapV3ProjectHit(hit: Labrinth.Search.v3.ResultSearchProject): DiscoverProjectSearchHit {
|
||||
return {
|
||||
...hit,
|
||||
project_type: hit.project_types[0] ?? projectTypeId.value,
|
||||
title: hit.name,
|
||||
description: hit.summary,
|
||||
versions: hit.version_id ? [hit.version_id] : [],
|
||||
latest_version: hit.version_id,
|
||||
icon_url: hit.icon_url ?? '',
|
||||
client_side: 'unknown',
|
||||
server_side: 'unknown',
|
||||
}
|
||||
}
|
||||
|
||||
const hostingContextQuery = computed(() => {
|
||||
const query: LocationQueryRaw = {}
|
||||
|
||||
for (const key of ['sid', 'wid', 'from', 'shi']) {
|
||||
const value = route.query[key]
|
||||
if (value != null) {
|
||||
query[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(query).length > 0 ? query : undefined
|
||||
})
|
||||
|
||||
function withHostingContext(path: string) {
|
||||
return hostingContextQuery.value ? { path, query: hostingContextQuery.value } : path
|
||||
}
|
||||
|
||||
async function fetchSearch(requestParams: string) {
|
||||
debug('search() called', {
|
||||
requestParams: requestParams.substring(0, 100),
|
||||
isServer: isServerType.value,
|
||||
projectTypeId: projectTypeId.value,
|
||||
})
|
||||
const config = useRuntimeConfig()
|
||||
let base = import.meta.server ? config.apiBaseUrl : config.public.apiBaseUrl
|
||||
|
||||
if (isServerType.value) {
|
||||
base = base.replace(/\/v\d\//, '/v3/').replace(/\/v\d$/, '/v3')
|
||||
}
|
||||
|
||||
const url = `${base}search${requestParams}`
|
||||
debug('search() fetching:', url.substring(0, 120))
|
||||
|
||||
const raw = await $fetch<Labrinth.Search.v2.SearchResults | Labrinth.Search.v3.SearchResults>(
|
||||
url,
|
||||
{
|
||||
headers: withLabrinthCanaryHeader(),
|
||||
},
|
||||
)
|
||||
const raw = await client.request<Labrinth.Search.v3.SearchResults>('/search', {
|
||||
api: 'labrinth',
|
||||
version: 3,
|
||||
method: 'GET',
|
||||
params: Object.fromEntries(new URLSearchParams(requestParams.replace(/^\?/, ''))),
|
||||
headers: withLabrinthCanaryHeader(),
|
||||
})
|
||||
|
||||
debug('search() response', { total_hits: raw.total_hits, hitCount: raw.hits?.length })
|
||||
|
||||
if ('hits_per_page' in raw) {
|
||||
// v3 response (servers)
|
||||
if (isServerType.value) {
|
||||
return {
|
||||
projectHits: [],
|
||||
serverHits: raw.hits as Labrinth.Search.v3.ResultSearchProject[],
|
||||
serverHits: raw.hits,
|
||||
total_hits: raw.total_hits,
|
||||
per_page: raw.hits_per_page,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
projectHits: raw.hits as Labrinth.Search.v2.ResultSearchProject[],
|
||||
projectHits: raw.hits.map(mapV3ProjectHit),
|
||||
serverHits: [],
|
||||
total_hits: raw.total_hits,
|
||||
per_page: raw.limit,
|
||||
per_page: raw.hits_per_page,
|
||||
}
|
||||
}
|
||||
|
||||
async function search(requestParams: string) {
|
||||
return await queryClient.ensureQueryData({
|
||||
queryKey: ['discover', 'search', 'v3', requestParams],
|
||||
queryFn: () => fetchSearch(requestParams),
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
|
||||
function getCardActions(
|
||||
result: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
|
||||
currentProjectType: string,
|
||||
@@ -424,7 +455,7 @@ watch(
|
||||
)
|
||||
|
||||
debug('calling initial refreshSearch')
|
||||
searchState.refreshSearch()
|
||||
await searchState.refreshSearch()
|
||||
|
||||
const ogTitle = computed(() =>
|
||||
searchState.query.value
|
||||
@@ -462,9 +493,11 @@ provideBrowseManager({
|
||||
projectType: projectTypeId,
|
||||
...searchState,
|
||||
getProjectLink: (result: Labrinth.Search.v2.ResultSearchProject) =>
|
||||
`/${projectType.value?.id ?? 'project'}/${result.slug ? result.slug : result.project_id}`,
|
||||
withHostingContext(
|
||||
`/${projectType.value?.id ?? 'project'}/${result.slug ? result.slug : result.project_id}`,
|
||||
),
|
||||
getServerProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) =>
|
||||
`/server/${result.slug ?? result.project_id}`,
|
||||
withHostingContext(`/server/${result.slug ?? result.project_id}`),
|
||||
selectableProjectTypes: computed(() => []),
|
||||
showProjectTypeTabs: computed(() => false),
|
||||
variant: 'web',
|
||||
@@ -504,25 +537,41 @@ provideBrowseManager({
|
||||
},
|
||||
loadingComponent: LogoAnimated,
|
||||
})
|
||||
|
||||
const stickyInstallHeaderRef = ref<HTMLElement | null>(null)
|
||||
const { isStuck: isInstallHeaderStuck } = useStickyObserver(
|
||||
stickyInstallHeaderRef,
|
||||
'DiscoverInstallHeader',
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<Teleport v-if="flags.searchBackground" to="#absolute-background-teleport">
|
||||
<div class="search-background"></div>
|
||||
</Teleport>
|
||||
|
||||
<div
|
||||
v-if="installContext"
|
||||
ref="stickyInstallHeaderRef"
|
||||
class="normal-page__header browse-install-header-bleed sticky top-0 z-20 mb-4 flex flex-col gap-2 border-0 bg-surface-1 py-3"
|
||||
class="sticky top-0 z-20 -mx-6 border-0 border-solid border-divider bg-surface-1 px-6 pt-4"
|
||||
:class="[isInstallHeaderStuck ? 'border-t' : '']"
|
||||
>
|
||||
<BrowseInstallHeader />
|
||||
<BrowseInstallHeader divider bottom-padding />
|
||||
</div>
|
||||
|
||||
<SelectedProjectsFloatingBar v-if="installContext" :install-context="installContext" />
|
||||
<aside class="normal-page__sidebar" :aria-label="formatMessage(commonMessages.filtersLabel)">
|
||||
<AdPlaceholder v-if="!auth.user && !serverData" />
|
||||
<BrowseSidebar />
|
||||
</aside>
|
||||
<section class="normal-page__content">
|
||||
<div class="flex flex-col gap-3">
|
||||
|
||||
<div
|
||||
class="grid min-w-0 gap-3"
|
||||
:class="
|
||||
cosmetics.rightSearchLayout
|
||||
? 'lg:grid-cols-[minmax(0,1fr)_18.75rem]'
|
||||
: 'lg:grid-cols-[18.75rem_minmax(0,1fr)]'
|
||||
"
|
||||
>
|
||||
<section
|
||||
class="flex min-w-0 flex-col gap-3"
|
||||
:class="cosmetics.rightSearchLayout ? 'lg:order-1' : 'lg:order-2'"
|
||||
>
|
||||
<BrowsePageLayout>
|
||||
<template #display-mode-icon>
|
||||
<GridIcon v-if="resultsDisplayMode === 'grid'" />
|
||||
@@ -530,78 +579,34 @@ provideBrowseManager({
|
||||
<ListIcon v-else />
|
||||
</template>
|
||||
</BrowsePageLayout>
|
||||
<CreationFlowModal
|
||||
v-if="currentServerId && projectType?.id === 'modpack'"
|
||||
ref="onboardingModalRef"
|
||||
:type="fromContext === 'reset-server' ? 'reset-server' : 'server-onboarding'"
|
||||
:available-loaders="['vanilla', 'fabric', 'neoforge', 'forge', 'quilt', 'paper', 'purpur']"
|
||||
:show-snapshot-toggle="true"
|
||||
:on-back="onOnboardingBack"
|
||||
@hide="onOnboardingHide"
|
||||
@browse-modpacks="() => {}"
|
||||
@create="onModpackFlowCreate"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<aside
|
||||
class="min-w-0"
|
||||
:class="cosmetics.rightSearchLayout ? 'lg:order-2' : 'lg:order-1'"
|
||||
:aria-label="formatMessage(commonMessages.filtersLabel)"
|
||||
>
|
||||
<BrowseSidebar>
|
||||
<template #prepend>
|
||||
<AdPlaceholder v-if="!auth.user && !serverData" />
|
||||
</template>
|
||||
</BrowseSidebar>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<CreationFlowModal
|
||||
v-if="currentServerId && projectType?.id === 'modpack'"
|
||||
ref="onboardingModalRef"
|
||||
:type="fromContext === 'reset-server' ? 'reset-server' : 'server-onboarding'"
|
||||
:available-loaders="['vanilla', 'fabric', 'neoforge', 'forge', 'quilt', 'paper', 'purpur']"
|
||||
:show-snapshot-toggle="true"
|
||||
:on-back="onOnboardingBack"
|
||||
@hide="onOnboardingHide"
|
||||
@browse-modpacks="() => {}"
|
||||
@create="onModpackFlowCreate"
|
||||
/>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.browse-install-header-bleed {
|
||||
grid-column: 1 / -1;
|
||||
margin-inline: -1.5rem;
|
||||
padding-inline: 0.75rem !important;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 50%;
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
border-bottom: 1px solid var(--surface-5);
|
||||
transform: translateX(50%);
|
||||
}
|
||||
}
|
||||
|
||||
.normal-page__content {
|
||||
display: contents;
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.normal-page__sidebar {
|
||||
grid-row: 3;
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.filters-card {
|
||||
padding: var(--spacing-card-md);
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
padding: var(--spacing-card-lg);
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.pagination-after {
|
||||
grid-row: 6;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
display: flow-root;
|
||||
}
|
||||
|
||||
.loading-logo {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.search-background {
|
||||
width: 100%;
|
||||
height: 20rem;
|
||||
|
||||
@@ -680,6 +680,31 @@ const formatPrice = useFormatPrice()
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const messages = defineMessages({
|
||||
errorFetchingPaymentDataTitle: {
|
||||
id: 'hosting-marketing.notification.error-fetching-payment-data-title',
|
||||
defaultMessage: 'Error fetching payment data',
|
||||
},
|
||||
unexpectedErrorText: {
|
||||
id: 'hosting-marketing.notification.unexpected-error-text',
|
||||
defaultMessage: 'An unexpected error occurred',
|
||||
},
|
||||
serverCapacityFullTitle: {
|
||||
id: 'hosting-marketing.notification.server-capacity-full-title',
|
||||
defaultMessage: 'Server Capacity Full',
|
||||
},
|
||||
serverCapacityFullText: {
|
||||
id: 'hosting-marketing.notification.server-capacity-full-text',
|
||||
defaultMessage: 'We are currently at capacity. Please try again later.',
|
||||
},
|
||||
invalidProductTitle: {
|
||||
id: 'hosting-marketing.notification.invalid-product-title',
|
||||
defaultMessage: 'Invalid product',
|
||||
},
|
||||
invalidProductText: {
|
||||
id: 'hosting-marketing.notification.invalid-product-text',
|
||||
defaultMessage:
|
||||
'The selected product was found but lacks necessary data. Please contact support.',
|
||||
},
|
||||
hostWithModrinth: {
|
||||
id: 'hosting-marketing.hero.host-with-modrinth',
|
||||
defaultMessage: 'Host your next server with Modrinth Hosting',
|
||||
@@ -1106,7 +1131,7 @@ const startTyping = () => {
|
||||
|
||||
const handleError = (err) => {
|
||||
addNotification({
|
||||
title: 'An error occurred',
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
type: 'error',
|
||||
text: err.message ?? (err.data ? err.data.description : err),
|
||||
})
|
||||
@@ -1124,9 +1149,9 @@ async function fetchPaymentData() {
|
||||
} catch (error) {
|
||||
console.error('Error fetching payment data:', error)
|
||||
addNotification({
|
||||
title: 'Error fetching payment data',
|
||||
title: formatMessage(messages.errorFetchingPaymentDataTitle),
|
||||
type: 'error',
|
||||
text: error.message || 'An unexpected error occurred',
|
||||
text: error.message || formatMessage(messages.unexpectedErrorText),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1177,9 +1202,9 @@ const selectProduct = async (product) => {
|
||||
|
||||
if ((product === 'custom' && isCustomAtCapacity.value) || isAtCapacity.value) {
|
||||
addNotification({
|
||||
title: 'Server Capacity Full',
|
||||
title: formatMessage(messages.serverCapacityFullTitle),
|
||||
type: 'error',
|
||||
text: 'We are currently at capacity. Please try again later.',
|
||||
text: formatMessage(messages.serverCapacityFullText),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -1192,9 +1217,9 @@ const selectProduct = async (product) => {
|
||||
(product !== 'custom' && !selectedPlan.metadata)
|
||||
) {
|
||||
addNotification({
|
||||
title: 'Invalid product',
|
||||
title: formatMessage(messages.invalidProductTitle),
|
||||
type: 'error',
|
||||
text: 'The selected product was found but lacks necessary data. Please contact support.',
|
||||
text: formatMessage(messages.invalidProductText),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -205,11 +205,16 @@
|
||||
<UpToDate class="icon" />
|
||||
<br />
|
||||
<span class="preserve-lines text">
|
||||
This organization doesn't have any projects yet.
|
||||
<template v-if="isPermission(currentMember?.permissions, 1 << 4)">
|
||||
Would you like to
|
||||
<a class="link" @click="modal_creation?.show()">create one</a>?
|
||||
<IntlFormatted :message-id="messages.noProjectsWithCreatePrompt">
|
||||
<template #create-link="{ children }">
|
||||
<a class="link" @click="modal_creation?.show()"
|
||||
><component :is="() => normalizeChildren(children)"
|
||||
/></a>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</template>
|
||||
<template v-else>{{ formatMessage(messages.noProjects) }}</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -233,8 +238,11 @@ import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
IntlFormatted,
|
||||
NavTabs,
|
||||
normalizeChildren,
|
||||
PROJECT_DEP_MARKER_QUERY,
|
||||
ProjectCard,
|
||||
ProjectCardList,
|
||||
@@ -266,6 +274,18 @@ type ProjectV3 = Labrinth.Projects.v3.Project & {
|
||||
const vintl = useVIntl()
|
||||
const { formatMessage } = vintl
|
||||
|
||||
const messages = defineMessages({
|
||||
noProjects: {
|
||||
id: 'organization.projects.none',
|
||||
defaultMessage: "This organization doesn't have any projects yet.",
|
||||
},
|
||||
noProjectsWithCreatePrompt: {
|
||||
id: 'organization.projects.none-with-create-prompt',
|
||||
defaultMessage:
|
||||
"This organization doesn't have any projects yet. Would you like to <create-link>create one</create-link>?",
|
||||
},
|
||||
})
|
||||
|
||||
const { formatCompactNumber } = useCompactNumber()
|
||||
|
||||
const auth: { user: any } & any = await useAuth()
|
||||
|
||||
@@ -288,6 +288,7 @@ import {
|
||||
AutoLink,
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessage,
|
||||
defineMessages,
|
||||
formatReportItemType,
|
||||
@@ -480,7 +481,7 @@ const submitReport = async () => {
|
||||
|
||||
if (error instanceof Error) {
|
||||
addNotification({
|
||||
title: 'An error occurred',
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
text: error.message,
|
||||
type: 'error',
|
||||
})
|
||||
@@ -505,7 +506,7 @@ const submitReport = async () => {
|
||||
|
||||
if (error instanceof Error) {
|
||||
addNotification({
|
||||
title: 'An error occurred',
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
text: error.message,
|
||||
type: 'error',
|
||||
})
|
||||
|
||||
@@ -11,12 +11,44 @@ export type VersionEntry = {
|
||||
|
||||
const VERSIONS: VersionEntry[] = [
|
||||
{
|
||||
date: `2026-07-16T19:36:45+00:00`,
|
||||
date: `2026-07-21T18:43:00+00:00`,
|
||||
product: 'web',
|
||||
body: `## Changed
|
||||
- Replaced default Aditude consent pop-up with a custom one with more accurate phrasing.
|
||||
- Updated page headers to use a consistent design style.
|
||||
- Updated translations
|
||||
|
||||
## Fixed
|
||||
- Fixed analytics tooltip not pinned after mobile chart drag
|
||||
- Fixed extra spacing sometimes on the download modal.`,
|
||||
},
|
||||
{
|
||||
date: `2026-07-21T18:43:00+00:00`,
|
||||
product: 'app',
|
||||
version: '0.15.12',
|
||||
body: `## Fixed
|
||||
version: '0.15.14',
|
||||
body: `## Added
|
||||
- Added browse page header onto project pages when going into a project page from search.
|
||||
- Added support for the [Ears](https://modrinth.com/mod/ears) mod in the Skin selector.
|
||||
|
||||
## Changed
|
||||
- Replaced default Aditude consent pop-up with a custom one with more accurate phrasing.
|
||||
- Updated page headers to use a consistent design style.
|
||||
- Updated translations
|
||||
|
||||
## Fixed
|
||||
- Fixed ratelimits not being handled properly.
|
||||
- Fixed UV spill issues causing some tiny parts of the skin texture to appear as floating lines.
|
||||
- Fixed issue with some modpacks failing to install due to file mismatches.
|
||||
- Fixed hardcore worlds generated in 26.1+ not being recognised as hardcore in the Jump back in and Worlds tab.`,
|
||||
},
|
||||
{
|
||||
date: `2026-07-21T18:43:00+00:00`,
|
||||
product: 'hosting',
|
||||
body: `## Added
|
||||
- Added browse page header onto project pages when going into a project page from search.
|
||||
|
||||
## Changed
|
||||
- Updated translations`,
|
||||
},
|
||||
{
|
||||
date: `2026-07-14T22:14:08+00:00`,
|
||||
|
||||
@@ -326,9 +326,11 @@ export default function () {
|
||||
}))(state)
|
||||
})
|
||||
.fix(
|
||||
//TODO chyz think of some way to have initial values actually be reflected in state without having to store them
|
||||
fix().project((patch, state) => {
|
||||
const slug = state['correct-slug'] as string
|
||||
if (!slug) return
|
||||
const slug =
|
||||
(state['correct-slug'] as string | undefined) ?? resolvedAutoSlug.value
|
||||
if (!slug || slug === project.value.slug) return
|
||||
patch.slug = slug
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -17,6 +17,9 @@ const config: StorybookConfig = {
|
||||
addons: ['@storybook/addon-themes', '@storybook/addon-a11y'],
|
||||
viteFinal: async (config) =>
|
||||
mergeConfig(config, {
|
||||
build: {
|
||||
reportCompressedSize: false,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@modrinth/api-client': path.resolve(storybookDirectory, '../../api-client/src/index.ts'),
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
|
||||
// Load locale messages from the UI package's locales
|
||||
// @ts-ignore
|
||||
const localeModules = import.meta.glob('../src/locales/*/index.json', {
|
||||
const localeModules = import.meta.glob('../src/locales/en-US/index.json', {
|
||||
eager: true,
|
||||
}) as Record<string, { default: CrowdinMessages }>
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface UseBrowseSearchOptions {
|
||||
}>
|
||||
providedFilters?: ComputedRef<FilterValue[]>
|
||||
environmentOverride?: ComputedRef<EnvironmentSearchOverride | undefined>
|
||||
active?: ComputedRef<boolean>
|
||||
search: (params: string) => Promise<BrowseSearchResponse>
|
||||
persistentQueryParams: string[]
|
||||
getExtraQueryParams?: () => Record<string, string | undefined>
|
||||
@@ -74,6 +75,7 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
|
||||
debug('init, projectType:', options.projectType.value)
|
||||
|
||||
const active = computed(() => options.active?.value ?? true)
|
||||
const projectTypes = computed(() => [options.projectType.value] as ProjectType[])
|
||||
const isServerType = computed(() => options.projectType.value === 'server')
|
||||
|
||||
@@ -184,6 +186,13 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
let searchVersion = 0
|
||||
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function clearSearchDebounce() {
|
||||
if (searchDebounceTimer) {
|
||||
clearTimeout(searchDebounceTimer)
|
||||
searchDebounceTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
const providedFiltersOrEmpty = computed(() => options.providedFilters?.value ?? [])
|
||||
|
||||
watch(
|
||||
@@ -209,13 +218,27 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
from: oldVal?.substring(0, 80),
|
||||
to: newVal?.substring(0, 80),
|
||||
})
|
||||
if (searchDebounceTimer) clearTimeout(searchDebounceTimer)
|
||||
clearSearchDebounce()
|
||||
if (!active.value) {
|
||||
return
|
||||
}
|
||||
searchDebounceTimer = setTimeout(() => {
|
||||
refreshSearch()
|
||||
}, 200)
|
||||
})
|
||||
|
||||
watch(active, (isActive, wasActive) => {
|
||||
clearSearchDebounce()
|
||||
if (isActive && wasActive === false) {
|
||||
void refreshSearch()
|
||||
}
|
||||
})
|
||||
|
||||
async function refreshSearch() {
|
||||
if (!active.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const version = ++searchVersion
|
||||
debug('refreshSearch start', {
|
||||
version,
|
||||
@@ -233,6 +256,10 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
try {
|
||||
const response = await options.search(effectiveRequestParams.value)
|
||||
|
||||
if (!active.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (version !== searchVersion) {
|
||||
debug('refreshSearch stale, discarding', { version, current: searchVersion })
|
||||
return
|
||||
@@ -263,6 +290,10 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
||||
}
|
||||
|
||||
function updateUrlParams() {
|
||||
if (!active.value) {
|
||||
return
|
||||
}
|
||||
|
||||
debug('updateUrlParams', { path: route.path })
|
||||
const persistentParams: Record<string, string | (string | null)[] | null | undefined> = {}
|
||||
|
||||
|
||||
@@ -121,12 +121,110 @@
|
||||
:class="['server-stagger-item', containedLayout ? 'shrink-0' : '', { 'mt-4': isNuxt }]"
|
||||
:style="{ '--si': 0 }"
|
||||
>
|
||||
<PageHeader
|
||||
:title="serverData?.name || 'Server'"
|
||||
:leading="serverHeaderLeading"
|
||||
:metadata="serverHeaderMetadata"
|
||||
:actions="serverHeaderActions"
|
||||
/>
|
||||
<PageHeader :title="serverData?.name || 'Server'">
|
||||
<template #leading>
|
||||
<ServerIcon
|
||||
:image="serverHeaderImage"
|
||||
:class="isNuxt ? 'size-20 !rounded-2xl' : 'size-16 !rounded-xl'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #metadata>
|
||||
<PageHeaderMetadata>
|
||||
<PageHeaderMetadataItem
|
||||
v-if="serverData.flows?.intro"
|
||||
:icon="SettingsIcon"
|
||||
class="font-semibold"
|
||||
>
|
||||
Configuring server...
|
||||
</PageHeaderMetadataItem>
|
||||
|
||||
<template v-else>
|
||||
<PageHeaderMetadataItem
|
||||
v-if="serverData.loader"
|
||||
:icon="LoaderIcon"
|
||||
:icon-props="{ loader: serverData.loader }"
|
||||
>
|
||||
{{ formatLoaderLabel(serverData.loader) }} {{ serverData.mc_version }}
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem
|
||||
v-if="serverData.net?.domain && !serverPreferences.hideSubdomainLabel"
|
||||
:icon="LinkIcon"
|
||||
tooltip="Copy server address"
|
||||
:action="copyServerAddress"
|
||||
>
|
||||
{{ serverData.net.domain }}.modrinth.gg
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem v-if="showServerUptime" :icon="TimerIcon">
|
||||
{{ formattedUptime }}
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem
|
||||
v-if="serverProject"
|
||||
:to="serverProjectLink"
|
||||
class="!text-primary"
|
||||
>
|
||||
Linked to
|
||||
<Avatar :src="serverProject.icon_url" :alt="serverProject.title" size="24px" />
|
||||
{{ serverProject.title }}
|
||||
</PageHeaderMetadataItem>
|
||||
</template>
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<PageHeaderActions>
|
||||
<PanelServerActionButton :disabled="!!installError" />
|
||||
<Tooltip
|
||||
theme="dismissable-prompt"
|
||||
:triggers="[]"
|
||||
:shown="showSettingsHint"
|
||||
:auto-hide="false"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ButtonStyled circular size="large">
|
||||
<button
|
||||
v-tooltip="showSettingsHint ? undefined : 'Server settings'"
|
||||
type="button"
|
||||
aria-label="Server settings"
|
||||
@click="handleOpenServerSettings"
|
||||
>
|
||||
<SettingsIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<template #popper>
|
||||
<div class="grid grid-cols-[min-content] gap-1">
|
||||
<div class="flex min-w-48 items-center justify-between gap-8">
|
||||
<h3 class="m-0 whitespace-nowrap text-base font-bold text-contrast">
|
||||
{{ formatMessage(settingsHintMessages.title) }}
|
||||
</h3>
|
||||
<ButtonStyled size="small" circular>
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="formatMessage(settingsHintMessages.dismiss)"
|
||||
@click="dismissSettingsHint"
|
||||
>
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p class="m-0 text-wrap text-sm font-medium leading-tight text-secondary">
|
||||
{{ formatMessage(settingsHintMessages.description) }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</Tooltip>
|
||||
<ButtonStyled circular type="transparent" size="large">
|
||||
<TeleportOverflowMenu
|
||||
:options="serverMenuOptions"
|
||||
:disabled="!!installError"
|
||||
aria-label="More server options"
|
||||
>
|
||||
<MoreVerticalIcon aria-hidden="true" />
|
||||
</TeleportOverflowMenu>
|
||||
</ButtonStyled>
|
||||
</PageHeaderActions>
|
||||
</template>
|
||||
</PageHeader>
|
||||
</div>
|
||||
|
||||
<ServerOnboardingPanelPage v-if="isOnboarding" :browse-modpacks="handleBrowseModpacks" />
|
||||
@@ -322,18 +420,25 @@ import {
|
||||
TransferIcon,
|
||||
TriangleAlertIcon,
|
||||
UsersIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { useStorage, useTimeoutFn } from '@vueuse/core'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { Tooltip } from 'floating-vue'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import Avatar from '#ui/components/base/Avatar.vue'
|
||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||
import ErrorInformationCard from '#ui/components/base/ErrorInformationCard.vue'
|
||||
import NavTabs from '#ui/components/base/NavTabs.vue'
|
||||
import PageHeader from '#ui/components/base/page-header/index.vue'
|
||||
import PageHeaderMetadata from '#ui/components/base/page-header/metadata/index.vue'
|
||||
import PageHeaderMetadataItem from '#ui/components/base/page-header/metadata/page-header-metadata-item.vue'
|
||||
import PageHeaderActions from '#ui/components/base/page-header/page-header-actions.vue'
|
||||
import ServerNotice from '#ui/components/base/ServerNotice.vue'
|
||||
import TeleportOverflowMenu from '#ui/components/base/TeleportOverflowMenu.vue'
|
||||
import ConfirmLeaveModal from '#ui/components/modal/ConfirmLeaveModal.vue'
|
||||
import ServerPanelAdmonitions from '#ui/components/servers/admonitions/ServerPanelAdmonitions.vue'
|
||||
import LoaderIcon from '#ui/components/servers/icons/LoaderIcon.vue'
|
||||
@@ -659,19 +764,11 @@ const {
|
||||
onStateEvent,
|
||||
})
|
||||
|
||||
const serverHeaderLeading = computed(() => [
|
||||
{
|
||||
id: 'server-icon',
|
||||
type: 'component' as const,
|
||||
component: ServerIcon,
|
||||
componentProps: {
|
||||
image: serverImage.value,
|
||||
},
|
||||
class: isNuxt.value ? 'size-20 !rounded-2xl' : 'size-16 !rounded-xl',
|
||||
},
|
||||
])
|
||||
const serverHeaderImage = computed(() =>
|
||||
serverData.value?.is_medal ? 'https://cdn-raw.modrinth.com/medal_icon.webp' : serverImage.value,
|
||||
)
|
||||
|
||||
const showServerUptime = computed(() => props.showUptime && uptimeSeconds.value > 0)
|
||||
const showServerUptime = computed(() => props.showUptime && serverPowerState.value === 'running')
|
||||
|
||||
const formattedUptime = computed(() => formatUptime(uptimeSeconds.value))
|
||||
|
||||
@@ -680,113 +777,19 @@ const serverProjectLink = computed(() => {
|
||||
return `/project/${serverProject.value.slug ?? serverProject.value.id}`
|
||||
})
|
||||
|
||||
const serverHeaderMetadata = computed(() => {
|
||||
const server = serverData.value
|
||||
const items = []
|
||||
|
||||
if (server?.flows?.intro) {
|
||||
items.push({
|
||||
id: 'intro',
|
||||
icon: SettingsIcon,
|
||||
label: 'Configuring server...',
|
||||
class: 'font-semibold',
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
if (server?.loader) {
|
||||
items.push({
|
||||
id: 'loader',
|
||||
icon: LoaderIcon,
|
||||
iconProps: {
|
||||
loader: server.loader,
|
||||
},
|
||||
label: `${formatLoaderLabel(server.loader)} ${server.mc_version}`,
|
||||
})
|
||||
}
|
||||
|
||||
if (server?.net?.domain && !serverPreferences.value.hideSubdomainLabel) {
|
||||
items.push({
|
||||
id: 'server-address',
|
||||
icon: LinkIcon,
|
||||
label: `${server.net.domain}.modrinth.gg`,
|
||||
tooltip: 'Copy server address',
|
||||
onClick: copyServerAddress,
|
||||
})
|
||||
}
|
||||
|
||||
if (showServerUptime.value) {
|
||||
items.push({
|
||||
id: 'uptime',
|
||||
icon: TimerIcon,
|
||||
label: formattedUptime.value,
|
||||
})
|
||||
}
|
||||
|
||||
if (serverProject.value) {
|
||||
items.push({
|
||||
id: 'linked-project',
|
||||
label: 'Linked to',
|
||||
value: serverProject.value.title,
|
||||
valueClass: 'text-primary',
|
||||
avatarSrc: serverProject.value.icon_url,
|
||||
avatarAlt: serverProject.value.title,
|
||||
to: serverProjectLink.value,
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
const serverHeaderActions = computed(() => [
|
||||
const serverMenuOptions = computed(() => [
|
||||
{
|
||||
id: 'server-power',
|
||||
label: 'Server power',
|
||||
kind: 'component' as const,
|
||||
component: PanelServerActionButton,
|
||||
componentProps: {
|
||||
disabled: !!installError.value,
|
||||
},
|
||||
id: 'all-servers',
|
||||
label: 'All servers',
|
||||
icon: ServerAssetIcon,
|
||||
action: () => void router.push('/hosting/manage'),
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
label: 'Server settings',
|
||||
icon: SettingsIcon,
|
||||
labelHidden: true,
|
||||
circular: true,
|
||||
tooltip: showSettingsHint.value ? undefined : 'Server settings',
|
||||
onClick: () => openServerSettingsModal(),
|
||||
prompt: {
|
||||
title: formatMessage(settingsHintMessages.title),
|
||||
description: formatMessage(settingsHintMessages.description),
|
||||
dismissLabel: formatMessage(settingsHintMessages.dismiss),
|
||||
shown: showSettingsHint.value,
|
||||
placement: 'bottom-end',
|
||||
onDismiss: dismissSettingsHint,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'more',
|
||||
label: 'More server options',
|
||||
icon: MoreVerticalIcon,
|
||||
labelHidden: true,
|
||||
circular: true,
|
||||
type: 'transparent' as const,
|
||||
menuActions: [
|
||||
{
|
||||
id: 'all-servers',
|
||||
label: 'All servers',
|
||||
icon: ServerAssetIcon,
|
||||
action: () => router.push('/hosting/manage'),
|
||||
},
|
||||
{
|
||||
id: 'copy-id',
|
||||
label: 'Copy ID',
|
||||
icon: CopyIcon,
|
||||
action: copyServerId,
|
||||
shown: props.showCopyIdAction,
|
||||
},
|
||||
],
|
||||
id: 'copy-id',
|
||||
label: 'Copy ID',
|
||||
icon: CopyIcon,
|
||||
action: copyServerId,
|
||||
shown: props.showCopyIdAction,
|
||||
},
|
||||
])
|
||||
|
||||
@@ -819,6 +822,11 @@ function copyServerId() {
|
||||
void navigator.clipboard.writeText(props.serverId)
|
||||
}
|
||||
|
||||
function handleOpenServerSettings() {
|
||||
openServerSettingsModal()
|
||||
dismissSettingsHint()
|
||||
}
|
||||
|
||||
const isUploading = computed(() => uploadState.value.isUploading)
|
||||
const canSetup = computed(() =>
|
||||
hasServerPermission(serverData.value?.current_user_permissions ?? 0, 'SETUP'),
|
||||
|
||||
Reference in New Issue
Block a user