mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 09:34:50 +00:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}}"
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user