refactor: move additional files into download dependencies section

This commit is contained in:
tdgao
2026-07-03 13:15:16 -07:00
parent 45306a55f1
commit 1bc4922b9f
3 changed files with 61 additions and 84 deletions
@@ -1,11 +1,11 @@
<template> <template>
<div v-if="dependencyRows.length > 0" class="flex flex-col gap-1"> <div v-if="downloadRows.length > 0" class="flex flex-col gap-1">
<div <div
v-if="showTitle || downloadableDependencyFiles.length > 0" v-if="showTitle || downloadableDependencyFiles.length > 0"
class="flex flex-wrap items-center justify-between gap-2" class="flex flex-wrap items-center justify-between gap-2"
> >
<h3 v-if="showTitle" class="m-0 text-base font-semibold text-contrast"> <h3 v-if="showTitle" class="m-0 text-base font-semibold text-contrast">
{{ formatMessage(messages.dependenciesTitle) }} {{ sectionTitle }}
</h3> </h3>
<ButtonStyled v-if="downloadableDependencyFiles.length > 0" type="transparent"> <ButtonStyled v-if="downloadableDependencyFiles.length > 0" type="transparent">
<button :disabled="downloadingDependencies" @click="downloadAllDependencies"> <button :disabled="downloadingDependencies" @click="downloadAllDependencies">
@@ -23,7 +23,7 @@
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<DownloadDependency <DownloadDependency
v-for="dependency in dependencyRows" v-for="dependency in downloadRows"
:key="dependency.key" :key="dependency.key"
:dependency="dependency" :dependency="dependency"
@download="emit('download')" @download="emit('download')"
@@ -34,11 +34,12 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client' import type { Labrinth } from '@modrinth/api-client'
import { DownloadIcon, SpinnerIcon } from '@modrinth/assets' import { DownloadIcon, FileIcon, SpinnerIcon } from '@modrinth/assets'
import { import {
ButtonStyled, ButtonStyled,
type CdnDownloadReason, type CdnDownloadReason,
defineMessages, defineMessages,
fileTypeMessages,
injectModrinthClient, injectModrinthClient,
injectNotificationManager, injectNotificationManager,
useVIntl, useVIntl,
@@ -46,7 +47,7 @@ import {
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 JSZip from 'jszip' import JSZip from 'jszip'
import { computed, ref } from 'vue' import { type Component, computed, ref } from 'vue'
import DownloadDependency from './DownloadDependency.vue' import DownloadDependency from './DownloadDependency.vue'
@@ -65,6 +66,7 @@ interface DownloadDependencyRow {
key: string key: string
name: string name: string
icon?: string icon?: string
fallbackIcon?: Component
projectHref?: string projectHref?: string
downloadHref?: string downloadHref?: string
filename?: string filename?: string
@@ -87,6 +89,7 @@ const props = withDefaults(
currentGameVersion?: string | null currentGameVersion?: string | null
currentPlatform?: string | null currentPlatform?: string | null
downloadReason?: CdnDownloadReason downloadReason?: CdnDownloadReason
additionalFiles?: Labrinth.Versions.v3.VersionFile[]
showTitle?: boolean showTitle?: boolean
}>(), }>(),
{ {
@@ -96,6 +99,7 @@ const props = withDefaults(
currentGameVersion: null, currentGameVersion: null,
currentPlatform: null, currentPlatform: null,
downloadReason: 'standalone', downloadReason: 'standalone',
additionalFiles: () => [],
showTitle: true, showTitle: true,
}, },
) )
@@ -258,8 +262,34 @@ const dependencyRows = computed<DownloadDependencyRow[]>(
() => props.dependencies || resolvedDependencyRows.value, () => props.dependencies || resolvedDependencyRows.value,
) )
const additionalFileRows = computed<DownloadDependencyRow[]>(() =>
props.additionalFiles.map((file) => ({
key: `additional-file-${additionalFileKey(file)}`,
name: file.filename,
fallbackIcon: FileIcon,
downloadHref: getDownloadUrl(file.url),
filename: file.filename,
typeLabel: fileTypeLabel(file.file_type),
unavailableTooltip: formatMessage(messages.unavailableFile),
dependencies: [],
})),
)
const downloadRows = computed<DownloadDependencyRow[]>(() => [
...dependencyRows.value,
...additionalFileRows.value,
])
const sectionTitle = computed(() =>
formatMessage(
dependencyRows.value.length > 0
? messages.dependenciesTitle
: messages.additionalFilesTitle,
),
)
const downloadableDependencyFiles = computed<DownloadableDependencyFile[]>(() => const downloadableDependencyFiles = computed<DownloadableDependencyFile[]>(() =>
collectDownloadableDependencyFiles(dependencyRows.value), collectDownloadableDependencyFiles(downloadRows.value),
) )
const dependencyZipFilename = computed( const dependencyZipFilename = computed(
@@ -336,6 +366,14 @@ function getDownloadUrl(url: string) {
}) })
} }
function fileTypeLabel(type?: Labrinth.Versions.v3.FileType | null) {
return formatMessage(fileTypeMessages[type ?? 'unknown'] ?? fileTypeMessages.unknown)
}
function additionalFileKey(file: Labrinth.Versions.v3.VersionFile) {
return file.hashes?.sha1 ?? file.filename
}
function collectDownloadableDependencyFiles( function collectDownloadableDependencyFiles(
rows: DownloadDependencyRow[], rows: DownloadDependencyRow[],
seenHrefs = new Set<string>(), seenHrefs = new Set<string>(),
@@ -458,6 +496,10 @@ const messages = defineMessages({
id: 'project.download.dependencies-title', id: 'project.download.dependencies-title',
defaultMessage: 'Dependencies', defaultMessage: 'Dependencies',
}, },
additionalFilesTitle: {
id: 'project.download.additional-files-title',
defaultMessage: 'Additional files',
},
downloadAllDependencies: { downloadAllDependencies: {
id: 'project.download.dependencies-download-all', id: 'project.download.dependencies-download-all',
defaultMessage: 'Download all', defaultMessage: 'Download all',
@@ -502,5 +544,9 @@ const messages = defineMessages({
id: 'project.download.dependency-unavailable', id: 'project.download.dependency-unavailable',
defaultMessage: 'This dependency cannot be downloaded', defaultMessage: 'This dependency cannot be downloaded',
}, },
unavailableFile: {
id: 'project.download.file-unavailable',
defaultMessage: 'This file cannot be downloaded',
},
}) })
</script> </script>
@@ -15,7 +15,11 @@
v-else v-else
class="flex size-4 flex-shrink-0 items-center justify-center rounded-lg border border-solid border-surface-5 text-secondary" class="flex size-4 flex-shrink-0 items-center justify-center rounded-lg border border-solid border-surface-5 text-secondary"
> >
<PackageIcon aria-hidden="true" class="size-5" /> <component
:is="dependency.fallbackIcon ?? PackageIcon"
aria-hidden="true"
class="size-5"
/>
</span> </span>
<a <a
v-if="dependency.projectHref" v-if="dependency.projectHref"
@@ -79,7 +83,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { DownloadIcon, PackageIcon } from '@modrinth/assets' import { DownloadIcon, PackageIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled, TagItem, truncatedTooltip } from '@modrinth/ui' import { Avatar, ButtonStyled, TagItem, truncatedTooltip } from '@modrinth/ui'
import { ref } from 'vue' import { type Component, ref } from 'vue'
defineOptions({ defineOptions({
name: 'DownloadDependency', name: 'DownloadDependency',
@@ -89,6 +93,7 @@ interface DownloadDependencyRow {
key: string key: string
name: string name: string
icon?: string icon?: string
fallbackIcon?: Component
projectHref?: string projectHref?: string
downloadHref?: string downloadHref?: string
filename?: string filename?: string
@@ -34,49 +34,9 @@
:current-game-version="currentGameVersion" :current-game-version="currentGameVersion"
:current-platform="currentPlatform" :current-platform="currentPlatform"
:download-reason="downloadReason" :download-reason="downloadReason"
:additional-files="additionalFiles"
@download="onDownload" @download="onDownload"
/> />
<div v-if="additionalFiles.length > 0" class="flex flex-col gap-2">
<h3 class="m-0 text-base font-semibold text-contrast">
{{ formatMessage(messages.additionalFilesTitle) }}
</h3>
<div class="flex flex-col gap-2">
<div
v-for="file in additionalFiles"
:key="additionalFileKey(file)"
class="grid min-h-10 grid-cols-[minmax(0,1fr)_min-content] items-center gap-3 rounded-xl bg-button-bg py-0 pl-3.5 pr-2 text-primary"
>
<span class="flex min-w-0 items-center gap-3">
<FileIcon aria-hidden="true" class="size-5 flex-shrink-0 text-secondary" />
<span
:ref="(element) => setAdditionalFileNameRef(file, element)"
v-tooltip="
truncatedTooltip(additionalFileNameRefs[additionalFileKey(file)], file.filename)
"
class="min-w-0 truncate text-base font-semibold text-contrast"
>
{{ file.filename }}
</span>
<TagItem
class="shrink-0 border !border-solid border-surface-5 !px-3 !py-1 text-base"
>
{{ fileTypeLabel(file.file_type) }}
</TagItem>
</span>
<ButtonStyled circular type="transparent">
<a
v-tooltip="'Download'"
:href="getDownloadUrl(file.url)"
:download="file.filename"
:aria-label="`Download ${file.filename}`"
@click="onDownload"
>
<DownloadIcon aria-hidden="true" class="size-6 text-secondary" />
</a>
</ButtonStyled>
</div>
</div>
</div>
</div> </div>
<ServersPromo <ServersPromo
v-if="flags.showProjectPageDownloadModalServersPromo" v-if="flags.showProjectPageDownloadModalServersPromo"
@@ -95,24 +55,20 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client' import type { Labrinth } from '@modrinth/api-client'
import { DownloadIcon, FileIcon } from '@modrinth/assets'
import { import {
Avatar, Avatar,
ButtonStyled,
type CdnDownloadReason, type CdnDownloadReason,
defineMessages, defineMessages,
fileTypeMessages,
injectModrinthClient, injectModrinthClient,
NewModal, NewModal,
ServersPromo, ServersPromo,
TagItem,
truncatedTooltip, truncatedTooltip,
useDebugLogger, useDebugLogger,
useVIntl, useVIntl,
} 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 { type ComponentPublicInstance, computed, nextTick, onUnmounted, ref, watch } from 'vue' import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
import { navigateTo } from '#app' import { navigateTo } from '#app'
import { saveFeatureFlags } from '~/composables/featureFlags.ts' import { saveFeatureFlags } from '~/composables/featureFlags.ts'
@@ -173,13 +129,11 @@ const route = useRoute()
const flags = useFeatureFlags() const flags = useFeatureFlags()
const tags = useGeneratedState() const tags = useGeneratedState()
const client = injectModrinthClient() const client = injectModrinthClient()
const { createProjectDownloadUrl } = useCdnDownloadContext()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const debug = useDebugLogger('DownloadModal') const debug = useDebugLogger('DownloadModal')
const modal = ref<NewModalRef | null>(null) const modal = ref<NewModalRef | null>(null)
const downloadTitleRef = ref<HTMLElement | null>(null) const downloadTitleRef = ref<HTMLElement | null>(null)
const additionalFileNameRefs = ref<Record<string, HTMLElement | null>>({})
const modalOpen = ref(false) const modalOpen = ref(false)
const showProjectId = ref<string | null>(null) const showProjectId = ref<string | null>(null)
const showOptions = ref<ResolvedProjectDownloadModalShowOptions>(getDefaultShowOptions()) const showOptions = ref<ResolvedProjectDownloadModalShowOptions>(getDefaultShowOptions())
@@ -285,28 +239,8 @@ const messages = defineMessages({
id: 'project.download.title', id: 'project.download.title',
defaultMessage: 'Download {title}', defaultMessage: 'Download {title}',
}, },
additionalFilesTitle: {
id: 'project.download.additional-files-title',
defaultMessage: 'Additional files',
},
}) })
function fileTypeLabel(type?: Labrinth.Versions.v3.FileType | null) {
return formatMessage(fileTypeMessages[type ?? 'unknown'] ?? fileTypeMessages.unknown)
}
function additionalFileKey(file: Labrinth.Versions.v3.VersionFile) {
return file.hashes?.sha1 ?? file.filename
}
function setAdditionalFileNameRef(
file: Labrinth.Versions.v3.VersionFile,
element: Element | ComponentPublicInstance | null,
) {
additionalFileNameRefs.value[additionalFileKey(file)] =
element instanceof HTMLElement ? element : null
}
function getProjectTypeForUrl( function getProjectTypeForUrl(
type: Labrinth.Projects.v2.ProjectType, type: Labrinth.Projects.v2.ProjectType,
loaders: string[], loaders: string[],
@@ -326,14 +260,6 @@ function getProjectTypeForUrl(
return 'mod' return 'mod'
} }
function getDownloadUrl(url: string) {
return createProjectDownloadUrl(url, {
reason: props.downloadReason,
gameVersion: currentGameVersion.value ?? undefined,
loader: currentPlatform.value ?? undefined,
})
}
function updateDownloadQuery({ function updateDownloadQuery({
gameVersion, gameVersion,
platform, platform,