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'
import type { DisplayProjectType } from '@modrinth/utils'
import { useQuery } from '@tanstack/vue-query'
import { computed, ref, watch } from 'vue'
import { computed, nextTick, ref, watch } from 'vue'
import { navigateTo } from '#app'
import { saveFeatureFlags } from '~/composables/featureFlags.ts'
@@ -118,11 +118,13 @@ type NewModalRef = {
const props = withDefaults(
defineProps<{
projectId: string
projectId?: string
downloadReason?: CdnDownloadReason
useRouteHash?: boolean
}>(),
{
downloadReason: 'standalone',
useRouteHash: true,
},
)
@@ -140,19 +142,20 @@ const debug = useDebugLogger('DownloadModal')
const modal = ref<NewModalRef | null>(null)
const modalOpen = ref(false)
const showProjectId = ref<string | null>(null)
const downloadProjectResetKey = ref(0)
const projectDownloadSelection = ref<ProjectDownloadSelection>({
currentGameVersion: null,
currentPlatform: null,
selectedVersion: null,
selectedPrimaryFile: null,
})
const projectDownloadSelection = ref<ProjectDownloadSelection>(getDefaultProjectDownloadSelection())
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]),
queryFn: () => client.labrinth.projects_v2.get(routeProjectId.value),
queryFn: () => client.labrinth.projects_v2.get(routeProjectId.value!),
enabled: computed(() => !!routeProjectId.value),
staleTime: STALE_TIME,
})
@@ -320,23 +323,33 @@ function onShow() {
modalOpen.value = true
debug('on-show fired')
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() {
const hadShowProjectId = !!showProjectId.value
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
showProjectId.value = projectId ?? null
await nextTick()
if (!(await loadProjectForModal(!!projectId))) return
modalOpen.value = true
modal.value.show(event)
}
function hide() {
async function hide() {
if (!modal.value || !modalOpen.value) return
modal.value?.hide()
await nextTick()
downloadProjectResetKey.value += 1
}
@@ -344,17 +357,43 @@ function onDownload() {
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() {
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')
show()
}
if (
route.hash === '#download' ||
route.query.version !== undefined ||
route.query.loader !== undefined
props.useRouteHash &&
(route.hash === '#download' ||
route.query.version !== undefined ||
route.query.loader !== undefined)
) {
debug('eager loadVersions from setup', {
hash: route.hash,
@@ -367,6 +406,10 @@ if (
watch(modal, openFromHash)
watch(() => route.hash, openFromHash)
watch(routeProjectId, () => {
projectDownloadSelection.value = getDefaultProjectDownloadSelection()
downloadProjectResetKey.value += 1
})
defineExpose({ show, hide })
</script>
@@ -80,6 +80,12 @@
</div>
</div>
</NewModal>
<ProjectDownloadModal
ref="dependencyDownloadModal"
download-reason="dependency"
:use-route-hash="false"
@download="emit('onDownload')"
/>
<div class="flex flex-col">
<nuxt-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 />
</a>
<a
<button
v-else-if="dependency.project"
v-tooltip="formatMessage(messages.downloadProject)"
:href="`/project/${dependency.project.id}#download`"
target="_blank"
:aria-label="formatMessage(messages.downloadProject)"
@click="openDependencyDownloadModal(dependency.project.id, $event)"
>
<DownloadIcon />
</a>
</button>
</ButtonStyled>
</template>
</VersionPage>
@@ -519,6 +525,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { onServerPrefetch } from '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 { projectQueryOptions, STALE_TIME } from '~/composables/queries/project'
import { versionQueryOptions } from '~/composables/queries/version'
@@ -774,6 +781,7 @@ useSeoMeta({
const editModal = useTemplateRef('editModal')
const confirmModal = useTemplateRef('confirmModal')
const packageModal = useTemplateRef('packageModal')
const dependencyDownloadModal = useTemplateRef('dependencyDownloadModal')
const packageLoaders = ref(['forge', 'fabric', 'quilt', 'neoforge'])
const packageLoaderOptions = [
@@ -796,6 +804,10 @@ function handleOpenEditVersionModal(versionId: string, projectId: string, stageI
editModal.value?.openEditVersionModal(versionId, projectId, stageId)
}
function openDependencyDownloadModal(projectId: string, event: MouseEvent) {
dependencyDownloadModal.value?.show(event, projectId)
}
const deleteVersionMutation = useMutation({
mutationFn: () => client.labrinth.versions_v3.deleteVersion(version.value!.id),
onSuccess: async () => {
@@ -1065,7 +1077,11 @@ function getDependencyVersion(dependency: Labrinth.Versions.v3.Dependency) {
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) {