mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 12:05:53 +00:00
refactor: componentize project download modal
This commit is contained in:
@@ -0,0 +1,663 @@
|
|||||||
|
<template>
|
||||||
|
<NewModal ref="modal" :on-show="onShow" :on-hide="onHide">
|
||||||
|
<template #title>
|
||||||
|
<Avatar :src="project.icon_url" :alt="project.title" class="icon" size="32px" />
|
||||||
|
<div class="truncate text-lg font-extrabold text-contrast">
|
||||||
|
{{ formatMessage(messages.downloadTitle, { title: project.title }) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<div class="mx-auto flex max-w-[44rem] flex-col gap-4 md:w-[30rem]">
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
project.project_type !== 'plugin' ||
|
||||||
|
project.loaders.some((x) => !tags.loaderData.allPluginLoaders.includes(x))
|
||||||
|
"
|
||||||
|
class="modrinth-app-section contents"
|
||||||
|
>
|
||||||
|
<div class="mx-auto flex w-fit flex-col">
|
||||||
|
<ButtonStyled color="brand">
|
||||||
|
<a class="w-fit" :href="`modrinth://mod/${project.slug}`" @click="installWithApp">
|
||||||
|
<ModrinthIcon aria-hidden="true" />
|
||||||
|
{{ formatMessage(messages.installWithModrinthApp) }}
|
||||||
|
<ExternalIcon aria-hidden="true" />
|
||||||
|
</a>
|
||||||
|
</ButtonStyled>
|
||||||
|
<Accordion ref="getModrinthAppAccordion">
|
||||||
|
<nuxt-link class="mt-2 flex justify-center text-brand-blue hover:underline" to="/app">
|
||||||
|
{{ formatMessage(messages.dontHaveModrinthApp) }}
|
||||||
|
</nuxt-link>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4 px-4">
|
||||||
|
<div class="flex h-[2px] w-full rounded-2xl bg-button-bg"></div>
|
||||||
|
<span class="flex-shrink-0 text-sm font-semibold text-secondary">
|
||||||
|
{{ formatMessage(commonMessages.orLabel) }}
|
||||||
|
</span>
|
||||||
|
<div class="flex h-[2px] w-full rounded-2xl bg-button-bg"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mx-auto flex w-fit flex-col gap-2">
|
||||||
|
<ButtonStyled v-if="project.game_versions.length === 1">
|
||||||
|
<div class="disabled button-like">
|
||||||
|
<GameIcon aria-hidden="true" />
|
||||||
|
{{
|
||||||
|
currentGameVersion
|
||||||
|
? formatMessage(messages.gameVersionLabel, { version: currentGameVersion })
|
||||||
|
: formatMessage(messages.gameVersionError)
|
||||||
|
}}
|
||||||
|
<InfoIcon
|
||||||
|
v-tooltip="
|
||||||
|
formatMessage(messages.gameVersionTooltip, {
|
||||||
|
title: project.title,
|
||||||
|
version: currentGameVersion,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
class="ml-auto size-5"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ButtonStyled>
|
||||||
|
<Accordion
|
||||||
|
v-else
|
||||||
|
ref="gameVersionAccordion"
|
||||||
|
class="accordion-with-bg"
|
||||||
|
@on-open="
|
||||||
|
() => {
|
||||||
|
platformAccordion?.close()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<GameIcon aria-hidden="true" />
|
||||||
|
{{
|
||||||
|
currentGameVersion
|
||||||
|
? formatMessage(messages.gameVersionLabel, { version: currentGameVersion })
|
||||||
|
: formatMessage(messages.selectGameVersion)
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
<label for="game-versions-filtering" hidden>{{
|
||||||
|
formatMessage(messages.searchGameVersionsLabel)
|
||||||
|
}}</label>
|
||||||
|
<StyledInput
|
||||||
|
id="game-versions-filtering"
|
||||||
|
ref="gameVersionFilterInput"
|
||||||
|
v-model="versionFilter"
|
||||||
|
type="search"
|
||||||
|
autocomplete="off"
|
||||||
|
:icon="SearchIcon"
|
||||||
|
:placeholder="formatMessage(messages.searchGameVersions)"
|
||||||
|
wrapper-class="mb-2 w-full"
|
||||||
|
/>
|
||||||
|
<ScrollablePanel :class="project.game_versions.length > 4 ? 'h-[15rem]' : ''">
|
||||||
|
<ButtonStyled
|
||||||
|
v-for="gameVersion in filteredGameVersions"
|
||||||
|
:key="gameVersion"
|
||||||
|
:color="currentGameVersion === gameVersion ? 'brand' : 'standard'"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-tooltip="
|
||||||
|
!possibleGameVersions.includes(gameVersion)
|
||||||
|
? formatMessage(messages.gameVersionUnsupportedTooltip, {
|
||||||
|
title: project.title,
|
||||||
|
gameVersion: gameVersion,
|
||||||
|
platform: currentPlatformText,
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
"
|
||||||
|
:class="{
|
||||||
|
'looks-disabled !text-brand-red':
|
||||||
|
!possibleGameVersions.includes(gameVersion),
|
||||||
|
}"
|
||||||
|
@click="selectGameVersion(gameVersion)"
|
||||||
|
>
|
||||||
|
{{ gameVersion }}
|
||||||
|
<CheckIcon v-if="userSelectedGameVersion === gameVersion" />
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</ScrollablePanel>
|
||||||
|
<Checkbox
|
||||||
|
v-if="showVersionsCheckbox"
|
||||||
|
v-model="showAllVersions"
|
||||||
|
class="mx-1"
|
||||||
|
:label="formatMessage(messages.showAllVersions)"
|
||||||
|
:disabled="!!versionFilter"
|
||||||
|
/>
|
||||||
|
</Accordion>
|
||||||
|
<ButtonStyled v-if="project.loaders.length === 1 && project.project_type !== 'resourcepack'">
|
||||||
|
<div class="disabled button-like">
|
||||||
|
<WrenchIcon aria-hidden="true" />
|
||||||
|
{{
|
||||||
|
currentPlatform
|
||||||
|
? formatMessage(messages.platformLabel, {
|
||||||
|
platform: currentPlatformText,
|
||||||
|
})
|
||||||
|
: formatMessage(messages.platformError)
|
||||||
|
}}
|
||||||
|
<InfoIcon
|
||||||
|
v-tooltip="
|
||||||
|
formatMessage(messages.platformTooltip, {
|
||||||
|
title: project.title,
|
||||||
|
platform: currentPlatformText,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
class="ml-auto size-5"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ButtonStyled>
|
||||||
|
<Accordion
|
||||||
|
v-else-if="project.project_type !== 'resourcepack'"
|
||||||
|
ref="platformAccordion"
|
||||||
|
class="accordion-with-bg"
|
||||||
|
@on-open="
|
||||||
|
() => {
|
||||||
|
gameVersionAccordion?.close()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<WrenchIcon aria-hidden="true" />
|
||||||
|
{{
|
||||||
|
currentPlatform
|
||||||
|
? formatMessage(messages.platformLabel, {
|
||||||
|
platform: currentPlatformText,
|
||||||
|
})
|
||||||
|
: formatMessage(messages.selectPlatform)
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
<ScrollablePanel :class="project.loaders.length > 4 ? 'h-[15rem]' : ''">
|
||||||
|
<ButtonStyled
|
||||||
|
v-for="platform in project.loaders.slice().reverse()"
|
||||||
|
:key="platform"
|
||||||
|
:color="currentPlatform === platform ? 'brand' : 'standard'"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-tooltip="
|
||||||
|
!possiblePlatforms.includes(platform)
|
||||||
|
? formatMessage(messages.platformUnsupportedTooltip, {
|
||||||
|
title: project.title,
|
||||||
|
platform: currentPlatformText,
|
||||||
|
gameVersion: currentGameVersion,
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
"
|
||||||
|
:class="{
|
||||||
|
'looks-disabled !text-brand-red': !possiblePlatforms.includes(platform),
|
||||||
|
}"
|
||||||
|
@click="selectPlatform(platform)"
|
||||||
|
>
|
||||||
|
{{ formatMessage(getTagMessage(platform, 'loader')) }}
|
||||||
|
<CheckIcon v-if="userSelectedPlatform === platform" />
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</ScrollablePanel>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
<AutomaticAccordion div class="flex flex-col gap-2">
|
||||||
|
<VersionSummary
|
||||||
|
v-if="filteredRelease"
|
||||||
|
:version="filteredRelease"
|
||||||
|
:decorate-download-url="decorateModalDownloadUrl"
|
||||||
|
@on-download="onDownload"
|
||||||
|
@on-navigate="onVersionNavigate"
|
||||||
|
/>
|
||||||
|
<VersionSummary
|
||||||
|
v-if="filteredBeta"
|
||||||
|
:version="filteredBeta"
|
||||||
|
:decorate-download-url="decorateModalDownloadUrl"
|
||||||
|
@on-download="onDownload"
|
||||||
|
@on-navigate="onVersionNavigate"
|
||||||
|
/>
|
||||||
|
<VersionSummary
|
||||||
|
v-if="filteredAlpha"
|
||||||
|
:version="filteredAlpha"
|
||||||
|
:decorate-download-url="decorateModalDownloadUrl"
|
||||||
|
@on-download="onDownload"
|
||||||
|
@on-navigate="onVersionNavigate"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
v-if="
|
||||||
|
currentPlatform &&
|
||||||
|
currentGameVersion &&
|
||||||
|
!filteredRelease &&
|
||||||
|
!filteredBeta &&
|
||||||
|
!filteredAlpha &&
|
||||||
|
!versionsLoading &&
|
||||||
|
versions.length > 0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
formatMessage(messages.noVersionsAvailable, {
|
||||||
|
gameVersion: currentGameVersion,
|
||||||
|
platform: currentPlatformText,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</AutomaticAccordion>
|
||||||
|
<ServersPromo
|
||||||
|
v-if="flags.showProjectPageDownloadModalServersPromo"
|
||||||
|
:link="`/hosting#plan`"
|
||||||
|
@close="
|
||||||
|
() => {
|
||||||
|
flags.showProjectPageDownloadModalServersPromo = false
|
||||||
|
saveFeatureFlags()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NewModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
ExternalIcon,
|
||||||
|
GameIcon,
|
||||||
|
InfoIcon,
|
||||||
|
ModrinthIcon,
|
||||||
|
SearchIcon,
|
||||||
|
WrenchIcon,
|
||||||
|
} from '@modrinth/assets'
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
Checkbox,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
getTagMessage,
|
||||||
|
NewModal,
|
||||||
|
ScrollablePanel,
|
||||||
|
ServersPromo,
|
||||||
|
StyledInput,
|
||||||
|
useDebugLogger,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
import VersionSummary from '@modrinth/ui/src/components/version/VersionSummary.vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { computed, nextTick, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
import { navigateTo } from '#app'
|
||||||
|
import Accordion from '~/components/ui/Accordion.vue'
|
||||||
|
import AutomaticAccordion from '~/components/ui/AutomaticAccordion.vue'
|
||||||
|
import { saveFeatureFlags } from '~/composables/featureFlags.ts'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
project: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
versions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
versionsLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
downloadReason: {
|
||||||
|
type: String,
|
||||||
|
default: 'standalone',
|
||||||
|
},
|
||||||
|
loadVersions: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['download'])
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const flags = useFeatureFlags()
|
||||||
|
const { createProjectDownloadUrl } = useCdnDownloadContext()
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
const debug = useDebugLogger('DownloadModal')
|
||||||
|
|
||||||
|
const modal = ref()
|
||||||
|
const userSelectedGameVersion = ref(null)
|
||||||
|
const userSelectedPlatform = ref(null)
|
||||||
|
const showAllVersions = ref(false)
|
||||||
|
const versionFilter = ref('')
|
||||||
|
const gameVersionFilterInput = ref()
|
||||||
|
const gameVersionAccordion = ref()
|
||||||
|
const platformAccordion = ref()
|
||||||
|
const getModrinthAppAccordion = ref()
|
||||||
|
|
||||||
|
const currentGameVersion = computed(() => {
|
||||||
|
return (
|
||||||
|
userSelectedGameVersion.value ||
|
||||||
|
(props.project.game_versions.length === 1 && props.project.game_versions[0])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const possibleGameVersions = computed(() => {
|
||||||
|
return props.versions
|
||||||
|
.filter((x) => !currentPlatform.value || x.loaders.includes(currentPlatform.value))
|
||||||
|
.flatMap((x) => x.game_versions)
|
||||||
|
})
|
||||||
|
|
||||||
|
const possiblePlatforms = computed(() => {
|
||||||
|
return props.versions
|
||||||
|
.filter((x) => !currentGameVersion.value || x.game_versions.includes(currentGameVersion.value))
|
||||||
|
.flatMap((x) => x.loaders)
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentPlatform = computed(() => {
|
||||||
|
return (
|
||||||
|
userSelectedPlatform.value ||
|
||||||
|
(props.project.loaders.length === 1 && props.project.loaders[0])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentPlatformText = computed(() => {
|
||||||
|
if (!currentPlatform.value) return null
|
||||||
|
return formatMessage(getTagMessage(currentPlatform.value, 'loader'))
|
||||||
|
})
|
||||||
|
|
||||||
|
const releaseVersions = computed(() => {
|
||||||
|
const set = new Set()
|
||||||
|
for (const gameVersion of props.tags.gameVersions || []) {
|
||||||
|
if (gameVersion?.version && gameVersion.version_type === 'release') {
|
||||||
|
set.add(gameVersion.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
})
|
||||||
|
|
||||||
|
const nonReleaseVersions = computed(() => {
|
||||||
|
const set = new Set()
|
||||||
|
for (const gameVersion of props.tags.gameVersions || []) {
|
||||||
|
if (gameVersion?.version && gameVersion.version_type !== 'release') {
|
||||||
|
set.add(gameVersion.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
})
|
||||||
|
|
||||||
|
const showVersionsCheckbox = computed(() => {
|
||||||
|
let hasRelease = false
|
||||||
|
let hasNonRelease = false
|
||||||
|
|
||||||
|
for (const version of props.project.game_versions) {
|
||||||
|
if (isReleaseGameVersion(version)) {
|
||||||
|
hasRelease = true
|
||||||
|
} else {
|
||||||
|
hasNonRelease = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasRelease && hasNonRelease) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredGameVersions = computed(() => {
|
||||||
|
return props.project.game_versions
|
||||||
|
.filter(
|
||||||
|
(x) =>
|
||||||
|
(versionFilter.value && x.includes(versionFilter.value)) ||
|
||||||
|
(!versionFilter.value && (showAllVersions.value || isReleaseGameVersion(x))),
|
||||||
|
)
|
||||||
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredVersions = computed(() => {
|
||||||
|
const result = props.versions.filter(
|
||||||
|
(x) =>
|
||||||
|
x.game_versions?.includes(currentGameVersion.value) &&
|
||||||
|
(x.loaders?.includes(currentPlatform.value) || props.project.project_type === 'resourcepack'),
|
||||||
|
)
|
||||||
|
debug('filteredVersions', {
|
||||||
|
total: props.versions.length,
|
||||||
|
filtered: result.length,
|
||||||
|
currentGameVersion: currentGameVersion.value,
|
||||||
|
currentPlatform: currentPlatform.value,
|
||||||
|
sampleLoaders: props.versions.slice(0, 3).map((v) => v.loaders),
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredRelease = computed(() => {
|
||||||
|
return filteredVersions.value.find((x) => x.version_type === 'release')
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredBeta = computed(() => {
|
||||||
|
return filteredVersions.value.find(
|
||||||
|
(x) =>
|
||||||
|
x.version_type === 'beta' &&
|
||||||
|
(!filteredRelease.value ||
|
||||||
|
dayjs(x.date_published).isAfter(dayjs(filteredRelease.value.date_published))),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredAlpha = computed(() => {
|
||||||
|
return filteredVersions.value.find(
|
||||||
|
(x) =>
|
||||||
|
x.version_type === 'alpha' &&
|
||||||
|
(!filteredRelease.value ||
|
||||||
|
dayjs(x.date_published).isAfter(dayjs(filteredRelease.value.date_published))) &&
|
||||||
|
(!filteredBeta.value ||
|
||||||
|
dayjs(x.date_published).isAfter(dayjs(filteredBeta.value.date_published))),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
dontHaveModrinthApp: {
|
||||||
|
id: 'project.download.no-app',
|
||||||
|
defaultMessage: "Don't have Modrinth App?",
|
||||||
|
},
|
||||||
|
downloadTitle: {
|
||||||
|
id: 'project.download.title',
|
||||||
|
defaultMessage: 'Download {title}',
|
||||||
|
},
|
||||||
|
gameVersionError: {
|
||||||
|
id: 'project.download.game-version-error',
|
||||||
|
defaultMessage: 'Error: no game versions found',
|
||||||
|
},
|
||||||
|
gameVersionLabel: {
|
||||||
|
id: 'project.download.game-version',
|
||||||
|
defaultMessage: 'Game version: {version}',
|
||||||
|
},
|
||||||
|
gameVersionTooltip: {
|
||||||
|
id: 'project.download.game-version-tooltip',
|
||||||
|
defaultMessage: '{title} is only available for {version}',
|
||||||
|
},
|
||||||
|
gameVersionUnsupportedTooltip: {
|
||||||
|
id: 'project.download.game-version-unsupported-tooltip',
|
||||||
|
defaultMessage: '{title} does not support {gameVersion} for {platform}',
|
||||||
|
},
|
||||||
|
installWithModrinthApp: {
|
||||||
|
id: 'project.download.install-with-app',
|
||||||
|
defaultMessage: 'Install with Modrinth App',
|
||||||
|
},
|
||||||
|
noVersionsAvailable: {
|
||||||
|
id: 'project.download.no-versions-available',
|
||||||
|
defaultMessage: 'No versions available for {gameVersion} and {platform}.',
|
||||||
|
},
|
||||||
|
platformError: {
|
||||||
|
id: 'project.download.platform-error',
|
||||||
|
defaultMessage: 'Error: no platforms found',
|
||||||
|
},
|
||||||
|
platformLabel: {
|
||||||
|
id: 'project.download.platform',
|
||||||
|
defaultMessage: 'Platform: {platform}',
|
||||||
|
},
|
||||||
|
platformTooltip: {
|
||||||
|
id: 'project.download.platform-tooltip',
|
||||||
|
defaultMessage: '{title} is only available for {platform}',
|
||||||
|
},
|
||||||
|
platformUnsupportedTooltip: {
|
||||||
|
id: 'project.download.platform-unsupported-tooltip',
|
||||||
|
defaultMessage: '{title} does not support {platform} for {gameVersion}',
|
||||||
|
},
|
||||||
|
searchGameVersions: {
|
||||||
|
id: 'project.download.search-game-versions',
|
||||||
|
defaultMessage: 'Search game versions...',
|
||||||
|
},
|
||||||
|
searchGameVersionsLabel: {
|
||||||
|
id: 'project.download.search-game-versions-label',
|
||||||
|
defaultMessage: 'Search game versions...',
|
||||||
|
},
|
||||||
|
selectGameVersion: {
|
||||||
|
id: 'project.download.select-game-version',
|
||||||
|
defaultMessage: 'Select game version',
|
||||||
|
},
|
||||||
|
selectPlatform: {
|
||||||
|
id: 'project.download.select-platform',
|
||||||
|
defaultMessage: 'Select platform',
|
||||||
|
},
|
||||||
|
showAllVersions: {
|
||||||
|
id: 'project.download.show-all-versions',
|
||||||
|
defaultMessage: 'Show all versions',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function decorateModalDownloadUrl(url) {
|
||||||
|
return createProjectDownloadUrl(url, {
|
||||||
|
reason: props.downloadReason,
|
||||||
|
gameVersion: currentGameVersion.value ?? undefined,
|
||||||
|
loader: currentPlatform.value ?? undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReleaseGameVersion(version) {
|
||||||
|
if (releaseVersions.value.has(version)) return true
|
||||||
|
if (nonReleaseVersions.value.has(version)) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDownloadQuery() {
|
||||||
|
navigateTo(
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
...route.query,
|
||||||
|
...(userSelectedGameVersion.value && {
|
||||||
|
version: userSelectedGameVersion.value,
|
||||||
|
}),
|
||||||
|
...(userSelectedPlatform.value && {
|
||||||
|
loader: userSelectedPlatform.value,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
hash: route.hash,
|
||||||
|
},
|
||||||
|
{ replace: true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectGameVersion(gameVersion) {
|
||||||
|
userSelectedGameVersion.value = gameVersion
|
||||||
|
gameVersionAccordion.value?.close()
|
||||||
|
|
||||||
|
if (!currentPlatform.value) {
|
||||||
|
platformAccordion.value?.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDownloadQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPlatform(platform) {
|
||||||
|
userSelectedPlatform.value = platform
|
||||||
|
platformAccordion.value?.close()
|
||||||
|
|
||||||
|
if (!currentGameVersion.value) {
|
||||||
|
gameVersionAccordion.value?.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDownloadQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
function installWithApp() {
|
||||||
|
setTimeout(() => {
|
||||||
|
getModrinthAppAccordion.value?.open()
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShow() {
|
||||||
|
debug('on-show fired')
|
||||||
|
props.loadVersions()
|
||||||
|
navigateTo({ query: route.query, hash: '#download' }, { replace: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHide() {
|
||||||
|
navigateTo({ query: route.query, hash: '' }, { replace: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
function show(event) {
|
||||||
|
modal.value?.show(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide(event) {
|
||||||
|
modal.value?.hide(event)
|
||||||
|
userSelectedPlatform.value = null
|
||||||
|
userSelectedGameVersion.value = null
|
||||||
|
showAllVersions.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDownload(event) {
|
||||||
|
emit('download')
|
||||||
|
setTimeout(() => {
|
||||||
|
hide(event)
|
||||||
|
}, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onVersionNavigate(url) {
|
||||||
|
hide()
|
||||||
|
nextTick(() => {
|
||||||
|
navigateTo(url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function openFromHash() {
|
||||||
|
if (!modal.value || route.hash !== '#download') return
|
||||||
|
|
||||||
|
debug('hash #download watch fired, opening modal')
|
||||||
|
props.loadVersions()
|
||||||
|
modal.value.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { version, loader } = route.query
|
||||||
|
|
||||||
|
if (
|
||||||
|
props.project.game_versions.length > 0 &&
|
||||||
|
props.project.game_versions.every((projectVersion) => !isReleaseGameVersion(projectVersion))
|
||||||
|
) {
|
||||||
|
showAllVersions.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version !== undefined && props.project.game_versions.includes(version)) {
|
||||||
|
userSelectedGameVersion.value = version
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loader !== undefined && props.project.loaders.includes(loader)) {
|
||||||
|
userSelectedPlatform.value = loader
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.hash === '#download' || version !== undefined || loader !== undefined) {
|
||||||
|
debug('eager loadVersions from setup', { hash: route.hash, version, loader })
|
||||||
|
props.loadVersions()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(modal, openFromHash)
|
||||||
|
watch(() => route.hash, openFromHash)
|
||||||
|
|
||||||
|
defineExpose({ show, hide })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.accordion-with-bg) {
|
||||||
|
@apply rounded-2xl bg-bg p-2;
|
||||||
|
--scrollable-pane-bg: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: none) and (max-width: 767px) {
|
||||||
|
.modrinth-app-section {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -88,338 +88,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NewModal
|
<ProjectDownloadModal
|
||||||
ref="downloadModal"
|
ref="downloadModal"
|
||||||
:on-show="
|
:project="project"
|
||||||
() => {
|
:versions="versions"
|
||||||
debug('on-show fired')
|
:versions-loading="versionsLoading"
|
||||||
loadVersions()
|
:tags="tags"
|
||||||
navigateTo({ query: route.query, hash: '#download' }, { replace: true })
|
:download-reason="downloadReason"
|
||||||
}
|
:load-versions="loadVersions"
|
||||||
"
|
@download="triggerDownloadAnimation"
|
||||||
:on-hide="
|
/>
|
||||||
() => {
|
|
||||||
navigateTo({ query: route.query, hash: '' }, { replace: true })
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template #title>
|
|
||||||
<Avatar :src="project.icon_url" :alt="project.title" class="icon" size="32px" />
|
|
||||||
<div class="truncate text-lg font-extrabold text-contrast">
|
|
||||||
{{ formatMessage(messages.downloadTitle, { title: project.title }) }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #default>
|
|
||||||
<div class="mx-auto flex max-w-[44rem] flex-col gap-4 md:w-[30rem]">
|
|
||||||
<div
|
|
||||||
v-if="
|
|
||||||
project.project_type !== 'plugin' ||
|
|
||||||
project.loaders.some((x) => !tags.loaderData.allPluginLoaders.includes(x))
|
|
||||||
"
|
|
||||||
class="modrinth-app-section contents"
|
|
||||||
>
|
|
||||||
<div class="mx-auto flex w-fit flex-col">
|
|
||||||
<ButtonStyled color="brand">
|
|
||||||
<a
|
|
||||||
class="w-fit"
|
|
||||||
:href="`modrinth://mod/${project.slug}`"
|
|
||||||
@click="() => installWithApp()"
|
|
||||||
>
|
|
||||||
<ModrinthIcon aria-hidden="true" />
|
|
||||||
{{ formatMessage(messages.installWithModrinthApp) }}
|
|
||||||
<ExternalIcon aria-hidden="true" />
|
|
||||||
</a>
|
|
||||||
</ButtonStyled>
|
|
||||||
<Accordion ref="getModrinthAppAccordion">
|
|
||||||
<nuxt-link
|
|
||||||
class="mt-2 flex justify-center text-brand-blue hover:underline"
|
|
||||||
to="/app"
|
|
||||||
>
|
|
||||||
{{ formatMessage(messages.dontHaveModrinthApp) }}
|
|
||||||
</nuxt-link>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-4 px-4">
|
|
||||||
<div class="flex h-[2px] w-full rounded-2xl bg-button-bg"></div>
|
|
||||||
<span class="flex-shrink-0 text-sm font-semibold text-secondary">
|
|
||||||
{{ formatMessage(commonMessages.orLabel) }}
|
|
||||||
</span>
|
|
||||||
<div class="flex h-[2px] w-full rounded-2xl bg-button-bg"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mx-auto flex w-fit flex-col gap-2">
|
|
||||||
<ButtonStyled v-if="project.game_versions.length === 1">
|
|
||||||
<div class="disabled button-like">
|
|
||||||
<GameIcon aria-hidden="true" />
|
|
||||||
{{
|
|
||||||
currentGameVersion
|
|
||||||
? formatMessage(messages.gameVersionLabel, { version: currentGameVersion })
|
|
||||||
: formatMessage(messages.gameVersionError)
|
|
||||||
}}
|
|
||||||
<InfoIcon
|
|
||||||
v-tooltip="
|
|
||||||
formatMessage(messages.gameVersionTooltip, {
|
|
||||||
title: project.title,
|
|
||||||
version: currentGameVersion,
|
|
||||||
})
|
|
||||||
"
|
|
||||||
class="ml-auto size-5"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ButtonStyled>
|
|
||||||
<Accordion
|
|
||||||
v-else
|
|
||||||
ref="gameVersionAccordion"
|
|
||||||
class="accordion-with-bg"
|
|
||||||
@on-open="
|
|
||||||
() => {
|
|
||||||
if (platformAccordion) {
|
|
||||||
platformAccordion.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template #title>
|
|
||||||
<GameIcon aria-hidden="true" />
|
|
||||||
{{
|
|
||||||
currentGameVersion
|
|
||||||
? formatMessage(messages.gameVersionLabel, { version: currentGameVersion })
|
|
||||||
: formatMessage(messages.selectGameVersion)
|
|
||||||
}}
|
|
||||||
</template>
|
|
||||||
<label for="game-versions-filtering" hidden>{{
|
|
||||||
formatMessage(messages.searchGameVersionsLabel)
|
|
||||||
}}</label>
|
|
||||||
<StyledInput
|
|
||||||
id="game-versions-filtering"
|
|
||||||
ref="gameVersionFilterInput"
|
|
||||||
v-model="versionFilter"
|
|
||||||
type="search"
|
|
||||||
autocomplete="off"
|
|
||||||
:icon="SearchIcon"
|
|
||||||
:placeholder="formatMessage(messages.searchGameVersions)"
|
|
||||||
wrapper-class="mb-2 w-full"
|
|
||||||
/>
|
|
||||||
<ScrollablePanel :class="project.game_versions.length > 4 ? 'h-[15rem]' : ''">
|
|
||||||
<ButtonStyled
|
|
||||||
v-for="gameVersion in project.game_versions
|
|
||||||
.filter(
|
|
||||||
(x) =>
|
|
||||||
(versionFilter && x.includes(versionFilter)) ||
|
|
||||||
(!versionFilter && (showAllVersions || isReleaseGameVersion(x))),
|
|
||||||
)
|
|
||||||
.slice()
|
|
||||||
.reverse()"
|
|
||||||
:key="gameVersion"
|
|
||||||
:color="currentGameVersion === gameVersion ? 'brand' : 'standard'"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-tooltip="
|
|
||||||
!possibleGameVersions.includes(gameVersion)
|
|
||||||
? formatMessage(messages.gameVersionUnsupportedTooltip, {
|
|
||||||
title: project.title,
|
|
||||||
gameVersion: gameVersion,
|
|
||||||
platform: currentPlatformText,
|
|
||||||
})
|
|
||||||
: null
|
|
||||||
"
|
|
||||||
:class="{
|
|
||||||
'looks-disabled !text-brand-red':
|
|
||||||
!possibleGameVersions.includes(gameVersion),
|
|
||||||
}"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
userSelectedGameVersion = gameVersion
|
|
||||||
gameVersionAccordion.close()
|
|
||||||
if (!currentPlatform && platformAccordion) {
|
|
||||||
platformAccordion.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateTo(
|
|
||||||
{
|
|
||||||
query: {
|
|
||||||
...route.query,
|
|
||||||
...(userSelectedGameVersion && {
|
|
||||||
version: userSelectedGameVersion,
|
|
||||||
}),
|
|
||||||
...(userSelectedPlatform && {
|
|
||||||
loader: userSelectedPlatform,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
hash: route.hash,
|
|
||||||
},
|
|
||||||
{ replace: true },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ gameVersion }}
|
|
||||||
<CheckIcon v-if="userSelectedGameVersion === gameVersion" />
|
|
||||||
</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
</ScrollablePanel>
|
|
||||||
<Checkbox
|
|
||||||
v-if="showVersionsCheckbox"
|
|
||||||
v-model="showAllVersions"
|
|
||||||
class="mx-1"
|
|
||||||
:label="formatMessage(messages.showAllVersions)"
|
|
||||||
:disabled="!!versionFilter"
|
|
||||||
/>
|
|
||||||
</Accordion>
|
|
||||||
<ButtonStyled
|
|
||||||
v-if="project.loaders.length === 1 && project.project_type !== 'resourcepack'"
|
|
||||||
>
|
|
||||||
<div class="disabled button-like">
|
|
||||||
<WrenchIcon aria-hidden="true" />
|
|
||||||
{{
|
|
||||||
currentPlatform
|
|
||||||
? formatMessage(messages.platformLabel, {
|
|
||||||
platform: currentPlatformText,
|
|
||||||
})
|
|
||||||
: formatMessage(messages.platformError)
|
|
||||||
}}
|
|
||||||
<InfoIcon
|
|
||||||
v-tooltip="
|
|
||||||
formatMessage(messages.platformTooltip, {
|
|
||||||
title: project.title,
|
|
||||||
platform: currentPlatformText,
|
|
||||||
})
|
|
||||||
"
|
|
||||||
class="ml-auto size-5"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ButtonStyled>
|
|
||||||
<Accordion
|
|
||||||
v-else-if="project.project_type !== 'resourcepack'"
|
|
||||||
ref="platformAccordion"
|
|
||||||
class="accordion-with-bg"
|
|
||||||
@on-open="
|
|
||||||
() => {
|
|
||||||
if (gameVersionAccordion) {
|
|
||||||
gameVersionAccordion.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template #title>
|
|
||||||
<WrenchIcon aria-hidden="true" />
|
|
||||||
{{
|
|
||||||
currentPlatform
|
|
||||||
? formatMessage(messages.platformLabel, {
|
|
||||||
platform: currentPlatformText,
|
|
||||||
})
|
|
||||||
: formatMessage(messages.selectPlatform)
|
|
||||||
}}
|
|
||||||
</template>
|
|
||||||
<ScrollablePanel :class="project.loaders.length > 4 ? 'h-[15rem]' : ''">
|
|
||||||
<ButtonStyled
|
|
||||||
v-for="platform in project.loaders.slice().reverse()"
|
|
||||||
:key="platform"
|
|
||||||
:color="currentPlatform === platform ? 'brand' : 'standard'"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-tooltip="
|
|
||||||
!possiblePlatforms.includes(platform)
|
|
||||||
? formatMessage(messages.platformUnsupportedTooltip, {
|
|
||||||
title: project.title,
|
|
||||||
platform: currentPlatformText,
|
|
||||||
gameVersion: currentGameVersion,
|
|
||||||
})
|
|
||||||
: null
|
|
||||||
"
|
|
||||||
:class="{
|
|
||||||
'looks-disabled !text-brand-red': !possiblePlatforms.includes(platform),
|
|
||||||
}"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
userSelectedPlatform = platform
|
|
||||||
|
|
||||||
platformAccordion.close()
|
|
||||||
if (!currentGameVersion && gameVersionAccordion) {
|
|
||||||
gameVersionAccordion.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateTo(
|
|
||||||
{
|
|
||||||
query: {
|
|
||||||
...route.query,
|
|
||||||
...(userSelectedGameVersion && {
|
|
||||||
version: userSelectedGameVersion,
|
|
||||||
}),
|
|
||||||
...(userSelectedPlatform && {
|
|
||||||
loader: userSelectedPlatform,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
hash: route.hash,
|
|
||||||
},
|
|
||||||
{ replace: true },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ formatMessage(getTagMessage(platform, 'loader')) }}
|
|
||||||
<CheckIcon v-if="userSelectedPlatform === platform" />
|
|
||||||
</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
</ScrollablePanel>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
|
||||||
<AutomaticAccordion div class="flex flex-col gap-2">
|
|
||||||
<VersionSummary
|
|
||||||
v-if="filteredRelease"
|
|
||||||
:version="filteredRelease"
|
|
||||||
:decorate-download-url="decorateModalDownloadUrl"
|
|
||||||
@on-download="onDownload"
|
|
||||||
@on-navigate="onVersionNavigate"
|
|
||||||
/>
|
|
||||||
<VersionSummary
|
|
||||||
v-if="filteredBeta"
|
|
||||||
:version="filteredBeta"
|
|
||||||
:decorate-download-url="decorateModalDownloadUrl"
|
|
||||||
@on-download="onDownload"
|
|
||||||
@on-navigate="onVersionNavigate"
|
|
||||||
/>
|
|
||||||
<VersionSummary
|
|
||||||
v-if="filteredAlpha"
|
|
||||||
:version="filteredAlpha"
|
|
||||||
:decorate-download-url="decorateModalDownloadUrl"
|
|
||||||
@on-download="onDownload"
|
|
||||||
@on-navigate="onVersionNavigate"
|
|
||||||
/>
|
|
||||||
<p
|
|
||||||
v-if="
|
|
||||||
currentPlatform &&
|
|
||||||
currentGameVersion &&
|
|
||||||
!filteredRelease &&
|
|
||||||
!filteredBeta &&
|
|
||||||
!filteredAlpha &&
|
|
||||||
!versionsLoading &&
|
|
||||||
versions.length > 0
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
formatMessage(messages.noVersionsAvailable, {
|
|
||||||
gameVersion: currentGameVersion,
|
|
||||||
platform: currentPlatformText,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
</AutomaticAccordion>
|
|
||||||
<ServersPromo
|
|
||||||
v-if="flags.showProjectPageDownloadModalServersPromo"
|
|
||||||
:link="`/hosting#plan`"
|
|
||||||
@close="
|
|
||||||
() => {
|
|
||||||
flags.showProjectPageDownloadModalServersPromo = false
|
|
||||||
saveFeatureFlags()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</NewModal>
|
|
||||||
<CollectionCreateModal ref="modal_collection" :project-ids="[project.id]" />
|
<CollectionCreateModal ref="modal_collection" :project-ids="[project.id]" />
|
||||||
<div
|
<div
|
||||||
class="new-page sidebar"
|
class="new-page sidebar"
|
||||||
@@ -1048,27 +726,21 @@ import {
|
|||||||
BookTextIcon,
|
BookTextIcon,
|
||||||
CalendarIcon,
|
CalendarIcon,
|
||||||
ChartIcon,
|
ChartIcon,
|
||||||
CheckIcon,
|
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
ExternalIcon,
|
ExternalIcon,
|
||||||
GameIcon,
|
|
||||||
HeartIcon,
|
HeartIcon,
|
||||||
InfoIcon,
|
|
||||||
ListIcon,
|
ListIcon,
|
||||||
ModrinthIcon,
|
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
ReportIcon,
|
ReportIcon,
|
||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
ScanEyeIcon,
|
ScanEyeIcon,
|
||||||
SearchIcon,
|
|
||||||
ServerPlusIcon,
|
ServerPlusIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
VersionIcon,
|
VersionIcon,
|
||||||
WrenchIcon,
|
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import {
|
import {
|
||||||
@@ -1078,7 +750,6 @@ import {
|
|||||||
Checkbox,
|
Checkbox,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
defineMessages,
|
defineMessages,
|
||||||
getTagMessage,
|
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
IntlFormatted,
|
IntlFormatted,
|
||||||
@@ -1098,8 +769,6 @@ import {
|
|||||||
ProjectSidebarServerInfo,
|
ProjectSidebarServerInfo,
|
||||||
ProjectSidebarTags,
|
ProjectSidebarTags,
|
||||||
provideProjectPageContext,
|
provideProjectPageContext,
|
||||||
ScrollablePanel,
|
|
||||||
ServersPromo,
|
|
||||||
StyledInput,
|
StyledInput,
|
||||||
TagItem,
|
TagItem,
|
||||||
useDebugLogger,
|
useDebugLogger,
|
||||||
@@ -1108,22 +777,19 @@ import {
|
|||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import VersionSummary from '@modrinth/ui/src/components/version/VersionSummary.vue'
|
|
||||||
import { capitalizeString, formatProjectType, renderString } from '@modrinth/utils'
|
import { capitalizeString, formatProjectType, renderString } from '@modrinth/utils'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||||
import { useLocalStorage } from '@vueuse/core'
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { Tooltip } from 'floating-vue'
|
import { Tooltip } from 'floating-vue'
|
||||||
import { nextTick, readonly, ref, useTemplateRef, watch } from 'vue'
|
import { readonly, ref, useTemplateRef, watch } from 'vue'
|
||||||
|
|
||||||
import { navigateTo } from '#app'
|
import { navigateTo } from '#app'
|
||||||
import Accordion from '~/components/ui/Accordion.vue'
|
|
||||||
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
||||||
import AutomaticAccordion from '~/components/ui/AutomaticAccordion.vue'
|
|
||||||
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
|
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
|
||||||
import MessageBanner from '~/components/ui/MessageBanner.vue'
|
import MessageBanner from '~/components/ui/MessageBanner.vue'
|
||||||
import ModerationChecklist from '~/components/ui/moderation/checklist/ModerationChecklist.vue'
|
import ModerationChecklist from '~/components/ui/moderation/checklist/ModerationChecklist.vue'
|
||||||
import ModerationProjectNags from '~/components/ui/moderation/ModerationProjectNags.vue'
|
import ModerationProjectNags from '~/components/ui/moderation/ModerationProjectNags.vue'
|
||||||
|
import ProjectDownloadModal from '~/components/ui/ProjectDownloadModal.vue'
|
||||||
import ProjectMemberHeader from '~/components/ui/ProjectMemberHeader.vue'
|
import ProjectMemberHeader from '~/components/ui/ProjectMemberHeader.vue'
|
||||||
import { getSignInRouteObj } from '~/composables/auth.ts'
|
import { getSignInRouteObj } from '~/composables/auth.ts'
|
||||||
import { saveFeatureFlags } from '~/composables/featureFlags.ts'
|
import { saveFeatureFlags } from '~/composables/featureFlags.ts'
|
||||||
@@ -1190,14 +856,6 @@ const downloadModal = ref()
|
|||||||
const openInAppModal = ref()
|
const openInAppModal = ref()
|
||||||
const overTheTopDownloadAnimation = ref()
|
const overTheTopDownloadAnimation = ref()
|
||||||
|
|
||||||
const userSelectedGameVersion = ref(null)
|
|
||||||
const userSelectedPlatform = ref(null)
|
|
||||||
const showAllVersions = ref(false)
|
|
||||||
|
|
||||||
const gameVersionFilterInput = ref()
|
|
||||||
|
|
||||||
const versionFilter = ref('')
|
|
||||||
|
|
||||||
const projectV3Loaded = computed(() => !projectV3Pending.value || projectV3.value != null)
|
const projectV3Loaded = computed(() => !projectV3Pending.value || projectV3.value != null)
|
||||||
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
||||||
|
|
||||||
@@ -1205,80 +863,6 @@ const projectEnvironmentModal = useTemplateRef('projectEnvironmentModal')
|
|||||||
|
|
||||||
const baseId = useId()
|
const baseId = useId()
|
||||||
|
|
||||||
const currentGameVersion = computed(() => {
|
|
||||||
if (!project.value) return null
|
|
||||||
return (
|
|
||||||
userSelectedGameVersion.value ||
|
|
||||||
(project.value.game_versions.length === 1 && project.value.game_versions[0])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const possibleGameVersions = computed(() => {
|
|
||||||
return versions.value
|
|
||||||
.filter((x) => !currentPlatform.value || x.loaders.includes(currentPlatform.value))
|
|
||||||
.flatMap((x) => x.game_versions)
|
|
||||||
})
|
|
||||||
|
|
||||||
const possiblePlatforms = computed(() => {
|
|
||||||
return versions.value
|
|
||||||
.filter((x) => !currentGameVersion.value || x.game_versions.includes(currentGameVersion.value))
|
|
||||||
.flatMap((x) => x.loaders)
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentPlatform = computed(() => {
|
|
||||||
if (!project.value) return null
|
|
||||||
return (
|
|
||||||
userSelectedPlatform.value || (project.value.loaders.length === 1 && project.value.loaders[0])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentPlatformText = computed(() => {
|
|
||||||
if (!currentPlatform.value) return null
|
|
||||||
return formatMessage(getTagMessage(currentPlatform.value, 'loader'))
|
|
||||||
})
|
|
||||||
|
|
||||||
function decorateModalDownloadUrl(url) {
|
|
||||||
return createProjectDownloadUrl(url, {
|
|
||||||
reason: downloadReason.value,
|
|
||||||
gameVersion: currentGameVersion.value ?? undefined,
|
|
||||||
loader: currentPlatform.value ?? undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const releaseVersions = computed(() => {
|
|
||||||
const set = new Set()
|
|
||||||
for (const gv of tags.value.gameVersions || []) {
|
|
||||||
if (gv?.version && gv.version_type === 'release') set.add(gv.version)
|
|
||||||
}
|
|
||||||
return set
|
|
||||||
})
|
|
||||||
|
|
||||||
const nonReleaseVersions = computed(() => {
|
|
||||||
const set = new Set()
|
|
||||||
for (const gv of tags.value.gameVersions || []) {
|
|
||||||
if (gv?.version && gv.version_type !== 'release') set.add(gv.version)
|
|
||||||
}
|
|
||||||
return set
|
|
||||||
})
|
|
||||||
|
|
||||||
function isReleaseGameVersion(ver) {
|
|
||||||
if (releaseVersions.value.has(ver)) return true
|
|
||||||
if (nonReleaseVersions.value.has(ver)) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const showVersionsCheckbox = computed(() => {
|
|
||||||
const list = project.value?.game_versions || []
|
|
||||||
let hasRelease = false
|
|
||||||
let hasNonRelease = false
|
|
||||||
for (const v of list) {
|
|
||||||
if (isReleaseGameVersion(v)) hasRelease = true
|
|
||||||
else hasNonRelease = true
|
|
||||||
if (hasRelease && hasNonRelease) return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
const serverProject = computed(() => ({
|
const serverProject = computed(() => ({
|
||||||
name: project.value.title,
|
name: project.value.title,
|
||||||
slug: project.value.slug || project.value.id,
|
slug: project.value.slug || project.value.id,
|
||||||
@@ -1294,16 +878,6 @@ function handlePlayServerProject() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function installWithApp() {
|
|
||||||
setTimeout(() => {
|
|
||||||
getModrinthAppAccordion.value.open()
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
|
|
||||||
const gameVersionAccordion = ref()
|
|
||||||
const platformAccordion = ref()
|
|
||||||
const getModrinthAppAccordion = ref()
|
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
|
||||||
const detailsMessages = defineMessages({
|
const detailsMessages = defineMessages({
|
||||||
@@ -1359,18 +933,10 @@ const messages = defineMessages({
|
|||||||
id: 'project.description.title',
|
id: 'project.description.title',
|
||||||
defaultMessage: 'Description',
|
defaultMessage: 'Description',
|
||||||
},
|
},
|
||||||
dontHaveModrinthApp: {
|
|
||||||
id: 'project.download.no-app',
|
|
||||||
defaultMessage: "Don't have Modrinth App?",
|
|
||||||
},
|
|
||||||
dontShowAgain: {
|
dontShowAgain: {
|
||||||
id: 'project.actions.dont-show-again',
|
id: 'project.actions.dont-show-again',
|
||||||
defaultMessage: "Don't show again",
|
defaultMessage: "Don't show again",
|
||||||
},
|
},
|
||||||
downloadTitle: {
|
|
||||||
id: 'project.download.title',
|
|
||||||
defaultMessage: 'Download {title}',
|
|
||||||
},
|
|
||||||
downloadsStat: {
|
downloadsStat: {
|
||||||
id: 'project.stats.downloads-label',
|
id: 'project.stats.downloads-label',
|
||||||
defaultMessage: '{count, plural, one {download} other {downloads}}',
|
defaultMessage: '{count, plural, one {download} other {downloads}}',
|
||||||
@@ -1409,26 +975,6 @@ const messages = defineMessages({
|
|||||||
id: 'project.gallery.title',
|
id: 'project.gallery.title',
|
||||||
defaultMessage: 'Gallery',
|
defaultMessage: 'Gallery',
|
||||||
},
|
},
|
||||||
gameVersionError: {
|
|
||||||
id: 'project.download.game-version-error',
|
|
||||||
defaultMessage: 'Error: no game versions found',
|
|
||||||
},
|
|
||||||
gameVersionLabel: {
|
|
||||||
id: 'project.download.game-version',
|
|
||||||
defaultMessage: 'Game version: {version}',
|
|
||||||
},
|
|
||||||
gameVersionTooltip: {
|
|
||||||
id: 'project.download.game-version-tooltip',
|
|
||||||
defaultMessage: '{title} is only available for {version}',
|
|
||||||
},
|
|
||||||
gameVersionUnsupportedTooltip: {
|
|
||||||
id: 'project.download.game-version-unsupported-tooltip',
|
|
||||||
defaultMessage: '{title} does not support {gameVersion} for {platform}',
|
|
||||||
},
|
|
||||||
installWithModrinthApp: {
|
|
||||||
id: 'project.download.install-with-app',
|
|
||||||
defaultMessage: 'Install with Modrinth App',
|
|
||||||
},
|
|
||||||
licenseErrorMessage: {
|
licenseErrorMessage: {
|
||||||
id: 'project.license.error',
|
id: 'project.license.error',
|
||||||
defaultMessage: 'License text could not be retrieved.',
|
defaultMessage: 'License text could not be retrieved.',
|
||||||
@@ -1453,30 +999,10 @@ const messages = defineMessages({
|
|||||||
id: 'project.collections.none-found',
|
id: 'project.collections.none-found',
|
||||||
defaultMessage: 'No collections found.',
|
defaultMessage: 'No collections found.',
|
||||||
},
|
},
|
||||||
noVersionsAvailable: {
|
|
||||||
id: 'project.download.no-versions-available',
|
|
||||||
defaultMessage: 'No versions available for {gameVersion} and {platform}.',
|
|
||||||
},
|
|
||||||
pageNotFound: {
|
pageNotFound: {
|
||||||
id: 'project.error.page-not-found',
|
id: 'project.error.page-not-found',
|
||||||
defaultMessage: 'The page could not be found',
|
defaultMessage: 'The page could not be found',
|
||||||
},
|
},
|
||||||
platformError: {
|
|
||||||
id: 'project.download.platform-error',
|
|
||||||
defaultMessage: 'Error: no platforms found',
|
|
||||||
},
|
|
||||||
platformLabel: {
|
|
||||||
id: 'project.download.platform',
|
|
||||||
defaultMessage: 'Platform: {platform}',
|
|
||||||
},
|
|
||||||
platformTooltip: {
|
|
||||||
id: 'project.download.platform-tooltip',
|
|
||||||
defaultMessage: '{title} is only available for {platform}',
|
|
||||||
},
|
|
||||||
platformUnsupportedTooltip: {
|
|
||||||
id: 'project.download.platform-unsupported-tooltip',
|
|
||||||
defaultMessage: '{title} does not support {platform} for {gameVersion}',
|
|
||||||
},
|
|
||||||
projectIconUpdated: {
|
projectIconUpdated: {
|
||||||
id: 'project.notification.icon-updated.title',
|
id: 'project.notification.icon-updated.title',
|
||||||
defaultMessage: 'Project icon updated',
|
defaultMessage: 'Project icon updated',
|
||||||
@@ -1505,22 +1031,6 @@ const messages = defineMessages({
|
|||||||
id: 'project.actions.review-project',
|
id: 'project.actions.review-project',
|
||||||
defaultMessage: 'Review project',
|
defaultMessage: 'Review project',
|
||||||
},
|
},
|
||||||
searchGameVersions: {
|
|
||||||
id: 'project.download.search-game-versions',
|
|
||||||
defaultMessage: 'Search game versions...',
|
|
||||||
},
|
|
||||||
searchGameVersionsLabel: {
|
|
||||||
id: 'project.download.search-game-versions-label',
|
|
||||||
defaultMessage: 'Search game versions...',
|
|
||||||
},
|
|
||||||
selectGameVersion: {
|
|
||||||
id: 'project.download.select-game-version',
|
|
||||||
defaultMessage: 'Select game version',
|
|
||||||
},
|
|
||||||
selectPlatform: {
|
|
||||||
id: 'project.download.select-platform',
|
|
||||||
defaultMessage: 'Select platform',
|
|
||||||
},
|
|
||||||
serversPromoDescription: {
|
serversPromoDescription: {
|
||||||
id: 'project.actions.servers-promo.description',
|
id: 'project.actions.servers-promo.description',
|
||||||
defaultMessage: 'Modrinth Hosting is the easiest way to play with your friends without hassle!',
|
defaultMessage: 'Modrinth Hosting is the easiest way to play with your friends without hassle!',
|
||||||
@@ -1537,10 +1047,6 @@ const messages = defineMessages({
|
|||||||
id: 'project.settings.title',
|
id: 'project.settings.title',
|
||||||
defaultMessage: 'Settings',
|
defaultMessage: 'Settings',
|
||||||
},
|
},
|
||||||
showAllVersions: {
|
|
||||||
id: 'project.download.show-all-versions',
|
|
||||||
defaultMessage: 'Show all versions',
|
|
||||||
},
|
|
||||||
versionsTab: {
|
versionsTab: {
|
||||||
id: 'project.versions.title',
|
id: 'project.versions.title',
|
||||||
defaultMessage: 'Versions',
|
defaultMessage: 'Versions',
|
||||||
@@ -1590,48 +1096,6 @@ async function getLicenseData(event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredVersions = computed(() => {
|
|
||||||
const result = versions.value.filter(
|
|
||||||
(x) =>
|
|
||||||
x.game_versions?.includes(currentGameVersion.value) &&
|
|
||||||
(x.loaders?.includes(currentPlatform.value) || project.value.project_type === 'resourcepack'),
|
|
||||||
)
|
|
||||||
debug('filteredVersions', {
|
|
||||||
total: versions.value.length,
|
|
||||||
filtered: result.length,
|
|
||||||
currentGameVersion: currentGameVersion.value,
|
|
||||||
currentPlatform: currentPlatform.value,
|
|
||||||
versionsEnabled: versionsEnabled.value,
|
|
||||||
versionsLoading: versionsV3Loading.value,
|
|
||||||
sampleLoaders: versions.value.slice(0, 3).map((v) => v.loaders),
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
|
|
||||||
const filteredRelease = computed(() => {
|
|
||||||
return filteredVersions.value.find((x) => x.version_type === 'release')
|
|
||||||
})
|
|
||||||
|
|
||||||
const filteredBeta = computed(() => {
|
|
||||||
return filteredVersions.value.find(
|
|
||||||
(x) =>
|
|
||||||
x.version_type === 'beta' &&
|
|
||||||
(!filteredRelease.value ||
|
|
||||||
dayjs(x.date_published).isAfter(dayjs(filteredRelease.value.date_published))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const filteredAlpha = computed(() => {
|
|
||||||
return filteredVersions.value.find(
|
|
||||||
(x) =>
|
|
||||||
x.version_type === 'alpha' &&
|
|
||||||
(!filteredRelease.value ||
|
|
||||||
dayjs(x.date_published).isAfter(dayjs(filteredRelease.value.date_published))) &&
|
|
||||||
(!filteredBeta.value ||
|
|
||||||
dayjs(x.date_published).isAfter(dayjs(filteredBeta.value.date_published))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const displayCollectionsSearch = ref('')
|
const displayCollectionsSearch = ref('')
|
||||||
const collections = computed(() =>
|
const collections = computed(() =>
|
||||||
user.value && user.value.collections
|
user.value && user.value.collections
|
||||||
@@ -2396,40 +1860,6 @@ if (!route.name.startsWith('type-project-settings')) {
|
|||||||
|
|
||||||
const onUserCollectProject = useClientTry(userCollectProject)
|
const onUserCollectProject = useClientTry(userCollectProject)
|
||||||
|
|
||||||
const { version, loader } = route.query
|
|
||||||
|
|
||||||
if (
|
|
||||||
project.value &&
|
|
||||||
project.value.game_versions.length > 0 &&
|
|
||||||
project.value.game_versions.every((v) => !isReleaseGameVersion(v))
|
|
||||||
) {
|
|
||||||
showAllVersions.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (project.value && version !== undefined && project.value.game_versions.includes(version)) {
|
|
||||||
userSelectedGameVersion.value = version
|
|
||||||
}
|
|
||||||
|
|
||||||
if (project.value && loader !== undefined && project.value.loaders.includes(loader)) {
|
|
||||||
userSelectedPlatform.value = loader
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.hash === '#download' || version !== undefined || loader !== undefined) {
|
|
||||||
debug('eager loadVersions from setup', { hash: route.hash, version, loader })
|
|
||||||
loadVersions()
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(downloadModal, (modal) => {
|
|
||||||
if (!modal) return
|
|
||||||
|
|
||||||
// route.hash returns everything in the hash string, including the # itself
|
|
||||||
if (route.hash === '#download') {
|
|
||||||
debug('hash #download watch fired, opening modal')
|
|
||||||
loadVersions()
|
|
||||||
modal.show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[versionsV3, _versionsV3Error],
|
[versionsV3, _versionsV3Error],
|
||||||
([data, error]) => {
|
([data, error]) => {
|
||||||
@@ -2650,32 +2080,11 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
function closeDownloadModal(event) {
|
|
||||||
downloadModal.value.hide(event)
|
|
||||||
userSelectedPlatform.value = null
|
|
||||||
userSelectedGameVersion.value = null
|
|
||||||
showAllVersions.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerDownloadAnimation() {
|
function triggerDownloadAnimation() {
|
||||||
overTheTopDownloadAnimation.value = true
|
overTheTopDownloadAnimation.value = true
|
||||||
setTimeout(() => (overTheTopDownloadAnimation.value = false), 500)
|
setTimeout(() => (overTheTopDownloadAnimation.value = false), 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDownload(event) {
|
|
||||||
triggerDownloadAnimation()
|
|
||||||
setTimeout(() => {
|
|
||||||
closeDownloadModal(event)
|
|
||||||
}, 400)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onVersionNavigate(url) {
|
|
||||||
closeDownloadModal()
|
|
||||||
nextTick(() => {
|
|
||||||
navigateTo(url)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteVersion(id) {
|
async function deleteVersion(id) {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
|
|
||||||
@@ -2831,13 +2240,8 @@ provideProjectPageContext({
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.accordion-with-bg) {
|
.over-the-top-download-animation {
|
||||||
@apply rounded-2xl bg-bg p-2;
|
position: fixed;
|
||||||
--scrollable-pane-bg: var(--color-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.over-the-top-download-animation {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -2886,13 +2290,7 @@ provideProjectPageContext({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (hover: none) and (max-width: 767px) {
|
.servers-popup {
|
||||||
.modrinth-app-section {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.servers-popup {
|
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 0 12px 1px rgba(0, 175, 92, 0.6),
|
0 0 12px 1px rgba(0, 175, 92, 0.6),
|
||||||
var(--shadow-floating);
|
var(--shadow-floating);
|
||||||
|
|||||||
Reference in New Issue
Block a user