fix: project page in app not receiving instance context & port version page to app (#6618)

* fix: project page in app not receiving instance context

* prepr

* feat: port version page to app

* commonize keys

* whoops bring those back
This commit is contained in:
Prospector
2026-07-06 15:17:25 -07:00
committed by GitHub
parent 8d20fd82db
commit 0905ea72f6
12 changed files with 262 additions and 445 deletions
@@ -410,12 +410,27 @@
"app.project.install-button.already-installed": { "app.project.install-button.already-installed": {
"message": "This project is already installed" "message": "This project is already installed"
}, },
"app.project.install-button.switch-version": {
"message": "Switch version"
},
"app.project.install-context.back-to-browse": { "app.project.install-context.back-to-browse": {
"message": "Back to discover" "message": "Back to discover"
}, },
"app.project.install-context.install-content-to-instance": { "app.project.install-context.install-content-to-instance": {
"message": "Install content to instance" "message": "Install content to instance"
}, },
"app.project.version.all-versions": {
"message": "All versions"
},
"app.project.version.download-in-browser": {
"message": "Download in browser"
},
"app.project.version.installing": {
"message": "Installing"
},
"app.project.versions.already-installed": {
"message": "Already installed"
},
"app.settings.developer-mode-enabled": { "app.settings.developer-mode-enabled": {
"message": "Developer mode enabled." "message": "Developer mode enabled."
}, },
@@ -1351,7 +1351,9 @@ provideContentManager({
title: item.file_name.replace('.disabled', ''), title: item.file_name.replace('.disabled', ''),
icon_url: null, icon_url: null,
}, },
projectLink: item.project?.id ? { path: `/project/${item.project.id}` } : undefined, projectLink: item.project?.id
? { path: `/project/${item.project.id}`, query: { i: props.instance.id } }
: undefined,
version: item.version ?? { version: item.version ?? {
id: item.file_name, id: item.file_name,
version_number: formatMessage(commonMessages.unknownLabel), version_number: formatMessage(commonMessages.unknownLabel),
@@ -1361,6 +1363,7 @@ provideContentManager({
item.project?.id && item.version?.id item.project?.id && item.version?.id
? { ? {
path: `/project/${item.project.id}/version/${item.version.id}`, path: `/project/${item.project.id}/version/${item.version.id}`,
query: { i: props.instance.id },
} }
: undefined, : undefined,
owner: item.owner owner: item.owner
+31 -3
View File
@@ -116,13 +116,27 @@
aria-label="More options" aria-label="More options"
> >
<MoreVerticalIcon aria-hidden="true" /> <MoreVerticalIcon aria-hidden="true" />
<template #open-in-browser> <ExternalIcon /> Open in browser </template> <template #open-in-browser>
<ExternalIcon /> {{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
<template #report> <ReportIcon /> Report </template> <template #report> <ReportIcon /> Report </template>
</OverflowMenu> </OverflowMenu>
</ButtonStyled> </ButtonStyled>
</template> </template>
<template v-else #actions> <template v-else #actions>
<ButtonStyled size="large" color="brand"> <ButtonStyled v-if="showSwitchVersion && onVersionsPage" size="large">
<button v-tooltip="installButtonTooltip" disabled>
<CheckIcon />
{{ formatMessage(commonMessages.installedLabel) }}
</button>
</ButtonStyled>
<ButtonStyled v-else-if="showSwitchVersion" size="large">
<button @click="goToVersions">
<SwapIcon />
{{ formatMessage(messages.switchVersion) }}
</button>
</ButtonStyled>
<ButtonStyled v-else size="large" color="brand">
<button <button
v-tooltip="installButtonTooltip" v-tooltip="installButtonTooltip"
:disabled="installButtonDisabled" :disabled="installButtonDisabled"
@@ -171,7 +185,9 @@
aria-label="More options" aria-label="More options"
> >
<MoreVerticalIcon aria-hidden="true" /> <MoreVerticalIcon aria-hidden="true" />
<template #open-in-browser> <ExternalIcon /> Open in browser </template> <template #open-in-browser>
<ExternalIcon /> {{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
<template #follow> <HeartIcon /> Follow </template> <template #follow> <HeartIcon /> Follow </template>
<template #save> <BookmarkIcon /> Save </template> <template #save> <BookmarkIcon /> Save </template>
<template #report> <ReportIcon /> Report </template> <template #report> <ReportIcon /> Report </template>
@@ -294,6 +310,7 @@ import relativeTime from 'dayjs/plugin/relativeTime'
import { computed, onUnmounted, ref, shallowRef, watch } from 'vue' import { computed, onUnmounted, ref, shallowRef, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { SwapIcon } from '@/assets/icons/index.js'
import ContextMenu from '@/components/ui/ContextMenu.vue' import ContextMenu from '@/components/ui/ContextMenu.vue'
import InstanceIndicator from '@/components/ui/InstanceIndicator.vue' import InstanceIndicator from '@/components/ui/InstanceIndicator.vue'
import { import {
@@ -349,6 +366,10 @@ const messages = defineMessages({
id: 'app.project.install-button.already-installed', id: 'app.project.install-button.already-installed',
defaultMessage: 'This project is already installed', defaultMessage: 'This project is already installed',
}, },
switchVersion: {
id: 'app.project.install-button.switch-version',
defaultMessage: 'Switch version',
},
}) })
const { installingServerProjects, playServerProject, showAddServerToInstanceModal } = const { installingServerProjects, playServerProject, showAddServerToInstanceModal } =
@@ -520,6 +541,13 @@ const installButtonTooltip = computed(() => {
return null return null
}) })
const showSwitchVersion = computed(() => !!instance.value && installed.value)
const onVersionsPage = computed(() => route.name === 'Versions')
function goToVersions() {
router.push(versionsHref.value)
}
const [allLoaders, allGameVersions] = await Promise.all([ const [allLoaders, allGameVersions] = await Promise.all([
get_loaders().catch(handleError).then(ref), get_loaders().catch(handleError).then(ref),
get_game_versions().catch(handleError).then(ref), get_game_versions().catch(handleError).then(ref),
+165 -429
View File
@@ -1,259 +1,152 @@
<template> <template>
<div> <div class="flex flex-col">
<Card> <router-link
<Breadcrumbs class="mb-4 flex w-fit items-center gap-2 rounded-lg px-2 py-0.5 pl-0 text-link"
:current-title="version.name" :to="buildProjectHref(`/project/${route.params.id}/versions`)"
:link-stack="[ >
{ <ChevronLeftIcon class="shrink-0" /> {{ formatMessage(messages.allVersions) }}
href: buildProjectHref(`/project/${route.params.id}/versions`), </router-link>
label: 'Versions', <VersionPage
}, v-if="version"
]" :version="version"
/> :enrichment="enrichment"
<div class="version-title"> :enrichment-loading="enrichmentLoading"
<h2>{{ version.name }}</h2> :members="members"
</div> :dependency-link-creator="createDependencyLink"
<div class="button-group"> >
<template #headerActions>
<ButtonStyled color="brand"> <ButtonStyled color="brand">
<button <button
:disabled="installing || (installed && installedVersion === version.id)" :disabled="installing || (installed && installedVersion === version.id)"
@click="() => install(version.id)" @click="() => version && install(version.id)"
> >
<DownloadIcon v-if="!installed" /> <DownloadIcon v-if="!installed" />
<SwapIcon v-else-if="installedVersion !== version.id" /> <SwapIcon v-else-if="installedVersion !== version.id" />
<CheckIcon v-else /> <CheckIcon v-else />
{{ {{
installing installing
? 'Installing...' ? formatMessage(messages.installing)
: installed && installedVersion === version.id : installed && installedVersion === version.id
? 'Installed' ? formatMessage(commonMessages.installedLabel)
: 'Install' : installed
? formatMessage(commonMessages.switchToVersionButton)
: formatMessage(commonMessages.installButton)
}} }}
</button> </button>
</ButtonStyled> </ButtonStyled>
<ButtonStyled> <ButtonStyled type="outlined" circular>
<button> <OverflowMenu
<ReportIcon /> v-tooltip="formatMessage(commonMessages.moreOptionsButton)"
Report :options="[
</button> {
</ButtonStyled> id: 'open-in-browser',
<ButtonStyled> link: `https://modrinth.com/${project.project_type}/${project.slug}/version/${version.id}`,
<a external: true,
:href="`https://modrinth.com/mod/${route.params.id}/version/${route.params.version}`" },
rel="external" {
id: 'report',
color: 'red',
hoverFilled: true,
link: `https://modrinth.com/report?item=version&itemID=${version.id}`,
external: true,
},
]"
aria-label="More options"
> >
Modrinth website <MoreVerticalIcon aria-hidden="true" />
<ExternalIcon /> <template #open-in-browser>
<ExternalIcon aria-hidden="true" />
{{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
<template #report>
<ReportIcon aria-hidden="true" /> {{ formatMessage(commonMessages.reportButton) }}
</template>
</OverflowMenu>
</ButtonStyled>
</template>
<template #supplementaryResourceActions="{ file }">
<ButtonStyled>
<a :href="file.url" :download="file.filename" target="_blank">
<DownloadIcon aria-hidden="true" />
{{ formatMessage(messages.downloadInBrowser) }}
</a> </a>
</ButtonStyled> </ButtonStyled>
</div> </template>
</Card> </VersionPage>
<div class="version-container">
<div class="description-cards">
<Card>
<h3 class="card-title">Changelog</h3>
<div class="markdown-body" v-html="renderString(version.changelog ?? '')" />
</Card>
<Card>
<h3 class="card-title">Files</h3>
<Card
v-for="file in version.files"
:key="file.id"
:class="{ primary: file.primary }"
class="file"
>
<span class="label">
<FileIcon />
<span>
<span class="title">
{{ file.filename }}
</span>
({{ formatBytes(file.size) }})
<span v-if="file.primary" class="primary-label"> Primary </span>
</span>
</span>
<ButtonStyled v-if="project.project_type !== 'modpack' || file.primary" color="brand">
<button class="download" :disabled="installed" @click="() => install(version.id)">
<DownloadIcon v-if="!installed" />
<CheckIcon v-else />
{{ installed ? 'Installed' : 'Install' }}
</button>
</ButtonStyled>
</Card>
</Card>
<Card v-if="displayDependencies.length > 0">
<h2>Dependencies</h2>
<div v-for="dependency in displayDependencies" :key="dependency.title">
<router-link v-if="dependency.link" class="btn dependency" :to="dependency.link">
<Avatar size="sm" :src="dependency.icon" />
<div>
<span class="title"> {{ dependency.title }} </span> <br />
<span> {{ dependency.subtitle }} </span>
</div>
</router-link>
<div v-else class="dependency disabled" disabled="">
<Avatar size="sm" :src="dependency.icon" />
<div class="text">
<div class="title">{{ dependency.title }}</div>
<div>{{ dependency.subtitle }}</div>
</div>
</div>
</div>
</Card>
</div>
<Card class="metadata-card">
<h3 class="card-title">Metadata</h3>
<div class="metadata">
<div class="metadata-item">
<span class="metadata-label">Release Channel</span>
<span class="metadata-value"
><Badge
:color="releaseColor(version.version_type)"
:type="
version.version_type.charAt(0).toUpperCase() + version.version_type.slice(1)
"
/></span>
</div>
<div class="metadata-item">
<span class="metadata-label">Version Number</span>
<span class="metadata-value">{{ version.version_number }}</span>
</div>
<div class="metadata-item">
<span class="metadata-label">Loaders</span>
<span class="metadata-value">{{
version.loaders
.map((loader) => loader.charAt(0).toUpperCase() + loader.slice(1))
.join(', ')
}}</span>
</div>
<div class="metadata-item">
<span class="metadata-label">Game Versions</span>
<span class="metadata-value"> {{ version.game_versions.join(', ') }} </span>
</div>
<div class="metadata-item">
<span class="metadata-label">Downloads</span>
<span class="metadata-value">{{ version.downloads }}</span>
</div>
<div class="metadata-item">
<span class="metadata-label">Publication Date</span>
<span class="metadata-value">
{{ formatDateTime(version.date_published) }}
</span>
</div>
<div v-if="author" class="metadata-item">
<span class="metadata-label">Author</span>
<a
:href="`https://modrinth.com/user/${author.user.username}`"
rel="external"
class="metadata-value btn author"
>
<Avatar size="sm" :src="author.user.avatar_url" circle />
<span>
<strong>
{{ author.user.username }}
</strong>
<br />
{{ author.role }}
</span>
</a>
</div>
<div class="metadata-item">
<span class="metadata-label">Version ID</span>
<span class="metadata-value"><CopyCode class="copycode" :text="version.id" /></span>
</div>
</div>
</Card>
</div>
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import { CheckIcon, DownloadIcon, ExternalIcon, FileIcon, ReportIcon } from '@modrinth/assets' import type { Labrinth } from '@modrinth/api-client'
import {
CheckIcon,
ChevronLeftIcon,
DownloadIcon,
ExternalIcon,
MoreVerticalIcon,
ReportIcon,
} from '@modrinth/assets'
import { import {
Avatar,
Badge,
Breadcrumbs,
ButtonStyled, ButtonStyled,
Card, commonMessages,
CopyCode, defineMessages,
useFormatBytes, type DependencyContext,
useFormatDateTime, OverflowMenu,
useVIntl,
VersionPage,
} from '@modrinth/ui' } from '@modrinth/ui'
import { renderString } from '@modrinth/utils' import { ref, watch } from 'vue'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { SwapIcon } from '@/assets/icons' import { SwapIcon } from '@/assets/icons'
import { get_project_many, get_version_many } from '@/helpers/cache.js' import { get_project_many, get_version_many } from '@/helpers/cache.js'
import { releaseColor } from '@/helpers/utils'
import { useBreadcrumbs } from '@/store/breadcrumbs' import { useBreadcrumbs } from '@/store/breadcrumbs'
const formatDateTime = useFormatDateTime({ const { formatMessage } = useVIntl()
timeStyle: 'short',
dateStyle: 'long',
})
const formatBytes = useFormatBytes()
const breadcrumbs = useBreadcrumbs() const messages = defineMessages({
allVersions: {
const route = useRoute() id: 'app.project.version.all-versions',
defaultMessage: 'All versions',
const props = defineProps({
project: {
type: Object,
required: true,
},
versions: {
type: Array,
required: true,
},
members: {
type: Array,
required: true,
},
install: {
type: Function,
required: true,
},
installed: {
type: Boolean,
required: true,
}, },
installing: { installing: {
type: Boolean, id: 'app.project.version.installing',
required: true, defaultMessage: 'Installing',
}, },
installedVersion: { downloadInBrowser: {
type: String, id: 'app.project.version.download-in-browser',
required: true, defaultMessage: 'Download in browser',
}, },
}) })
const breadcrumbs = useBreadcrumbs()
const route = useRoute()
const props = defineProps<{
project: Labrinth.Projects.v2.Project
versions: Labrinth.Versions.v3.Version[]
members: Labrinth.Projects.v3.TeamMember[]
install: (version: string | null) => void
installed: boolean
installing: boolean
installedVersion: string
}>()
const version = ref(props.versions.find((version) => version.id === route.params.version)) const version = ref(props.versions.find((version) => version.id === route.params.version))
breadcrumbs.setName('Version', version.value.name) if (version.value) {
breadcrumbs.setName('Version', version.value.name)
}
watch( const enrichment = ref<Labrinth.Projects.v2.DependencyInfo | undefined>(undefined)
() => props.versions, const enrichmentLoading = ref(false)
async () => {
if (route.params.version) {
version.value = props.versions.find((version) => version.id === route.params.version)
await refreshDisplayDependencies()
breadcrumbs.setName('Version', version.value.name)
}
},
)
const author = computed(() => function buildProjectHref(path: string): string {
props.members ? props.members.find((member) => member.user.id === version.value.author_id) : null,
)
const displayDependencies = ref({})
function buildProjectHref(path) {
const params = new URLSearchParams() const params = new URLSearchParams()
for (const [key, val] of Object.entries(route.query)) { for (const [key, val] of Object.entries(route.query)) {
if (Array.isArray(val)) { if (Array.isArray(val)) {
for (const v of val) params.append(key, v) for (const v of val) {
if (v != null) params.append(key, v)
}
} else if (val) { } else if (val) {
params.append(key, String(val)) params.append(key, String(val))
} }
@@ -262,222 +155,65 @@ function buildProjectHref(path) {
return qs ? `${path}?${qs}` : path return qs ? `${path}?${qs}` : path
} }
async function refreshDisplayDependencies() { function createDependencyLink(context: DependencyContext): string | undefined {
const projectIds = new Set() if (context.version) {
const versionIds = new Set() return buildProjectHref(`/project/${context.version.project_id}/version/${context.version.id}`)
if (version.value.dependencies) {
for (const dependency of version.value.dependencies) {
if (dependency.project_id) {
projectIds.add(dependency.project_id)
}
if (dependency.version_id) {
versionIds.add(dependency.version_id)
}
}
} }
const [projectDeps, versionDeps] = await Promise.all([ if (context.project) {
get_project_many([...projectIds]), return buildProjectHref(`/project/${context.project.id}`)
get_version_many([...versionIds]),
])
const dependencies = {
projects: projectDeps,
versions: versionDeps,
} }
return undefined
displayDependencies.value = version.value.dependencies.map((dependency) => {
const version = dependencies.versions.find((obj) => obj.id === dependency.version_id)
if (version) {
const project = dependencies.projects.find(
(obj) => obj.id === version.project_id || obj.id === dependency.project_id,
)
return {
icon: project?.icon_url,
title: project?.title || project?.name,
subtitle: `Version ${version.version_number} is ${dependency.dependency_type}`,
link: buildProjectHref(`/project/${project.slug}/version/${version.id}`),
}
} else {
const project = dependencies.projects.find((obj) => obj.id === dependency.project_id)
if (project) {
return {
icon: project?.icon_url,
title: project?.title || project?.name,
subtitle: `${dependency.dependency_type}`,
link: buildProjectHref(`/project/${project.slug}`),
}
} else {
return {
icon: null,
title: dependency.file_name,
subtitle: `Added via overrides`,
link: null,
}
}
}
})
} }
await refreshDisplayDependencies()
async function refreshEnrichment() {
if (!version.value) return
const projectIds = new Set<string>()
const versionIds = new Set<string>()
for (const dependency of version.value.dependencies ?? []) {
if (dependency.project_id) {
projectIds.add(dependency.project_id)
}
if (dependency.version_id) {
versionIds.add(dependency.version_id)
}
}
if (projectIds.size === 0 && versionIds.size === 0) {
enrichment.value = { projects: [], versions: [] }
return
}
enrichmentLoading.value = true
try {
const versionResults = versionIds.size > 0 ? await get_version_many([...versionIds]) : []
for (const dependencyVersion of versionResults ?? []) {
if (dependencyVersion.project_id) {
projectIds.add(dependencyVersion.project_id)
}
}
const projectResults = projectIds.size > 0 ? await get_project_many([...projectIds]) : []
enrichment.value = {
projects: projectResults ?? [],
versions: versionResults ?? [],
}
} finally {
enrichmentLoading.value = false
}
}
watch(
() => props.versions,
async () => {
if (route.params.version) {
version.value = props.versions.find((v) => v.id === route.params.version)
if (version.value) {
breadcrumbs.setName('Version', version.value.name)
}
await refreshEnrichment()
}
},
)
await refreshEnrichment()
</script> </script>
<style scoped lang="scss">
.version-container {
display: flex;
flex-direction: row;
gap: 1rem;
}
.version-title {
margin-bottom: 1rem;
h2 {
font-size: var(--font-size-2xl);
font-weight: 700;
color: var(--color-contrast);
margin: 0;
}
}
.dependency {
display: flex;
padding: 0.5rem 1rem 0.5rem 0.5rem;
gap: 0.5rem;
background: var(--color-raised-bg);
color: var(--color-base);
width: 100%;
.title {
font-weight: bolder;
}
:deep(svg) {
margin-right: 0 !important;
}
}
.file {
display: flex;
flex-direction: row;
gap: 0.5rem;
background: var(--color-button-bg);
color: var(--color-base);
padding: 0.5rem 1rem;
.download {
margin-left: auto;
background-color: var(--color-raised-bg);
}
.label {
display: flex;
margin: auto 0 auto;
gap: 0.5rem;
.title {
font-weight: bolder;
word-break: break-all;
}
svg {
min-width: 1.1rem;
min-height: 1.1rem;
width: 1.1rem;
height: 1.1rem;
margin: auto 0;
}
.primary-label {
font-style: italic;
}
}
}
.primary {
background: var(--color-brand-highlight);
color: var(--color-contrast);
}
.button-group {
display: flex;
flex-wrap: wrap;
flex-direction: row;
gap: 0.5rem;
}
.card-title {
font-size: var(--font-size-lg);
color: var(--color-contrast);
margin: 0 0 0.5rem;
}
.description-cards {
width: 100%;
}
.metadata-card {
width: 20rem;
height: min-content;
}
.metadata {
display: flex;
flex-direction: column;
flex-wrap: wrap;
gap: 1rem;
.metadata-item {
display: flex;
flex-direction: column;
gap: 0.5rem;
.metadata-label {
font-weight: bold;
}
}
}
.author {
display: flex;
flex-direction: row;
gap: 0.5rem;
align-items: center;
text-decoration: none;
color: var(--color-base);
background: var(--color-raised-bg);
padding: 0.5rem;
width: 100%;
box-shadow: none;
}
.markdown-body {
:deep(hr),
:deep(h1),
:deep(h2),
img {
max-width: max(60rem, 90%) !important;
}
:deep(ul),
:deep(ol) {
margin-left: 2rem;
}
}
.copycode {
border: 0;
color: var(--color-contrast);
}
.disabled {
display: flex;
flex-direction: row;
vertical-align: center;
align-items: center;
cursor: not-allowed;
border-radius: var(--radius-lg);
.text {
filter: brightness(0.5);
}
}
</style>
@@ -9,10 +9,18 @@
:version-link="(version) => buildProjectHref(`/project/${project.id}/version/${version.id}`)" :version-link="(version) => buildProjectHref(`/project/${project.id}/version/${version.id}`)"
> >
<template #actions="{ version }"> <template #actions="{ version }">
<ButtonStyled circular type="transparent" color="green"> <ButtonStyled
circular
type="transparent"
:color="installed && version.id === installedVersion ? 'standard' : 'green'"
>
<button <button
v-tooltip=" v-tooltip="
!installed ? 'Install' : version.id !== installedVersion ? 'Swap version' : '' !installed
? formatMessage(commonMessages.installButton)
: version.id !== installedVersion
? formatMessage(commonMessages.switchToVersionButton)
: formatMessage(messages.alreadyInstalled)
" "
:disabled="installing || (installed && version.id === installedVersion)" :disabled="installing || (installed && version.id === installedVersion)"
@click.stop="() => install(version.id)" @click.stop="() => install(version.id)"
@@ -45,11 +53,13 @@
<DownloadIcon aria-hidden="true" /> <DownloadIcon aria-hidden="true" />
Add to another instance Add to another instance
</template> </template>
<template #open-in-browser> <ExternalIcon /> Open in browser </template> <template #open-in-browser>
<ExternalIcon /> {{ formatMessage(commonMessages.openInBrowserButton) }}
</template>
</OverflowMenu> </OverflowMenu>
<a <a
v-else v-else
v-tooltip="`Open in browser`" v-tooltip="formatMessage(commonMessages.openInBrowserButton)"
:href="`https://modrinth.com/${project.project_type}/${project.slug}/version/${version.id}`" :href="`https://modrinth.com/${project.project_type}/${project.slug}/version/${version.id}`"
target="_blank" target="_blank"
> >
@@ -65,9 +75,12 @@
import { CheckIcon, DownloadIcon, ExternalIcon, MoreVerticalIcon } from '@modrinth/assets' import { CheckIcon, DownloadIcon, ExternalIcon, MoreVerticalIcon } from '@modrinth/assets'
import { import {
ButtonStyled, ButtonStyled,
commonMessages,
defineMessages,
injectNotificationManager, injectNotificationManager,
OverflowMenu, OverflowMenu,
ProjectPageVersions, ProjectPageVersions,
useVIntl,
} from '@modrinth/ui' } from '@modrinth/ui'
import { ref } from 'vue' import { ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
@@ -76,8 +89,16 @@ import { SwapIcon } from '@/assets/icons/index.js'
import { get_game_versions, get_loaders } from '@/helpers/tags.js' import { get_game_versions, get_loaders } from '@/helpers/tags.js'
import { useTheming } from '@/store/theme.ts' import { useTheming } from '@/store/theme.ts'
const { formatMessage } = useVIntl()
const themeStore = useTheming() const themeStore = useTheming()
const messages = defineMessages({
alreadyInstalled: {
id: 'app.project.versions.already-installed',
defaultMessage: 'Already installed',
},
})
defineProps({ defineProps({
project: { project: {
type: Object, type: Object,
@@ -257,7 +257,7 @@ const messages = defineMessages({
}, },
managePasskeyModalLoading: { managePasskeyModalLoading: {
id: 'settings.account.security.passkey.modal.loading', id: 'settings.account.security.passkey.modal.loading',
defaultMessage: 'Loading passkeys', defaultMessage: 'Loading passkeys...',
}, },
managePasskeyModalNoPasskeys: { managePasskeyModalNoPasskeys: {
id: 'settings.account.security.passkey.modal.no-passkeys', id: 'settings.account.security.passkey.modal.no-passkeys',
+1 -1
View File
@@ -4206,7 +4206,7 @@
"message": "Last used {ago}" "message": "Last used {ago}"
}, },
"settings.account.security.passkey.modal.loading": { "settings.account.security.passkey.modal.loading": {
"message": "Loading passkeys" "message": "Loading passkeys..."
}, },
"settings.account.security.passkey.modal.never-used": { "settings.account.security.passkey.modal.never-used": {
"message": "Never used" "message": "Never used"
+1 -1
View File
@@ -143,7 +143,7 @@ onMounted(() => {
{{ previewError }} {{ previewError }}
</div> </div>
<div v-if="previewLoading" class="my-4 text-sm text-secondary">Loading preview</div> <div v-if="previewLoading" class="my-4 text-sm text-secondary">Loading preview...</div>
<div v-else> <div v-else>
<div v-if="previewVariables.length" class="mt-2 grid gap-3 md:grid-cols-2"> <div v-if="previewVariables.length" class="mt-2 grid gap-3 md:grid-cols-2">
<label <label
@@ -38,7 +38,7 @@ const messages = defineMessages({
}, },
addFilesModalSearchPlaceholder: { addFilesModalSearchPlaceholder: {
id: 'external-files.permissions-card.add-files-modal.search-placeholder', id: 'external-files.permissions-card.add-files-modal.search-placeholder',
defaultMessage: 'Search files', defaultMessage: 'Search files...',
}, },
addFilesModalNoSearchResults: { addFilesModalNoSearchResults: {
id: 'external-files.permissions-card.add-files-modal.no-search-results', id: 'external-files.permissions-card.add-files-modal.no-search-results',
@@ -216,7 +216,7 @@ defineExpose({ show, hide })
:icon="SearchIcon" :icon="SearchIcon"
type="text" type="text"
autocomplete="off" autocomplete="off"
placeholder="Search external projects" placeholder="Search external projects..."
clearable clearable
wrapper-class="flex-1 min-w-[12rem]" wrapper-class="flex-1 min-w-[12rem]"
:disabled="addFilesMutation.isPending.value" :disabled="addFilesMutation.isPending.value"
@@ -237,14 +237,14 @@ defineExpose({ show, hide })
> >
<span class="text-contrast font-semibold"> <span class="text-contrast font-semibold">
<template v-if="searchNeedsInput"> Enter a search term to get started </template> <template v-if="searchNeedsInput"> Enter a search term to get started </template>
<template v-else-if="isLoading"> Loading external projects </template> <template v-else-if="isLoading"> Loading external projects...</template>
<template v-else> No projects matched that search </template> <template v-else> No projects matched that search </template>
</span> </span>
<span class="text-secondary text-sm"> <span class="text-secondary text-sm">
<template v-if="searchNeedsInput"> <template v-if="searchNeedsInput">
Type at least 3 characters of a project's title to begin browsing. Type at least 3 characters of a project's title to begin browsing.
</template> </template>
<template v-else-if="isLoading"> Loading external projects </template> <template v-else-if="isLoading"> Loading external projects...</template>
<template v-else> No projects matched that search </template> <template v-else> No projects matched that search </template>
</span> </span>
</div> </div>
+7 -1
View File
@@ -221,6 +221,9 @@
"button.open-folder": { "button.open-folder": {
"defaultMessage": "Open folder" "defaultMessage": "Open folder"
}, },
"button.open-in-browser": {
"defaultMessage": "Open in browser"
},
"button.open-in-folder": { "button.open-in-folder": {
"defaultMessage": "Open in folder" "defaultMessage": "Open in folder"
}, },
@@ -290,6 +293,9 @@
"button.stop": { "button.stop": {
"defaultMessage": "Stop" "defaultMessage": "Stop"
}, },
"button.switch-to-version": {
"defaultMessage": "Switch to version"
},
"button.switch-version": { "button.switch-version": {
"defaultMessage": "Switch version" "defaultMessage": "Switch version"
}, },
@@ -912,7 +918,7 @@
"defaultMessage": "No files match your search." "defaultMessage": "No files match your search."
}, },
"external-files.permissions-card.add-files-modal.search-placeholder": { "external-files.permissions-card.add-files-modal.search-placeholder": {
"defaultMessage": "Search files" "defaultMessage": "Search files..."
}, },
"external-files.permissions-card.add-files-modal.selected-count": { "external-files.permissions-card.add-files-modal.selected-count": {
"defaultMessage": "{count, plural, one {# file selected} other {# files selected}}" "defaultMessage": "{count, plural, one {# file selected} other {# files selected}}"
+8
View File
@@ -207,6 +207,10 @@ export const commonMessages = defineMessages({
id: 'button.open-folder', id: 'button.open-folder',
defaultMessage: 'Open folder', defaultMessage: 'Open folder',
}, },
openInBrowserButton: {
id: 'button.open-in-browser',
defaultMessage: 'Open in browser',
},
openInModrinthButton: { openInModrinthButton: {
id: 'button.open-in-modrinth', id: 'button.open-in-modrinth',
defaultMessage: 'Open in Modrinth', defaultMessage: 'Open in Modrinth',
@@ -545,6 +549,10 @@ export const commonMessages = defineMessages({
id: 'button.copy-link', id: 'button.copy-link',
defaultMessage: 'Copy link', defaultMessage: 'Copy link',
}, },
switchToVersionButton: {
id: 'button.switch-to-version',
defaultMessage: 'Switch to version',
},
switchVersionButton: { switchVersionButton: {
id: 'button.switch-version', id: 'button.switch-version',
defaultMessage: 'Switch version', defaultMessage: 'Switch version',