mirror of
https://github.com/modrinth/code.git
synced 2026-09-02 13:05:50 +00:00
feat: implement better handling for showing required/recommended resource packs with additional files
This commit is contained in:
@@ -1,31 +1,49 @@
|
||||
<template>
|
||||
<div v-if="downloadRows.length > 0" class="flex flex-col gap-2.5">
|
||||
<div v-if="showTitle" class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h3 class="m-0 flex items-center gap-1.5 text-base font-semibold text-contrast">
|
||||
{{ sectionTitle }}
|
||||
<InfoIcon
|
||||
v-if="duplicateDependencyRowsHidden"
|
||||
v-tooltip="formatMessage(messages.duplicateDependenciesHidden)"
|
||||
aria-hidden="true"
|
||||
class="size-4 text-secondary"
|
||||
<div v-if="downloadRows.length > 0 || recommendedRows.length > 0" class="flex flex-col gap-4">
|
||||
<div v-if="downloadRows.length > 0" class="flex flex-col gap-2.5">
|
||||
<div v-if="showTitle" class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h3 class="m-0 flex items-center gap-1.5 text-base font-semibold text-contrast">
|
||||
{{ formatMessage(messages.dependenciesTitle) }}
|
||||
<InfoIcon
|
||||
v-if="duplicateDependencyRowsHidden"
|
||||
v-tooltip="formatMessage(messages.duplicateDependenciesHidden)"
|
||||
aria-hidden="true"
|
||||
class="size-4 text-secondary"
|
||||
/>
|
||||
</h3>
|
||||
</div>
|
||||
<Admonition v-if="requiredResourcePackAdmonitionVisible" type="info">
|
||||
{{ formatMessage(messages.requiredResourcePackAdmonition) }}
|
||||
</Admonition>
|
||||
<div class="rounded-2xl bg-surface-2 p-2 pl-4 pr-3">
|
||||
<DownloadDependency
|
||||
v-for="dependency in downloadRows"
|
||||
:key="dependency.key"
|
||||
:dependency="dependency"
|
||||
@download="emit('download')"
|
||||
/>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-surface-2 p-2 pl-4 pr-3">
|
||||
<DownloadDependency
|
||||
v-for="dependency in downloadRows"
|
||||
:key="dependency.key"
|
||||
:dependency="dependency"
|
||||
@download="emit('download')"
|
||||
/>
|
||||
|
||||
<div v-if="recommendedRows.length > 0" class="flex flex-col gap-2.5">
|
||||
<h3 class="m-0 text-base font-semibold text-contrast">
|
||||
{{ formatMessage(messages.recommendedTitle) }}
|
||||
</h3>
|
||||
<div class="rounded-2xl bg-surface-2 p-2 pl-4 pr-3">
|
||||
<DownloadDependency
|
||||
v-for="dependency in recommendedRows"
|
||||
:key="dependency.key"
|
||||
:dependency="dependency"
|
||||
@download="emit('download')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { InfoIcon } from '@modrinth/assets'
|
||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed } from 'vue'
|
||||
import { Admonition, defineMessages, useVIntl } from '@modrinth/ui'
|
||||
|
||||
import { injectDownloadModalProvider } from './download-modal-provider'
|
||||
import DownloadDependency from './DownloadDependency.vue'
|
||||
@@ -47,29 +65,30 @@ const emit = defineEmits<{
|
||||
download: []
|
||||
}>()
|
||||
const { formatMessage } = useVIntl()
|
||||
const { visibleDependencyRows, duplicateDependencyRowsHidden, downloadRows } =
|
||||
injectDownloadModalProvider()
|
||||
|
||||
const sectionTitle = computed(() =>
|
||||
formatMessage(
|
||||
visibleDependencyRows.value.length > 0
|
||||
? messages.dependenciesTitle
|
||||
: messages.additionalFilesTitle,
|
||||
),
|
||||
)
|
||||
const {
|
||||
duplicateDependencyRowsHidden,
|
||||
downloadRows,
|
||||
recommendedRows,
|
||||
requiredResourcePackAdmonitionVisible,
|
||||
} = injectDownloadModalProvider()
|
||||
|
||||
const messages = defineMessages({
|
||||
dependenciesTitle: {
|
||||
id: 'project.download.dependencies-title',
|
||||
defaultMessage: 'Dependencies',
|
||||
},
|
||||
recommendedTitle: {
|
||||
id: 'project.download.recommended-title',
|
||||
defaultMessage: 'Recommended',
|
||||
},
|
||||
duplicateDependenciesHidden: {
|
||||
id: 'project.download.duplicate-dependencies-hidden',
|
||||
defaultMessage: 'Duplicate dependencies are hidden',
|
||||
},
|
||||
additionalFilesTitle: {
|
||||
id: 'project.download.additional-files-title',
|
||||
defaultMessage: 'Additional files',
|
||||
requiredResourcePackAdmonition: {
|
||||
id: 'project.download.required-resource-pack-admonition',
|
||||
defaultMessage:
|
||||
'This data pack also requires a resource pack. Download it and place it in your resourcepacks folder.',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="flex min-w-0 flex-col">
|
||||
<div
|
||||
class="z-10 grid h-11 grid-cols-[minmax(0,1fr)_min-content] items-center gap-3 text-primary"
|
||||
class="z-10 grid h-11 grid-cols-[minmax(0,1fr)_min-content] items-center gap-1 text-primary"
|
||||
>
|
||||
<span class="flex min-w-0 items-center gap-2">
|
||||
<Avatar
|
||||
v-if="dependency.icon"
|
||||
v-if="dependency.icon && !dependency.hideIcon"
|
||||
:src="dependency.icon"
|
||||
:alt="dependency.name"
|
||||
size="24px"
|
||||
class="!rounded-lg !shadow-none"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
v-else-if="!dependency.hideIcon"
|
||||
class="flex size-6 flex-shrink-0 items-center justify-center rounded-lg border border-solid border-surface-5 text-secondary"
|
||||
>
|
||||
<component
|
||||
@@ -28,7 +28,7 @@
|
||||
:href="dependency.projectHref"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="min-w-0 truncate text-base font-semibold text-contrast hover:underline"
|
||||
class="min-w-0 truncate bg-surface-2 text-base font-semibold text-contrast hover:underline"
|
||||
>
|
||||
{{ dependency.name }}
|
||||
</a>
|
||||
@@ -36,11 +36,20 @@
|
||||
v-else
|
||||
ref="dependencyNameRef"
|
||||
v-tooltip="truncatedTooltip(dependencyNameRef, dependency.name)"
|
||||
class="min-w-0 truncate text-base font-semibold text-contrast"
|
||||
class="min-w-0 truncate bg-surface-2 text-base text-contrast"
|
||||
:class="dependency.isAdditionalFile ? 'font-medium' : 'font-semibold'"
|
||||
>
|
||||
{{ dependency.name }}
|
||||
</span>
|
||||
<TagItem
|
||||
v-if="dependency.isAdditionalFile"
|
||||
v-tooltip="metadataTooltip"
|
||||
class="min-w-0 max-w-[50%] shrink-0 truncate border !border-solid border-surface-5"
|
||||
>
|
||||
{{ metadataLabel }}
|
||||
</TagItem>
|
||||
<span
|
||||
v-else
|
||||
v-tooltip="metadataTooltip"
|
||||
class="min-w-0 max-w-[50%] truncate text-sm text-secondary"
|
||||
>
|
||||
@@ -93,6 +102,7 @@ import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
defineMessages,
|
||||
TagItem,
|
||||
truncatedTooltip,
|
||||
useFormatBytes,
|
||||
useVIntl,
|
||||
@@ -108,6 +118,8 @@ interface DownloadDependencyRow {
|
||||
name: string
|
||||
icon?: string
|
||||
fallbackIcon?: Component
|
||||
hideIcon?: boolean
|
||||
isAdditionalFile?: boolean
|
||||
projectHref?: string
|
||||
downloadHref?: string
|
||||
filename?: string
|
||||
@@ -131,9 +143,11 @@ const formatBytes = useFormatBytes()
|
||||
const dependencyNameRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const metadataLabel = computed(() => props.dependency.metadataLabel ?? props.dependency.typeLabel)
|
||||
const metadataTooltip = computed(() =>
|
||||
metadataLabel.value === props.dependency.typeLabel ? null : metadataLabel.value,
|
||||
)
|
||||
const metadataTooltip = computed(() => {
|
||||
if (props.dependency.isAdditionalFile) return null
|
||||
if (metadataLabel.value === props.dependency.typeLabel) return null
|
||||
return metadataLabel.value
|
||||
})
|
||||
|
||||
const downloadTooltip = computed(() => {
|
||||
const filename = props.dependency.filename || props.dependency.name
|
||||
|
||||
@@ -427,10 +427,38 @@ const selectedPrimaryFile = computed<Labrinth.Versions.v3.VersionFile | null>(()
|
||||
)
|
||||
})
|
||||
|
||||
const requiredResourcePackFile = computed<Labrinth.Versions.v3.VersionFile | null>(() => {
|
||||
if (props.project.project_type !== 'datapack') return null
|
||||
|
||||
return (
|
||||
selectedVersion.value?.files?.find(
|
||||
(file) => file !== selectedPrimaryFile.value && file.file_type === 'required-resource-pack',
|
||||
) || null
|
||||
)
|
||||
})
|
||||
|
||||
const recommendedResourcePackFiles = computed<Labrinth.Versions.v3.VersionFile[]>(() => {
|
||||
if (props.project.project_type !== 'datapack') return []
|
||||
|
||||
return (
|
||||
selectedVersion.value?.files?.filter(
|
||||
(file) => file !== selectedPrimaryFile.value && file.file_type === 'optional-resource-pack',
|
||||
) || []
|
||||
)
|
||||
})
|
||||
|
||||
const hasAdditionalDownloads = computed(() => {
|
||||
const hrefs = new Set<string>()
|
||||
|
||||
for (const file of selectedVersion.value?.files ?? []) {
|
||||
if (selectedPrimaryFile.value) {
|
||||
hrefs.add(selectedPrimaryFile.value.url)
|
||||
}
|
||||
|
||||
if (requiredResourcePackFile.value) {
|
||||
hrefs.add(requiredResourcePackFile.value.url)
|
||||
}
|
||||
|
||||
for (const file of recommendedResourcePackFiles.value) {
|
||||
hrefs.add(file.url)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { AbstractModrinthClient, Labrinth } from '@modrinth/api-client'
|
||||
import { FileIcon } from '@modrinth/assets'
|
||||
import {
|
||||
type CdnDownloadReason,
|
||||
createContext,
|
||||
@@ -26,6 +25,8 @@ export interface DownloadDependencyRow {
|
||||
name: string
|
||||
icon?: string
|
||||
fallbackIcon?: Component
|
||||
hideIcon?: boolean
|
||||
isAdditionalFile?: boolean
|
||||
projectHref?: string
|
||||
downloadHref?: string
|
||||
filename?: string
|
||||
@@ -63,7 +64,9 @@ export interface DownloadModalProvider {
|
||||
visibleDependencyRows: ComputedRef<DownloadDependencyRow[]>
|
||||
duplicateDependencyRowsHidden: ComputedRef<boolean>
|
||||
downloadRows: ComputedRef<DownloadDependencyRow[]>
|
||||
recommendedRows: ComputedRef<DownloadDependencyRow[]>
|
||||
downloadRowsLoaded: ComputedRef<boolean>
|
||||
requiredResourcePackAdmonitionVisible: ComputedRef<boolean>
|
||||
downloadableDependencyFiles: ComputedRef<DownloadableDependencyFile[]>
|
||||
downloadableDependencyFilesLoaded: ComputedRef<boolean>
|
||||
preloadDependenciesForSelection: (selection: ProjectDownloadSelection) => Promise<void>
|
||||
@@ -79,6 +82,7 @@ export function provideDownloadModalProvider(
|
||||
const queryClient = useQueryClient()
|
||||
const { createProjectDownloadUrl } = useCdnDownloadContext()
|
||||
const { formatMessage } = useVIntl()
|
||||
const tags = useGeneratedState()
|
||||
|
||||
const shouldResolveDependencies = computed(
|
||||
() => !!options.project.value && !!options.selectedVersion.value,
|
||||
@@ -230,23 +234,50 @@ export function provideDownloadModalProvider(
|
||||
return keepPreviousDownloadRows.value ? (previous ?? false) : false
|
||||
})
|
||||
|
||||
const visibleRequiredResourcePackFiles = computed(() => {
|
||||
if (options.project.value?.project_type !== 'datapack') return []
|
||||
|
||||
return options.additionalFiles.value.filter(
|
||||
(file) => file.file_type === 'required-resource-pack',
|
||||
)
|
||||
})
|
||||
|
||||
const visibleRecommendedResourcePackFiles = computed(() => {
|
||||
if (options.project.value?.project_type !== 'datapack') return []
|
||||
|
||||
return options.additionalFiles.value.filter(
|
||||
(file) => file.file_type === 'optional-resource-pack',
|
||||
)
|
||||
})
|
||||
|
||||
const additionalFileRows = computed<DownloadDependencyRow[]>(() =>
|
||||
selectedDownloadRowsLoaded.value
|
||||
? options.additionalFiles.value.map((file) => ({
|
||||
key: `additional-file-${additionalFileKey(file)}`,
|
||||
name: file.filename,
|
||||
fallbackIcon: FileIcon,
|
||||
downloadHref: getDownloadUrl(file.url),
|
||||
filename: file.filename,
|
||||
fileSize: file.size,
|
||||
metadataLabel: fileTypeLabel(file.file_type),
|
||||
typeLabel: fileTypeLabel(file.file_type),
|
||||
unavailableTooltip: formatMessage(messages.unavailableFile),
|
||||
dependencies: [],
|
||||
}))
|
||||
? visibleRequiredResourcePackFiles.value.map(createAdditionalFileRow)
|
||||
: [],
|
||||
)
|
||||
|
||||
const recommendedRows = computed<DownloadDependencyRow[]>(() =>
|
||||
selectedDownloadRowsLoaded.value
|
||||
? visibleRecommendedResourcePackFiles.value.map(createAdditionalFileRow)
|
||||
: [],
|
||||
)
|
||||
|
||||
function createAdditionalFileRow(file: Labrinth.Versions.v3.VersionFile): DownloadDependencyRow {
|
||||
return {
|
||||
key: `additional-file-${additionalFileKey(file)}`,
|
||||
name: file.filename,
|
||||
hideIcon: true,
|
||||
isAdditionalFile: true,
|
||||
downloadHref: getDownloadUrl(file.url),
|
||||
filename: file.filename,
|
||||
fileSize: file.size,
|
||||
metadataLabel: fileTypeDisplayLabel(file.file_type),
|
||||
typeLabel: fileTypeLabel(file.file_type),
|
||||
unavailableTooltip: formatMessage(messages.unavailableFile),
|
||||
dependencies: [],
|
||||
}
|
||||
}
|
||||
|
||||
const downloadRows = computed<DownloadDependencyRow[]>((previous) => {
|
||||
if (selectedDownloadRowsLoaded.value) {
|
||||
return [...visibleDependencyRows.value, ...additionalFileRows.value]
|
||||
@@ -260,6 +291,10 @@ export function provideDownloadModalProvider(
|
||||
return keepPreviousDownloadRows.value ? (previous ?? false) : false
|
||||
})
|
||||
|
||||
const requiredResourcePackAdmonitionVisible = computed(() => {
|
||||
return selectedDownloadRowsLoaded.value && visibleRequiredResourcePackFiles.value.length > 0
|
||||
})
|
||||
|
||||
const downloadableDependencyFiles = computed<DownloadableDependencyFile[]>(() =>
|
||||
collectDownloadableDependencyFiles(visibleDependencyRows.value),
|
||||
)
|
||||
@@ -332,6 +367,13 @@ export function provideDownloadModalProvider(
|
||||
const metadataLabel = isProjectOnlyDependencyReference(dependency)
|
||||
? formatMessage(messages.anyCompatibleDependency)
|
||||
: (version?.version_number ?? formatMessage(messages.anyCompatibleDependency))
|
||||
const childDependencies = (versionId && dependenciesByParentVersionId.value.get(versionId)
|
||||
? dependenciesByParentVersionId.value.get(versionId)!
|
||||
: []
|
||||
).flatMap((subDependency) => {
|
||||
const row = createDependencyRow(subDependency)
|
||||
return row ? [row] : []
|
||||
})
|
||||
|
||||
return {
|
||||
key: `${dependency.project_id}-${versionId ?? 'unresolved'}-${
|
||||
@@ -347,16 +389,44 @@ export function provideDownloadModalProvider(
|
||||
metadataLabel,
|
||||
typeLabel: 'Required',
|
||||
unavailableTooltip,
|
||||
dependencies: (versionId && dependenciesByParentVersionId.value.get(versionId)
|
||||
? dependenciesByParentVersionId.value.get(versionId)!
|
||||
: []
|
||||
).flatMap((subDependency) => {
|
||||
const row = createDependencyRow(subDependency)
|
||||
return row ? [row] : []
|
||||
}),
|
||||
dependencies: [
|
||||
...childDependencies,
|
||||
...createRequiredResourcePackRowsForDependency(project, version, primaryFile),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
function createRequiredResourcePackRowsForDependency(
|
||||
project: Labrinth.Projects.v2.Project,
|
||||
version: Labrinth.Versions.v3.Version | undefined,
|
||||
primaryFile: Labrinth.Versions.v3.VersionFile | undefined,
|
||||
): DownloadDependencyRow[] {
|
||||
if (!version || !isDataPackProject(project)) return []
|
||||
|
||||
return version.files
|
||||
.filter((file) => file !== primaryFile && file.file_type === 'required-resource-pack')
|
||||
.map((file) => ({
|
||||
key: `dependency-resource-pack-${version.id}-${additionalFileKey(file)}`,
|
||||
name: file.filename,
|
||||
hideIcon: true,
|
||||
isAdditionalFile: true,
|
||||
downloadHref: getDownloadUrl(file.url),
|
||||
filename: file.filename,
|
||||
fileSize: file.size,
|
||||
metadataLabel: fileTypeDisplayLabel(file.file_type),
|
||||
typeLabel: fileTypeLabel(file.file_type),
|
||||
unavailableTooltip: formatMessage(messages.unavailableFile),
|
||||
dependencies: [],
|
||||
}))
|
||||
}
|
||||
|
||||
function isDataPackProject(project: Labrinth.Projects.v2.Project) {
|
||||
return (
|
||||
project.project_type === 'datapack' ||
|
||||
project.loaders.some((loader) => tags.value.loaderData.dataPackLoaders.includes(loader))
|
||||
)
|
||||
}
|
||||
|
||||
function isProjectOnlyDependencyReference(dependency: ResolvedContent) {
|
||||
const parentVersionId = dependency.dependent_on_version_id
|
||||
const parentVersion =
|
||||
@@ -397,11 +467,19 @@ export function provideDownloadModalProvider(
|
||||
return formatMessage(fileTypeMessages[type ?? 'unknown'] ?? fileTypeMessages.unknown)
|
||||
}
|
||||
|
||||
function fileTypeDisplayLabel(type?: Labrinth.Versions.v3.FileType | null) {
|
||||
if (type === 'required-resource-pack') return formatMessage(messages.requiredResourcePackShort)
|
||||
|
||||
return fileTypeLabel(type)
|
||||
}
|
||||
|
||||
const provider = {
|
||||
visibleDependencyRows,
|
||||
duplicateDependencyRowsHidden,
|
||||
downloadRows,
|
||||
recommendedRows,
|
||||
downloadRowsLoaded,
|
||||
requiredResourcePackAdmonitionVisible,
|
||||
downloadableDependencyFiles,
|
||||
downloadableDependencyFilesLoaded,
|
||||
preloadDependenciesForSelection,
|
||||
@@ -627,4 +705,8 @@ const messages = defineMessages({
|
||||
id: 'project.download.file-unavailable',
|
||||
defaultMessage: 'This file cannot be downloaded',
|
||||
},
|
||||
requiredResourcePackShort: {
|
||||
id: 'project.download.required-resource-pack-short',
|
||||
defaultMessage: 'Resource pack',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="showDependencyDownloadActions" #actions>
|
||||
<div class="flex flex-wrap justify-end gap-2">
|
||||
<div class="flex flex-wrap justify-end gap-2 p-2">
|
||||
<ButtonStyled>
|
||||
<button
|
||||
class="!shadow-none"
|
||||
@@ -285,14 +285,16 @@ const downloadRowsLoaded = downloadModalProvider.downloadRowsLoaded
|
||||
const selectedVersionDownloadFiles = computed<DownloadableFile[]>(() => {
|
||||
if (!selectedVersion.value) return []
|
||||
|
||||
return selectedVersion.value.files.map((file) => ({
|
||||
href: createProjectDownloadUrl(file.url, {
|
||||
reason: props.downloadReason,
|
||||
gameVersion: currentGameVersion.value ?? undefined,
|
||||
loader: currentPlatform.value ?? undefined,
|
||||
}),
|
||||
filename: file.filename,
|
||||
}))
|
||||
return selectedVersion.value.files
|
||||
.filter((file) => file.file_type !== 'optional-resource-pack')
|
||||
.map((file) => ({
|
||||
href: createProjectDownloadUrl(file.url, {
|
||||
reason: props.downloadReason,
|
||||
gameVersion: currentGameVersion.value ?? undefined,
|
||||
loader: currentPlatform.value ?? undefined,
|
||||
}),
|
||||
filename: file.filename,
|
||||
}))
|
||||
})
|
||||
|
||||
const showDependencyDownloadActions = computed(
|
||||
|
||||
Reference in New Issue
Block a user