diff --git a/packages/ui/src/components/base/Table.vue b/packages/ui/src/components/base/Table.vue index db2d1a4f47..c4c9f44a46 100644 --- a/packages/ui/src/components/base/Table.vue +++ b/packages/ui/src/components/base/Table.vue @@ -33,7 +33,7 @@ - - - - - - {{ row[column.key] ?? '' }} - - - + + + + + {{ row[column.key] ?? '' }} + + + + + + + + + @@ -132,39 +146,53 @@ :style="{ height: `${topSpacerHeight}px` }" > - - - - - - - {{ row[column.key] ?? '' }} - - - + + + + + {{ row[column.key] ?? '' }} + + + + + + + + + boolean) rowClass?: string | ((row: T, index: number) => string) rowClickable?: boolean | ((row: T, index: number) => boolean) }>(), @@ -250,6 +279,7 @@ const sortDirection = defineModel('sortDirection', { default: 'as const slots = useSlots() const selectionAnchorId = ref() const hasHeaderSlot = computed(() => Boolean(slots.header)) +const hasRowBelowSlot = computed(() => Boolean(slots['row-below'])) const columnSpan = computed(() => Math.max(props.columns.length + (props.showSelection ? 1 : 0), 1)) const { @@ -331,6 +361,22 @@ function getRowRenderKey(row: T, rowIndex: number): PropertyKey { return rowIndex } +function getRowPartRenderKey(row: T, rowIndex: number, part: 'group' | 'row' | 'below'): string { + return `${String(getRowRenderKey(row, rowIndex))}-${part}` +} + +function isRowBelowVisible(row: T, rowIndex: number): boolean { + if (!hasRowBelowSlot.value || props.virtualized) { + return false + } + + if (typeof props.rowBelowVisible === 'function') { + return props.rowBelowVisible(row, rowIndex) + } + + return props.rowBelowVisible ?? true +} + function getRowClass(row: T, rowIndex: number): string[] { const baseClass = rowIndex % 2 === 0 ? 'bg-surface-2' : 'bg-surface-1.5' const customClass = @@ -339,6 +385,20 @@ function getRowClass(row: T, rowIndex: number): string[] { return customClass ? [baseClass, customClass] : [baseClass] } +function getRowBelowClass(row: T, rowIndex: number): string[] { + const classes = [ + rowIndex % 2 === 0 ? 'bg-surface-2' : 'bg-surface-1.5', + 'table-row-below', + 'transition-[filter]', + ] + + if (isRowClickable(row, rowIndex)) { + classes.push('cursor-pointer') + } + + return classes +} + function isRowClickable(row: T, rowIndex: number): boolean { return typeof props.rowClickable === 'function' ? props.rowClickable(row, rowIndex) diff --git a/packages/ui/src/components/project/ProjectPageVersions.vue b/packages/ui/src/components/project/ProjectPageVersions.vue index 31614b55ee..012fea3115 100644 --- a/packages/ui/src/components/project/ProjectPageVersions.vue +++ b/packages/ui/src/components/project/ProjectPageVersions.vue @@ -49,13 +49,14 @@ row-key="id" :row-class="getVersionRowClass" :row-clickable="!!versionLink" + :row-below-visible="isFileRowVisible" table-layout="auto" @row-click="openVersionRow" > + + @@ -425,7 +446,6 @@ import { import { AutoLink, ButtonStyled, - FormattedTag, Pagination, SmartClickable, Table, @@ -444,6 +464,7 @@ import { useRoute, useRouter } from 'vue-router' import { useRelativeTime } from '../../composables' import { defineMessages, useVIntl } from '../../composables/i18n' +import { formatTag } from '../../utils/tag-messages' import { getEnvironmentTags } from './settings/environment/environments' const { formatMessage } = useVIntl() @@ -519,18 +540,18 @@ const versionColumns = computed[]>(() => { }, { key: 'name', - label: 'Name', + label: 'Version', cellClass: '!overflow-visible py-3 pr-4 min-w-[7rem]', }, { key: 'gameVersions', label: 'Game version', - cellClass: '!overflow-visible py-3 align-middle pr-2.5 w-fit max-w-[10rem]', + cellClass: '!overflow-visible py-3 align-middle pr-2.5 min-w-0 max-w-[12rem]', }, { key: 'platforms', - label: 'Platforms', - cellClass: '!overflow-visible py-3 align-middle pr-2.5 w-fit max-w-[10rem]', + label: 'Platform', + cellClass: '!overflow-visible py-3 align-middle pr-2.5 min-w-0 max-w-[12rem]', }, ] @@ -538,7 +559,7 @@ const versionColumns = computed[]>(() => { columns.push({ key: 'environment', label: 'Environment', - cellClass: visibleCellClass, + cellClass: `${visibleCellClass} min-w-0 max-w-[12rem]`, }) } @@ -599,6 +620,26 @@ function getDisplayGameVersions(version: DisplayVersion): string[] { return formatVersionsForDisplay(version.game_versions, props.gameVersions) } +function getFilterTooltip(filter: string): string { + return formatMessage(messages.toggleFilterTooltip, { filter }) +} + +function getPlatformLabel(platform: string): string { + if (platform === 'modloader') { + return formatMessage(messages.modloaderShort) + } + + return formatTag(formatMessage, platform, 'loader') +} + +function getPlatformTooltip(platform: string): string { + return getFilterTooltip(formatTag(formatMessage, platform, 'loader')) +} + +function isFileRowVisible(version: VersionTableRow): boolean { + return props.showFiles && Array.isArray(version.files) && version.files.length > 0 +} + const normalizedVersions = computed(() => props.versions.map((version) => { const loaders = getModpackLoaders(version) @@ -674,9 +715,7 @@ function switchPage(page: number) { } function getVersionRowClass(): string { - return props.versionLink - ? 'group version-row-link cursor-pointer transition-[filter] [&:hover:not(:has([data-no-row-click]:hover))]:brightness-[115%]' - : 'group' + return props.versionLink ? 'group version-row-link cursor-pointer transition-[filter]' : 'group' } function openVersionRow(version: VersionTableRow) { @@ -709,11 +748,27 @@ const messages = defineMessages({ id: 'project.versions.version.withheld.tooltip', defaultMessage: 'Version withheld due to missing permissions', }, + toggleFilterTooltip: { + id: 'project.versions.filter.toggle-tooltip', + defaultMessage: 'Toggle filter for {filter}', + }, + modloaderShort: { + id: 'project.versions.platform.modloader.short', + defaultMessage: 'ModLoader', + }, })