mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
feat: exclude project types filter (#6660)
* feat: exclude project types filter * fix merge issue
This commit is contained in:
@@ -49,6 +49,7 @@ import {
|
|||||||
get_installed_project_ids as getInstalledProjectIds,
|
get_installed_project_ids as getInstalledProjectIds,
|
||||||
} from '@/helpers/instance'
|
} from '@/helpers/instance'
|
||||||
import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata'
|
import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata'
|
||||||
|
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||||
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
|
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
|
||||||
import { get_instance_worlds } from '@/helpers/worlds'
|
import { get_instance_worlds } from '@/helpers/worlds'
|
||||||
import { injectContentInstall } from '@/providers/content-install'
|
import { injectContentInstall } from '@/providers/content-install'
|
||||||
@@ -1041,10 +1042,24 @@ function getProjectBrowseQuery() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const advancedFiltersCollapsed = computed({
|
||||||
|
get: () => themeStore.getFeatureFlag('advanced_filters_collapsed'),
|
||||||
|
set: (value) => {
|
||||||
|
themeStore.featureFlags['advanced_filters_collapsed'] = value
|
||||||
|
getSettings()
|
||||||
|
.then((settings) => {
|
||||||
|
settings.feature_flags['advanced_filters_collapsed'] = value
|
||||||
|
return setSettings(settings)
|
||||||
|
})
|
||||||
|
.catch(handleError)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
provideBrowseManager({
|
provideBrowseManager({
|
||||||
tags,
|
tags,
|
||||||
projectType,
|
projectType,
|
||||||
...searchState,
|
...searchState,
|
||||||
|
advancedFiltersCollapsed,
|
||||||
getProjectLink: (result: Labrinth.Search.v2.ResultSearchProject) => ({
|
getProjectLink: (result: Labrinth.Search.v2.ResultSearchProject) => ({
|
||||||
path: `/project/${result.project_id ?? result.slug}`,
|
path: `/project/${result.project_id ?? result.slug}`,
|
||||||
query: getProjectBrowseQuery(),
|
query: getProjectBrowseQuery(),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const DEFAULT_FEATURE_FLAGS = {
|
|||||||
pride_fundraiser: true,
|
pride_fundraiser: true,
|
||||||
i18n_debug: false,
|
i18n_debug: false,
|
||||||
show_instance_play_time: true,
|
show_instance_play_time: true,
|
||||||
|
advanced_filters_collapsed: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const THEME_OPTIONS = ['dark', 'light', 'oled', 'system'] as const
|
export const THEME_OPTIONS = ['dark', 'light', 'oled', 'system'] as const
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
|||||||
showHostingAccessInstanceAuditLog: false,
|
showHostingAccessInstanceAuditLog: false,
|
||||||
versionDevInfoCollapsed: true,
|
versionDevInfoCollapsed: true,
|
||||||
alwaysShowVersionDevInfo: false,
|
alwaysShowVersionDevInfo: false,
|
||||||
|
advancedFiltersCollapsed: true,
|
||||||
} as const)
|
} as const)
|
||||||
|
|
||||||
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
|
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
|
||||||
|
|||||||
@@ -372,6 +372,14 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const advancedFiltersCollapsed = computed({
|
||||||
|
get: () => flags.value.advancedFiltersCollapsed,
|
||||||
|
set: (value) => {
|
||||||
|
flags.value.advancedFiltersCollapsed = value
|
||||||
|
saveFeatureFlags()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const projectTypeId = computed(() => projectType.value?.id ?? 'mod')
|
const projectTypeId = computed(() => projectType.value?.id ?? 'mod')
|
||||||
|
|
||||||
debug('projectTypeId:', projectTypeId.value)
|
debug('projectTypeId:', projectTypeId.value)
|
||||||
@@ -478,6 +486,7 @@ provideBrowseManager({
|
|||||||
showServerOnly: showServerOnlyToggle,
|
showServerOnly: showServerOnlyToggle,
|
||||||
serverOnlyLabel: computed(() => formatMessage(commonMessages.serverOnlyLabel)),
|
serverOnlyLabel: computed(() => formatMessage(commonMessages.serverOnlyLabel)),
|
||||||
hiddenFilterTypes: computed(() => (showServerOnlyToggle.value ? ['environment'] : [])),
|
hiddenFilterTypes: computed(() => (showServerOnlyToggle.value ? ['environment'] : [])),
|
||||||
|
advancedFiltersCollapsed,
|
||||||
displayMode: resultsDisplayMode,
|
displayMode: resultsDisplayMode,
|
||||||
cycleDisplayMode: cycleSearchDisplayMode,
|
cycleDisplayMode: cycleSearchDisplayMode,
|
||||||
maxResultsOptions: currentMaxResultsOptions,
|
maxResultsOptions: currentMaxResultsOptions,
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ pub enum FeatureFlag {
|
|||||||
I18nDebug,
|
I18nDebug,
|
||||||
ShowInstancePlayTime,
|
ShowInstancePlayTime,
|
||||||
SkipNonEssentialWarnings,
|
SkipNonEssentialWarnings,
|
||||||
|
AdvancedFiltersCollapsed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
|||||||
@@ -71,83 +71,105 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else #default>
|
<template v-else #default>
|
||||||
<slot name="prefix" />
|
<slot name="prefix" />
|
||||||
<StyledInput
|
<div
|
||||||
v-if="filterType.searchable"
|
v-if="filterType.display === 'toggle'"
|
||||||
:id="`search-${filterType.id}`"
|
:class="innerPanelClass ? innerPanelClass : ''"
|
||||||
v-model="query"
|
class="flex flex-col gap-3"
|
||||||
:icon="SearchIcon"
|
>
|
||||||
type="text"
|
<label
|
||||||
:placeholder="formatMessage(messages.searchPlaceholder)"
|
v-for="option in filterType.options"
|
||||||
autocomplete="off"
|
:key="`${filterType.id}-toggle-${option.id}`"
|
||||||
clearable
|
class="flex cursor-pointer items-center justify-between text-secondary gap-3 font-semibold"
|
||||||
size="small"
|
>
|
||||||
input-class="!bg-button-bg"
|
<span class="text-sm">{{ option.formatted_name ?? option.id }}</span>
|
||||||
wrapper-class="mx-2 my-1 w-[calc(100%-1rem)]"
|
<Toggle
|
||||||
/>
|
:model-value="isExcluded(option)"
|
||||||
|
small
|
||||||
<ScrollablePanel :class="{ 'h-[16rem]': scrollable }" :disable-scrolling="!scrollable">
|
class="shrink-0"
|
||||||
<div :class="innerPanelClass ? innerPanelClass : ''" class="flex flex-col gap-1">
|
@update:model-value="toggleNegativeFilter(option)"
|
||||||
<template v-if="groupedOptions">
|
/>
|
||||||
<SearchFilterGroup
|
</label>
|
||||||
v-for="[groupName, options] in groupedOptions"
|
</div>
|
||||||
:key="`${filterType.id}-group-${groupName}`"
|
<template v-if="filterType.display !== 'toggle'">
|
||||||
:group-name="groupName"
|
<StyledInput
|
||||||
:options="options"
|
v-if="filterType.searchable"
|
||||||
:supports-negative-filter="filterType.supports_negative_filter"
|
:id="`search-${filterType.id}`"
|
||||||
:included="isIncluded"
|
v-model="query"
|
||||||
:excluded="isExcluded"
|
:icon="SearchIcon"
|
||||||
@toggle="toggleFilter"
|
type="text"
|
||||||
@toggle-exclude="toggleNegativeFilter"
|
:placeholder="formatMessage(messages.searchPlaceholder)"
|
||||||
/>
|
autocomplete="off"
|
||||||
</template>
|
clearable
|
||||||
<template v-else>
|
size="small"
|
||||||
<SearchFilterOption
|
input-class="!bg-button-bg"
|
||||||
v-for="option in visibleOptions"
|
wrapper-class="mx-2 my-1 w-[calc(100%-1rem)]"
|
||||||
:key="`${filterType.id}-${option}`"
|
/>
|
||||||
:option="option"
|
<ScrollablePanel :class="{ 'h-[16rem]': scrollable }" :disable-scrolling="!scrollable">
|
||||||
:included="isIncluded(option)"
|
<div :class="innerPanelClass ? innerPanelClass : ''" class="flex flex-col gap-1">
|
||||||
:excluded="isExcluded(option)"
|
<template v-if="groupedOptions">
|
||||||
:supports-negative-filter="filterType.supports_negative_filter"
|
<SearchFilterGroup
|
||||||
:class="{
|
v-for="[groupName, options] in groupedOptions"
|
||||||
'mr-3': scrollable,
|
:key="`${filterType.id}-group-${groupName}`"
|
||||||
}"
|
:group-name="groupName"
|
||||||
@toggle="toggleFilter"
|
:options="options"
|
||||||
@toggle-exclude="toggleNegativeFilter"
|
:supports-negative-filter="filterType.supports_negative_filter"
|
||||||
|
:included="isIncluded"
|
||||||
|
:excluded="isExcluded"
|
||||||
|
@toggle="toggleFilter"
|
||||||
|
@toggle-exclude="toggleNegativeFilter"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<SearchFilterOption
|
||||||
|
v-for="option in visibleOptions"
|
||||||
|
:key="`${filterType.id}-${option}`"
|
||||||
|
:option="option"
|
||||||
|
:included="isIncluded(option)"
|
||||||
|
:excluded="isExcluded(option)"
|
||||||
|
:supports-negative-filter="filterType.supports_negative_filter"
|
||||||
|
:class="{
|
||||||
|
'mr-3': scrollable,
|
||||||
|
}"
|
||||||
|
@toggle="toggleFilter"
|
||||||
|
@toggle-exclude="toggleNegativeFilter"
|
||||||
|
>
|
||||||
|
<slot name="option" :filter="filterType" :option="option">
|
||||||
|
<span
|
||||||
|
v-if="option.icon"
|
||||||
|
class="inline-flex items-center justify-center shrink-0 h-4 w-4"
|
||||||
|
:style="iconStyle(option)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="typeof option.icon === 'string'"
|
||||||
|
class="h-4 w-4"
|
||||||
|
v-html="option.icon"
|
||||||
|
/>
|
||||||
|
<component :is="option.icon" v-else class="h-4 w-4" />
|
||||||
|
</span>
|
||||||
|
<span class="truncate text-sm" :style="iconStyle(option)">
|
||||||
|
{{ option.formatted_name ?? option.id }}
|
||||||
|
</span>
|
||||||
|
</slot>
|
||||||
|
</SearchFilterOption>
|
||||||
|
</template>
|
||||||
|
<button
|
||||||
|
v-if="filterType.display === 'expandable'"
|
||||||
|
class="flex bg-transparent text-secondary border-none cursor-pointer !w-full items-center gap-2 truncate rounded-xl px-2 py-1 text-sm font-semibold transition-all hover:text-contrast focus-visible:text-contrast active:scale-[0.98]"
|
||||||
|
@click="showMore = !showMore"
|
||||||
>
|
>
|
||||||
<slot name="option" :filter="filterType" :option="option">
|
<DropdownIcon
|
||||||
<span
|
class="h-4 w-4 transition-transform"
|
||||||
v-if="option.icon"
|
:class="{ 'rotate-180': showMore }"
|
||||||
class="inline-flex items-center justify-center shrink-0 h-4 w-4"
|
/>
|
||||||
:style="iconStyle(option)"
|
<span class="truncate text-sm">
|
||||||
>
|
{{
|
||||||
<div
|
showMore ? formatMessage(messages.showFewer) : formatMessage(messages.showMore)
|
||||||
v-if="typeof option.icon === 'string'"
|
}}
|
||||||
class="h-4 w-4"
|
</span>
|
||||||
v-html="option.icon"
|
</button>
|
||||||
/>
|
</div>
|
||||||
<component :is="option.icon" v-else class="h-4 w-4" />
|
</ScrollablePanel>
|
||||||
</span>
|
</template>
|
||||||
<span class="truncate text-sm" :style="iconStyle(option)">
|
|
||||||
{{ option.formatted_name ?? option.id }}
|
|
||||||
</span>
|
|
||||||
</slot>
|
|
||||||
</SearchFilterOption>
|
|
||||||
</template>
|
|
||||||
<button
|
|
||||||
v-if="filterType.display === 'expandable'"
|
|
||||||
class="flex bg-transparent text-secondary border-none cursor-pointer !w-full items-center gap-2 truncate rounded-xl px-2 py-1 text-sm font-semibold transition-all hover:text-contrast focus-visible:text-contrast active:scale-[0.98]"
|
|
||||||
@click="showMore = !showMore"
|
|
||||||
>
|
|
||||||
<DropdownIcon
|
|
||||||
class="h-4 w-4 transition-transform"
|
|
||||||
:class="{ 'rotate-180': showMore }"
|
|
||||||
/>
|
|
||||||
<span class="truncate text-sm">
|
|
||||||
{{ showMore ? formatMessage(messages.showFewer) : formatMessage(messages.showMore) }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ScrollablePanel>
|
|
||||||
<div :class="innerPanelClass ? innerPanelClass : ''" class="empty:hidden">
|
<div :class="innerPanelClass ? innerPanelClass : ''" class="empty:hidden">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-for="group in filterType.toggle_groups"
|
v-for="group in filterType.toggle_groups"
|
||||||
@@ -191,6 +213,7 @@ import { defineMessages, useVIntl } from '../../composables/i18n'
|
|||||||
import type { FilterOption, FilterType, FilterValue } from '../../utils/search'
|
import type { FilterOption, FilterType, FilterValue } from '../../utils/search'
|
||||||
import Accordion from '../base/Accordion.vue'
|
import Accordion from '../base/Accordion.vue'
|
||||||
import ButtonStyled from '../base/ButtonStyled.vue'
|
import ButtonStyled from '../base/ButtonStyled.vue'
|
||||||
|
import Toggle from '../base/Toggle.vue'
|
||||||
import { Checkbox, ScrollablePanel, StyledInput } from '../index'
|
import { Checkbox, ScrollablePanel, StyledInput } from '../index'
|
||||||
import SearchFilterGroup from './SearchFilterGroup.vue'
|
import SearchFilterGroup from './SearchFilterGroup.vue'
|
||||||
import SearchFilterOption from './SearchFilterOption.vue'
|
import SearchFilterOption from './SearchFilterOption.vue'
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export interface BrowseManagerContext {
|
|||||||
showServerOnly?: ComputedRef<boolean>
|
showServerOnly?: ComputedRef<boolean>
|
||||||
serverOnlyLabel?: ComputedRef<string>
|
serverOnlyLabel?: ComputedRef<string>
|
||||||
hiddenFilterTypes?: ComputedRef<string[]>
|
hiddenFilterTypes?: ComputedRef<string[]>
|
||||||
|
advancedFiltersCollapsed?: Ref<boolean>
|
||||||
onInstalled?: (projectId: string) => void
|
onInstalled?: (projectId: string) => void
|
||||||
|
|
||||||
displayMode?: Ref<'list' | 'grid' | 'gallery'> | ComputedRef<'list' | 'grid' | 'gallery'>
|
displayMode?: Ref<'list' | 'grid' | 'gallery'> | ComputedRef<'list' | 'grid' | 'gallery'>
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ const isApp = computed(() => ctx.variant === 'app')
|
|||||||
const lockedMessages = computed(() => toValue(ctx.lockedFilterMessages))
|
const lockedMessages = computed(() => toValue(ctx.lockedFilterMessages))
|
||||||
const hiddenFilterTypes = computed(() => ctx.hiddenFilterTypes?.value ?? [])
|
const hiddenFilterTypes = computed(() => ctx.hiddenFilterTypes?.value ?? [])
|
||||||
|
|
||||||
|
const advancedFiltersCollapsed = computed(() => ctx.advancedFiltersCollapsed?.value ?? true)
|
||||||
|
|
||||||
|
function setAdvancedFiltersCollapsed(collapsed: boolean) {
|
||||||
|
if (ctx.advancedFiltersCollapsed) {
|
||||||
|
ctx.advancedFiltersCollapsed.value = collapsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function closeFiltersMenu() {
|
function closeFiltersMenu() {
|
||||||
if (ctx.filtersMenuOpen) {
|
if (ctx.filtersMenuOpen) {
|
||||||
ctx.filtersMenuOpen.value = false
|
ctx.filtersMenuOpen.value = false
|
||||||
@@ -49,6 +57,9 @@ function hasProvidedFilter(filterId: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getFilterOpenByDefault(filterId: string): boolean {
|
function getFilterOpenByDefault(filterId: string): boolean {
|
||||||
|
if (filterId === 'advanced') {
|
||||||
|
return !advancedFiltersCollapsed.value
|
||||||
|
}
|
||||||
if (hasProvidedFilter(filterId)) {
|
if (hasProvidedFilter(filterId)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -188,6 +199,8 @@ function getFilterOpenByDefault(filterId: string): boolean {
|
|||||||
:content-class="contentClass"
|
:content-class="contentClass"
|
||||||
:inner-panel-class="innerPanelClass"
|
:inner-panel-class="innerPanelClass"
|
||||||
:open-by-default="getFilterOpenByDefault(filter.id)"
|
:open-by-default="getFilterOpenByDefault(filter.id)"
|
||||||
|
@on-open="() => filter.id === 'advanced' && setAdvancedFiltersCollapsed(false)"
|
||||||
|
@on-close="() => filter.id === 'advanced' && setAdvancedFiltersCollapsed(true)"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<h3 :class="isApp ? 'text-base m-0' : 'm-0 text-lg font-semibold'">
|
<h3 :class="isApp ? 'text-base m-0' : 'm-0 text-lg font-semibold'">
|
||||||
|
|||||||
@@ -3572,6 +3572,18 @@
|
|||||||
"search.filter.option.show_more": {
|
"search.filter.option.show_more": {
|
||||||
"defaultMessage": "Show more"
|
"defaultMessage": "Show more"
|
||||||
},
|
},
|
||||||
|
"search.filter_type.advanced": {
|
||||||
|
"defaultMessage": "Advanced"
|
||||||
|
},
|
||||||
|
"search.filter_type.advanced.exclude_datapack": {
|
||||||
|
"defaultMessage": "Exclude data packs"
|
||||||
|
},
|
||||||
|
"search.filter_type.advanced.exclude_mod": {
|
||||||
|
"defaultMessage": "Exclude mods"
|
||||||
|
},
|
||||||
|
"search.filter_type.advanced.exclude_plugin": {
|
||||||
|
"defaultMessage": "Exclude plugins"
|
||||||
|
},
|
||||||
"search.filter_type.environment": {
|
"search.filter_type.environment": {
|
||||||
"defaultMessage": "Environment"
|
"defaultMessage": "Environment"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export type FilterType = {
|
|||||||
ordering?: number
|
ordering?: number
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
display: 'all' | 'scrollable' | 'none'
|
display: 'all' | 'scrollable' | 'none' | 'toggle'
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
display: 'expandable'
|
display: 'expandable'
|
||||||
@@ -112,6 +112,12 @@ export interface SortType {
|
|||||||
|
|
||||||
const PLUGIN_PLATFORMS = ['bungeecord', 'waterfall', 'velocity', 'geyser']
|
const PLUGIN_PLATFORMS = ['bungeecord', 'waterfall', 'velocity', 'geyser']
|
||||||
|
|
||||||
|
const PROJECT_TYPE_EXCLUSION_FILTERS: Partial<Record<ProjectType, ProjectType[]>> = {
|
||||||
|
mod: ['plugin', 'datapack'],
|
||||||
|
plugin: ['mod', 'datapack'],
|
||||||
|
datapack: ['mod', 'plugin'],
|
||||||
|
}
|
||||||
|
|
||||||
export function useSearch(
|
export function useSearch(
|
||||||
projectTypes: Ref<ProjectType[]>,
|
projectTypes: Ref<ProjectType[]>,
|
||||||
tags: Ref<Tags>,
|
tags: Ref<Tags>,
|
||||||
@@ -143,6 +149,34 @@ export function useSearch(
|
|||||||
return formatCategory(formatMessage, categoryName)
|
return formatCategory(formatMessage, categoryName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatExcludeProjectTypeLabel = (projectType: ProjectType): string => {
|
||||||
|
switch (projectType) {
|
||||||
|
case 'mod':
|
||||||
|
return formatMessage(
|
||||||
|
defineMessage({
|
||||||
|
id: 'search.filter_type.advanced.exclude_mod',
|
||||||
|
defaultMessage: 'Exclude mods',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
case 'plugin':
|
||||||
|
return formatMessage(
|
||||||
|
defineMessage({
|
||||||
|
id: 'search.filter_type.advanced.exclude_plugin',
|
||||||
|
defaultMessage: 'Exclude plugins',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
case 'datapack':
|
||||||
|
return formatMessage(
|
||||||
|
defineMessage({
|
||||||
|
id: 'search.filter_type.advanced.exclude_datapack',
|
||||||
|
defaultMessage: 'Exclude data packs',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return projectType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const filters = computed(() => {
|
const filters = computed(() => {
|
||||||
const categoryFilters: Record<string, FilterType> = {}
|
const categoryFilters: Record<string, FilterType> = {}
|
||||||
for (const category of sortedCategories(tags.value, formatCategoryName, locale.value)) {
|
for (const category of sortedCategories(tags.value, formatCategoryName, locale.value)) {
|
||||||
@@ -171,6 +205,15 @@ export function useSearch(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const excludeableProjectTypes: ProjectType[] = []
|
||||||
|
for (const projectType of projectTypes.value) {
|
||||||
|
for (const target of PROJECT_TYPE_EXCLUSION_FILTERS[projectType] ?? []) {
|
||||||
|
if (!excludeableProjectTypes.includes(target)) {
|
||||||
|
excludeableProjectTypes.push(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const filterTypes: FilterType[] = [
|
const filterTypes: FilterType[] = [
|
||||||
...Object.values(categoryFilters),
|
...Object.values(categoryFilters),
|
||||||
{
|
{
|
||||||
@@ -429,6 +472,26 @@ export function useSearch(
|
|||||||
options: [],
|
options: [],
|
||||||
allows_custom_options: 'and',
|
allows_custom_options: 'and',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'advanced',
|
||||||
|
formatted_name: formatMessage(
|
||||||
|
defineMessage({
|
||||||
|
id: 'search.filter_type.advanced',
|
||||||
|
defaultMessage: 'Advanced',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
supported_project_types: ['mod', 'plugin', 'datapack'],
|
||||||
|
display: 'toggle',
|
||||||
|
query_param: 'a',
|
||||||
|
searchable: false,
|
||||||
|
ordering: -1000,
|
||||||
|
options: excludeableProjectTypes.map((target) => ({
|
||||||
|
id: target,
|
||||||
|
formatted_name: formatExcludeProjectTypeLabel(target),
|
||||||
|
method: 'and',
|
||||||
|
value: `all_project_types:${mapProjectTypeToSearch(target)}`,
|
||||||
|
})),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return filterTypes
|
return filterTypes
|
||||||
@@ -460,6 +523,9 @@ export function useSearch(
|
|||||||
console.error(`Filter type ${filterValue.type} not found`)
|
console.error(`Filter type ${filterValue.type} not found`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if (type.id === 'advanced') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
let option = type?.options.find((option) => option.id === filterValue.option)
|
let option = type?.options.find((option) => option.id === filterValue.option)
|
||||||
if (!option && type.allows_custom_options) {
|
if (!option && type.allows_custom_options) {
|
||||||
option = {
|
option = {
|
||||||
@@ -550,6 +616,15 @@ export function useSearch(
|
|||||||
parts.push(`project_types IN [${quoted}]`)
|
parts.push(`project_types IN [${quoted}]`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const excludedProjectTypes = filterValues
|
||||||
|
.filter((filterValue) => filterValue.type === 'advanced')
|
||||||
|
.map((filterValue) =>
|
||||||
|
formatSearchFilterValue(mapProjectTypeToSearch(filterValue.option as ProjectType)),
|
||||||
|
)
|
||||||
|
if (excludedProjectTypes.length > 0) {
|
||||||
|
parts.push(`all_project_types NOT IN [${excludedProjectTypes.join(', ')}]`)
|
||||||
|
}
|
||||||
|
|
||||||
return parts.join(' AND ')
|
return parts.join(' AND ')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user