feat: implement opening project download modal in place in version page

This commit is contained in:
tdgao
2026-07-02 14:17:20 -07:00
parent 355450535d
commit 58d195da77
2 changed files with 83 additions and 24 deletions
@@ -89,7 +89,7 @@ import {
} from '@modrinth/ui' } from '@modrinth/ui'
import type { DisplayProjectType } from '@modrinth/utils' import type { DisplayProjectType } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query' import { useQuery } from '@tanstack/vue-query'
import { computed, ref, watch } from 'vue' import { computed, nextTick, ref, watch } from 'vue'
import { navigateTo } from '#app' import { navigateTo } from '#app'
import { saveFeatureFlags } from '~/composables/featureFlags.ts' import { saveFeatureFlags } from '~/composables/featureFlags.ts'
@@ -118,11 +118,13 @@ type NewModalRef = {
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
projectId: string projectId?: string
downloadReason?: CdnDownloadReason downloadReason?: CdnDownloadReason
useRouteHash?: boolean
}>(), }>(),
{ {
downloadReason: 'standalone', downloadReason: 'standalone',
useRouteHash: true,
}, },
) )
@@ -140,19 +142,20 @@ const debug = useDebugLogger('DownloadModal')
const modal = ref<NewModalRef | null>(null) const modal = ref<NewModalRef | null>(null)
const modalOpen = ref(false) const modalOpen = ref(false)
const showProjectId = ref<string | null>(null)
const downloadProjectResetKey = ref(0) const downloadProjectResetKey = ref(0)
const projectDownloadSelection = ref<ProjectDownloadSelection>({ const projectDownloadSelection = ref<ProjectDownloadSelection>(getDefaultProjectDownloadSelection())
currentGameVersion: null,
currentPlatform: null,
selectedVersion: null,
selectedPrimaryFile: null,
})
const routeProjectId = computed(() => props.projectId) const routeProjectId = computed(() => showProjectId.value ?? props.projectId ?? null)
const { data: projectRaw, error: projectV2Error } = useQuery({ const {
data: projectRaw,
error: projectV2Error,
refetch: refetchProject,
} = useQuery({
queryKey: computed(() => ['project', 'v2', routeProjectId.value]), queryKey: computed(() => ['project', 'v2', routeProjectId.value]),
queryFn: () => client.labrinth.projects_v2.get(routeProjectId.value), queryFn: () => client.labrinth.projects_v2.get(routeProjectId.value!),
enabled: computed(() => !!routeProjectId.value),
staleTime: STALE_TIME, staleTime: STALE_TIME,
}) })
@@ -320,23 +323,33 @@ function onShow() {
modalOpen.value = true modalOpen.value = true
debug('on-show fired') debug('on-show fired')
versionsEnabled.value = true versionsEnabled.value = true
navigateTo({ query: route.query, hash: '#download' }, { replace: true }) if (props.useRouteHash && !showProjectId.value) {
navigateTo({ query: route.query, hash: '#download' }, { replace: true })
}
} }
function onHide() { function onHide() {
const hadShowProjectId = !!showProjectId.value
modalOpen.value = false modalOpen.value = false
navigateTo({ query: route.query, hash: '' }, { replace: true }) showProjectId.value = null
if (props.useRouteHash && !hadShowProjectId) {
navigateTo({ query: route.query, hash: '' }, { replace: true })
}
} }
function show(event?: MouseEvent) { async function show(event?: MouseEvent, projectId?: string): Promise<void> {
if (!modal.value || modalOpen.value) return if (!modal.value || modalOpen.value) return
showProjectId.value = projectId ?? null
await nextTick()
if (!(await loadProjectForModal(!!projectId))) return
modalOpen.value = true modalOpen.value = true
modal.value.show(event) modal.value.show(event)
} }
function hide() { async function hide() {
if (!modal.value || !modalOpen.value) return if (!modal.value || !modalOpen.value) return
modal.value?.hide() modal.value?.hide()
await nextTick()
downloadProjectResetKey.value += 1 downloadProjectResetKey.value += 1
} }
@@ -344,17 +357,43 @@ function onDownload() {
emit('download') emit('download')
} }
function getDefaultProjectDownloadSelection(): ProjectDownloadSelection {
return {
currentGameVersion: null,
currentPlatform: null,
selectedVersion: null,
selectedPrimaryFile: null,
}
}
async function loadProjectForModal(forceRefetch: boolean) {
if (!routeProjectId.value) return false
if (!forceRefetch && projectRaw.value) return true
const { data } = await refetchProject()
return !!data
}
function openFromHash() { function openFromHash() {
if (!modal.value || modalOpen.value || route.hash !== '#download') return if (
!props.useRouteHash ||
!modal.value ||
modalOpen.value ||
showProjectId.value ||
route.hash !== '#download'
) {
return
}
debug('hash #download watch fired, opening modal') debug('hash #download watch fired, opening modal')
show() show()
} }
if ( if (
route.hash === '#download' || props.useRouteHash &&
route.query.version !== undefined || (route.hash === '#download' ||
route.query.loader !== undefined route.query.version !== undefined ||
route.query.loader !== undefined)
) { ) {
debug('eager loadVersions from setup', { debug('eager loadVersions from setup', {
hash: route.hash, hash: route.hash,
@@ -367,6 +406,10 @@ if (
watch(modal, openFromHash) watch(modal, openFromHash)
watch(() => route.hash, openFromHash) watch(() => route.hash, openFromHash)
watch(routeProjectId, () => {
projectDownloadSelection.value = getDefaultProjectDownloadSelection()
downloadProjectResetKey.value += 1
})
defineExpose({ show, hide }) defineExpose({ show, hide })
</script> </script>
@@ -80,6 +80,12 @@
</div> </div>
</div> </div>
</NewModal> </NewModal>
<ProjectDownloadModal
ref="dependencyDownloadModal"
download-reason="dependency"
:use-route-hash="false"
@download="emit('onDownload')"
/>
<div class="flex flex-col"> <div class="flex flex-col">
<nuxt-link <nuxt-link
class="mb-4 flex w-fit items-center gap-2 rounded-lg px-2 py-0.5 pl-0 text-link" class="mb-4 flex w-fit items-center gap-2 rounded-lg px-2 py-0.5 pl-0 text-link"
@@ -364,14 +370,14 @@
> >
<DownloadIcon /> <DownloadIcon />
</a> </a>
<a <button
v-else-if="dependency.project" v-else-if="dependency.project"
v-tooltip="formatMessage(messages.downloadProject)" v-tooltip="formatMessage(messages.downloadProject)"
:href="`/project/${dependency.project.id}#download`" :aria-label="formatMessage(messages.downloadProject)"
target="_blank" @click="openDependencyDownloadModal(dependency.project.id, $event)"
> >
<DownloadIcon /> <DownloadIcon />
</a> </button>
</ButtonStyled> </ButtonStyled>
</template> </template>
</VersionPage> </VersionPage>
@@ -519,6 +525,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { onServerPrefetch } from 'vue' import { onServerPrefetch } from 'vue'
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue' import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
import ProjectDownloadModal from '~/components/ui/ProjectDownloadModal/index.vue'
import { getSignInRouteObj } from '~/composables/auth.ts' import { getSignInRouteObj } from '~/composables/auth.ts'
import { projectQueryOptions, STALE_TIME } from '~/composables/queries/project' import { projectQueryOptions, STALE_TIME } from '~/composables/queries/project'
import { versionQueryOptions } from '~/composables/queries/version' import { versionQueryOptions } from '~/composables/queries/version'
@@ -774,6 +781,7 @@ useSeoMeta({
const editModal = useTemplateRef('editModal') const editModal = useTemplateRef('editModal')
const confirmModal = useTemplateRef('confirmModal') const confirmModal = useTemplateRef('confirmModal')
const packageModal = useTemplateRef('packageModal') const packageModal = useTemplateRef('packageModal')
const dependencyDownloadModal = useTemplateRef('dependencyDownloadModal')
const packageLoaders = ref(['forge', 'fabric', 'quilt', 'neoforge']) const packageLoaders = ref(['forge', 'fabric', 'quilt', 'neoforge'])
const packageLoaderOptions = [ const packageLoaderOptions = [
@@ -796,6 +804,10 @@ function handleOpenEditVersionModal(versionId: string, projectId: string, stageI
editModal.value?.openEditVersionModal(versionId, projectId, stageId) editModal.value?.openEditVersionModal(versionId, projectId, stageId)
} }
function openDependencyDownloadModal(projectId: string, event: MouseEvent) {
dependencyDownloadModal.value?.show(event, projectId)
}
const deleteVersionMutation = useMutation({ const deleteVersionMutation = useMutation({
mutationFn: () => client.labrinth.versions_v3.deleteVersion(version.value!.id), mutationFn: () => client.labrinth.versions_v3.deleteVersion(version.value!.id),
onSuccess: async () => { onSuccess: async () => {
@@ -1065,7 +1077,11 @@ function getDependencyVersion(dependency: Labrinth.Versions.v3.Dependency) {
return undefined return undefined
} }
return projectOnlyDependencyVersions.value?.[dependency.project_id] return (
(Object.keys(projectOnlyDependencyVersions.value || {}).length === 1 &&
projectOnlyDependencyVersions.value?.[dependency.project_id]) ||
undefined
)
} }
function getDependencyPrimaryFile(version?: Labrinth.Versions.v2.Version) { function getDependencyPrimaryFile(version?: Labrinth.Versions.v2.Version) {