fix: small breakdowns fixes (#6473)

* fix: members to member, no dependents to none

* feat: add fuzzy search to multiselect options search and projects dropdown

* fix: duplicate key
This commit is contained in:
Truman Gao
2026-06-22 20:40:53 +00:00
committed by GitHub
parent a747c2f290
commit 042a27a17c
4 changed files with 34 additions and 8 deletions
@@ -86,7 +86,7 @@ export const analyticsMessages = defineMessages({
},
noDependent: {
id: 'analytics.value.no-dependent',
defaultMessage: 'No dependents',
defaultMessage: 'None',
},
noDependentTooltip: {
id: 'analytics.value.no-dependent-tooltip',
@@ -370,7 +370,7 @@ export const analyticsBreakdownMessages = defineMessages({
},
members: {
id: 'analytics.breakdown.members',
defaultMessage: 'Members',
defaultMessage: 'Member',
},
dependentProjectDownload: {
id: 'analytics.breakdown.dependent-project-download',
@@ -12,6 +12,7 @@
:placeholder="formatMessage(analyticsMessages.selectProjects)"
:no-options-message="noProjectsMessage"
:searchable="projectOptions.length > 6"
fuzzy-search
:max-tag-rows="1"
:trigger-class="analyticsQueryChipTriggerClass"
fit-content
@@ -211,6 +212,7 @@
:placeholder="formatMessage(analyticsMessages.selectProjects)"
:no-options-message="noProjectsMessage"
:searchable="projectOptions.length > 6"
fuzzy-search
:max-tag-rows="1"
checkbox-position="right"
show-selection-actions
@@ -545,7 +547,7 @@ function getProjectOption(
return {
value: project.id,
label: project.name,
searchTerms: groupTitle ? [groupTitle] : undefined,
searchTerms: [project.id, groupTitle].filter((term): term is string => Boolean(term)),
}
}
+2 -2
View File
@@ -48,7 +48,7 @@
"message": "Loader"
},
"analytics.breakdown.members": {
"message": "Members"
"message": "Member"
},
"analytics.breakdown.monetization": {
"message": "Monetization"
@@ -504,7 +504,7 @@
"message": "Monetized"
},
"analytics.value.no-dependent": {
"message": "No dependents"
"message": "None"
},
"analytics.value.no-dependent-tooltip": {
"message": "Downloaded for reasons other than being a dependency"
@@ -400,6 +400,7 @@ import 'overlayscrollbars/overlayscrollbars.css'
import { CheckIcon, ChevronLeftIcon, MinusIcon, SearchIcon, XIcon } from '@modrinth/assets'
import { onClickOutside } from '@vueuse/core'
import { Menu } from 'floating-vue'
import Fuse from 'fuse.js'
import { OverlayScrollbars, type PartialOptions } from 'overlayscrollbars'
import {
type Component,
@@ -500,6 +501,7 @@ const props = withDefaults(
noOptionsMessage?: string
noResultsMessage?: string
disableSearchFilter?: boolean
fuzzySearch?: boolean
includeSelectAllOption?: boolean
selectAllLabel?: string
showSelectionActions?: boolean
@@ -519,6 +521,7 @@ const props = withDefaults(
noOptionsMessage: 'No options available',
noResultsMessage: 'No results found',
includeSelectAllOption: false,
fuzzySearch: false,
selectAllLabel: 'Select all',
showSelectionActions: false,
selectionActionsClearLabel: 'Clear',
@@ -605,12 +608,32 @@ const popperOverflowTags = shallowRef<MultiSelectOption<T>[]>([])
const lastClickedValue = shallowRef<{ value: T } | null>(null)
const fuzzySearchOptions = computed(() =>
selectableOptions.value.map((option) => ({
option,
label: option.label,
searchTerms: option.searchTerms ?? [],
})),
)
const fuzzyMatcher = computed(
() =>
new Fuse(fuzzySearchOptions.value, {
keys: ['label', 'searchTerms'],
threshold: 0.4,
ignoreLocation: true,
}),
)
const filteredOptions = computed(() => {
if (!searchQuery.value || !props.searchable || props.disableSearchFilter) {
return props.options
}
const query = searchQuery.value.toLowerCase()
const fuzzyMatches = props.fuzzySearch
? new Set(fuzzyMatcher.value.search(searchQuery.value).map(({ item }) => item.option))
: null
const items: MultiSelectItem<T>[] = []
let pendingSectionHeader: MultiSelectSectionHeader | null = null
@@ -620,9 +643,10 @@ const filteredOptions = computed(() => {
continue
}
const matches =
opt.label.toLowerCase().includes(query) ||
opt.searchTerms?.some((term) => term.toLowerCase().includes(query))
const matches = fuzzyMatches
? fuzzyMatches.has(opt)
: opt.label.toLowerCase().includes(query) ||
opt.searchTerms?.some((term) => term.toLowerCase().includes(query))
if (!matches) {
continue