mirror of
https://github.com/modrinth/code.git
synced 2026-08-24 16:44:51 +00:00
fix: version tags toggling every supported version and freezing the page (#6967)
Fixes #6601. Clicking any game version tag on the versions tab passed the release's entire game_versions array to toggleFilters, so clicking 26.x (or even a single version tag) selected every version the release supports. On projects supporting many versions this also froze the page for seconds: toggleFilters awaited a needlessly-async toggleFilter per version, which let Vue flush a full re-render between every iteration (~90 flushes on the reported project). - formatVersionsForDisplay's grouping now also returns the member versions per displayed range (getVersionGroupsForDisplay), so version tags toggle exactly the versions they represent - toggleFilters applies the whole batch synchronously in one update, and selects the missing versions unless all are already selected (instead of blindly inverting each one) Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -120,14 +120,17 @@
|
||||
<template #cell-gameVersions="{ row: version }">
|
||||
<div class="flex min-w-0 w-full max-w-[12rem] flex-wrap gap-1">
|
||||
<TagItem
|
||||
v-for="gameVersion in getDisplayGameVersions(version).slice(0, MAX_GAME_VERSION_TAGS)"
|
||||
:key="`version-tag-${gameVersion}`"
|
||||
v-tooltip="getFilterTooltip(gameVersion)"
|
||||
v-for="gameVersionGroup in getDisplayGameVersions(version).slice(
|
||||
0,
|
||||
MAX_GAME_VERSION_TAGS,
|
||||
)"
|
||||
:key="`version-tag-${gameVersionGroup.label}`"
|
||||
v-tooltip="getFilterTooltip(gameVersionGroup.label)"
|
||||
data-no-row-click
|
||||
class="w-fit max-w-full truncate"
|
||||
:action="() => versionFilters?.toggleFilters('gameVersion', version.game_versions)"
|
||||
:action="() => versionFilters?.toggleFilters('gameVersion', gameVersionGroup.versions)"
|
||||
>
|
||||
<span class="min-w-0 truncate">{{ gameVersion }}</span>
|
||||
<span class="min-w-0 truncate">{{ gameVersionGroup.label }}</span>
|
||||
</TagItem>
|
||||
<Menu
|
||||
v-if="getDisplayGameVersions(version).length > MAX_GAME_VERSION_TAGS"
|
||||
@@ -144,12 +147,16 @@
|
||||
<template #popper>
|
||||
<div class="flex max-w-[20rem] flex-wrap gap-1">
|
||||
<TagItem
|
||||
v-for="gameVersion in getDisplayGameVersions(version).slice(MAX_GAME_VERSION_TAGS)"
|
||||
:key="`overflow-version-tag-${gameVersion}`"
|
||||
v-for="gameVersionGroup in getDisplayGameVersions(version).slice(
|
||||
MAX_GAME_VERSION_TAGS,
|
||||
)"
|
||||
:key="`overflow-version-tag-${gameVersionGroup.label}`"
|
||||
class="w-fit max-w-full truncate"
|
||||
:action="() => versionFilters?.toggleFilters('gameVersion', version.game_versions)"
|
||||
:action="
|
||||
() => versionFilters?.toggleFilters('gameVersion', gameVersionGroup.versions)
|
||||
"
|
||||
>
|
||||
<span class="min-w-0 truncate">{{ gameVersion }}</span>
|
||||
<span class="min-w-0 truncate">{{ gameVersionGroup.label }}</span>
|
||||
</TagItem>
|
||||
</div>
|
||||
</template>
|
||||
@@ -316,16 +323,18 @@
|
||||
<div class="flex flex-col justify-center gap-3">
|
||||
<div class="flex flex-row flex-wrap items-center gap-1.5">
|
||||
<TagItem
|
||||
v-for="gameVersion in getDisplayGameVersions(version).slice(
|
||||
v-for="gameVersionGroup in getDisplayGameVersions(version).slice(
|
||||
0,
|
||||
MAX_GAME_VERSION_TAGS,
|
||||
)"
|
||||
:key="`version-tag-${gameVersion}`"
|
||||
v-tooltip="getFilterTooltip(gameVersion)"
|
||||
:key="`version-tag-${gameVersionGroup.label}`"
|
||||
v-tooltip="getFilterTooltip(gameVersionGroup.label)"
|
||||
class="smart-clickable:allow-pointer-events"
|
||||
:action="() => versionFilters?.toggleFilters('gameVersion', version.game_versions)"
|
||||
:action="
|
||||
() => versionFilters?.toggleFilters('gameVersion', gameVersionGroup.versions)
|
||||
"
|
||||
>
|
||||
{{ gameVersion }}
|
||||
{{ gameVersionGroup.label }}
|
||||
</TagItem>
|
||||
<Menu
|
||||
v-if="getDisplayGameVersions(version).length > MAX_GAME_VERSION_TAGS"
|
||||
@@ -339,15 +348,16 @@
|
||||
<template #popper>
|
||||
<div class="flex max-w-[20rem] flex-wrap gap-1">
|
||||
<TagItem
|
||||
v-for="gameVersion in getDisplayGameVersions(version).slice(
|
||||
v-for="gameVersionGroup in getDisplayGameVersions(version).slice(
|
||||
MAX_GAME_VERSION_TAGS,
|
||||
)"
|
||||
:key="`overflow-version-tag-${gameVersion}`"
|
||||
:key="`overflow-version-tag-${gameVersionGroup.label}`"
|
||||
:action="
|
||||
() => versionFilters?.toggleFilters('gameVersion', version.game_versions)
|
||||
() =>
|
||||
versionFilters?.toggleFilters('gameVersion', gameVersionGroup.versions)
|
||||
"
|
||||
>
|
||||
{{ gameVersion }}
|
||||
{{ gameVersionGroup.label }}
|
||||
</TagItem>
|
||||
</div>
|
||||
</template>
|
||||
@@ -461,7 +471,12 @@ import {
|
||||
VersionChannelIndicator,
|
||||
VersionFilterControl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatVersionsForDisplay, type GameVersionTag, type Version } from '@modrinth/utils'
|
||||
import {
|
||||
type GameVersionTag,
|
||||
getVersionGroupsForDisplay,
|
||||
type Version,
|
||||
type VersionDisplayGroup,
|
||||
} from '@modrinth/utils'
|
||||
import { Menu } from 'floating-vue'
|
||||
import { computed, type Ref, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
@@ -622,8 +637,8 @@ function hasNoModLoader(loaders: string[]): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
function getDisplayGameVersions(version: DisplayVersion): string[] {
|
||||
return formatVersionsForDisplay(version.game_versions, props.gameVersions)
|
||||
function getDisplayGameVersions(version: DisplayVersion): VersionDisplayGroup[] {
|
||||
return getVersionGroupsForDisplay(version.game_versions, props.gameVersions)
|
||||
}
|
||||
|
||||
function getFilterTooltip(filter: string): string {
|
||||
|
||||
@@ -243,31 +243,35 @@ if (selectedGameVersions.value.some((version) => !isReleaseGameVersion(version))
|
||||
showSnapshots.value = true
|
||||
}
|
||||
|
||||
async function toggleFilters(type: FilterType, filters: Filter[]) {
|
||||
for (const filter of filters) {
|
||||
await toggleFilter(type, filter, true)
|
||||
function selectedFiltersOfType(type: FilterType) {
|
||||
if (type === 'channel') {
|
||||
return selectedChannels
|
||||
} else if (type === 'gameVersion') {
|
||||
return selectedGameVersions
|
||||
} else {
|
||||
return selectedPlatforms
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFilters(type: FilterType, filters: Filter[]) {
|
||||
const selected = selectedFiltersOfType(type)
|
||||
const allSelected = filters.every((filter) => selected.value.includes(filter))
|
||||
|
||||
selected.value = allSelected
|
||||
? selected.value.filter((x) => !filters.includes(x))
|
||||
: [...selected.value, ...filters.filter((filter) => !selected.value.includes(filter))]
|
||||
|
||||
updateFilters()
|
||||
}
|
||||
|
||||
async function toggleFilter(type: FilterType, filter: Filter, bulk = false) {
|
||||
if (type === 'channel') {
|
||||
selectedChannels.value = selectedChannels.value.includes(filter)
|
||||
? selectedChannels.value.filter((x) => x !== filter)
|
||||
: [...selectedChannels.value, filter]
|
||||
} else if (type === 'gameVersion') {
|
||||
selectedGameVersions.value = selectedGameVersions.value.includes(filter)
|
||||
? selectedGameVersions.value.filter((x) => x !== filter)
|
||||
: [...selectedGameVersions.value, filter]
|
||||
} else if (type === 'platform') {
|
||||
selectedPlatforms.value = selectedPlatforms.value.includes(filter)
|
||||
? selectedPlatforms.value.filter((x) => x !== filter)
|
||||
: [...selectedPlatforms.value, filter]
|
||||
}
|
||||
if (!bulk) {
|
||||
updateFilters()
|
||||
}
|
||||
function toggleFilter(type: FilterType, filter: Filter) {
|
||||
const selected = selectedFiltersOfType(type)
|
||||
|
||||
selected.value = selected.value.includes(filter)
|
||||
? selected.value.filter((x) => x !== filter)
|
||||
: [...selected.value, filter]
|
||||
|
||||
updateFilters()
|
||||
}
|
||||
|
||||
function updateSelectedGameVersions(versions: string[]) {
|
||||
@@ -302,7 +306,7 @@ function updateShowSnapshots(value: boolean, _event?: MouseEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
async function clearFilters() {
|
||||
function clearFilters() {
|
||||
selectedChannels.value = []
|
||||
selectedGameVersions.value = []
|
||||
selectedPlatforms.value = []
|
||||
|
||||
Reference in New Issue
Block a user