mirror of
https://github.com/modrinth/code.git
synced 2026-08-27 18:14:49 +00:00
fix: environment filters (#6611)
This commit is contained in:
@@ -180,7 +180,7 @@
|
|||||||
"message": "Discover servers"
|
"message": "Discover servers"
|
||||||
},
|
},
|
||||||
"app.browse.hide-added-servers": {
|
"app.browse.hide-added-servers": {
|
||||||
"message": "Hide already added servers"
|
"message": "Hide servers already added"
|
||||||
},
|
},
|
||||||
"app.browse.project-type.modpacks": {
|
"app.browse.project-type.modpacks": {
|
||||||
"message": "Modpacks"
|
"message": "Modpacks"
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
hideAddedServers: {
|
hideAddedServers: {
|
||||||
id: 'app.browse.hide-added-servers',
|
id: 'app.browse.hide-added-servers',
|
||||||
defaultMessage: 'Hide already added servers',
|
defaultMessage: 'Hide servers already added',
|
||||||
},
|
},
|
||||||
installingToServer: {
|
installingToServer: {
|
||||||
id: 'app.browse.server.installing',
|
id: 'app.browse.server.installing',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type {
|
|||||||
BrowseInstallPlan,
|
BrowseInstallPlan,
|
||||||
BrowseSearchState,
|
BrowseSearchState,
|
||||||
CreationFlowContextValue,
|
CreationFlowContextValue,
|
||||||
|
EnvironmentSearchOverride,
|
||||||
FilterValue,
|
FilterValue,
|
||||||
PendingServerContentInstall,
|
PendingServerContentInstall,
|
||||||
PendingServerContentInstallType,
|
PendingServerContentInstallType,
|
||||||
@@ -160,6 +161,7 @@ export function useServerInstallContent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const serverHideInstalled = ref(false)
|
const serverHideInstalled = ref(false)
|
||||||
|
const serverContentServerOnly = ref(false)
|
||||||
const hideSelectedServerInstalls = ref(false)
|
const hideSelectedServerInstalls = ref(false)
|
||||||
const installingProjectIds = ref<Set<string>>(new Set())
|
const installingProjectIds = ref<Set<string>>(new Set())
|
||||||
const optimisticallyInstalledProjectIds = ref<Set<string>>(new Set())
|
const optimisticallyInstalledProjectIds = ref<Set<string>>(new Set())
|
||||||
@@ -348,10 +350,6 @@ export function useServerInstallContent({
|
|||||||
filters.push({ type: 'plugin_loader', option: platform })
|
filters.push({ type: 'plugin_loader', option: platform })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projectType.value?.id === 'mod') {
|
|
||||||
filters.push({ type: 'environment', option: 'server' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serverHideInstalled.value && hiddenInstalledProjectIds.value.size > 0) {
|
if (serverHideInstalled.value && hiddenInstalledProjectIds.value.size > 0) {
|
||||||
for (const x of hiddenInstalledProjectIds.value) {
|
for (const x of hiddenInstalledProjectIds.value) {
|
||||||
filters.push({
|
filters.push({
|
||||||
@@ -383,6 +381,28 @@ export function useServerInstallContent({
|
|||||||
return filters
|
return filters
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const showServerOnlyToggle = computed(() => !!serverData.value && projectType.value?.id === 'mod')
|
||||||
|
|
||||||
|
const serverEnvironmentOverride = computed<EnvironmentSearchOverride | undefined>(() => {
|
||||||
|
if (!showServerOnlyToggle.value) return undefined
|
||||||
|
if (serverContentServerOnly.value) {
|
||||||
|
return {
|
||||||
|
mode: 'include',
|
||||||
|
values: [
|
||||||
|
'server_only',
|
||||||
|
'dedicated_server_only',
|
||||||
|
'server_only_client_optional',
|
||||||
|
'client_or_server_prefers_both',
|
||||||
|
'client_or_server',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mode: 'exclude',
|
||||||
|
values: ['client_only', 'singleplayer_only'],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function getCurrentServerInstallType(): BrowseInstallContentType {
|
function getCurrentServerInstallType(): BrowseInstallContentType {
|
||||||
const type = projectType.value?.id
|
const type = projectType.value?.id
|
||||||
if (type === 'modpack' || type === 'mod' || type === 'plugin' || type === 'datapack') {
|
if (type === 'modpack' || type === 'mod' || type === 'plugin' || type === 'datapack') {
|
||||||
@@ -737,6 +757,10 @@ export function useServerInstallContent({
|
|||||||
serverHideInstalled.value = route.query.shi === 'true'
|
serverHideInstalled.value = route.query.shi === 'true'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (route.query.so && projectType.value?.id === 'mod') {
|
||||||
|
serverContentServerOnly.value = route.query.so === 'true'
|
||||||
|
}
|
||||||
|
|
||||||
watch(serverHideInstalled, (hideInstalled) => {
|
watch(serverHideInstalled, (hideInstalled) => {
|
||||||
if (hideInstalled) {
|
if (hideInstalled) {
|
||||||
syncHiddenInstalledProjectIds()
|
syncHiddenInstalledProjectIds()
|
||||||
@@ -763,6 +787,9 @@ export function useServerInstallContent({
|
|||||||
serverContentData,
|
serverContentData,
|
||||||
serverFilters,
|
serverFilters,
|
||||||
serverHideInstalled,
|
serverHideInstalled,
|
||||||
|
serverContentServerOnly,
|
||||||
|
showServerOnlyToggle,
|
||||||
|
serverEnvironmentOverride,
|
||||||
hideSelectedServerInstalls,
|
hideSelectedServerInstalls,
|
||||||
installingProjectIds,
|
installingProjectIds,
|
||||||
optimisticallyInstalledProjectIds,
|
optimisticallyInstalledProjectIds,
|
||||||
|
|||||||
@@ -158,6 +158,9 @@ const {
|
|||||||
serverContentData,
|
serverContentData,
|
||||||
serverFilters,
|
serverFilters,
|
||||||
serverHideInstalled,
|
serverHideInstalled,
|
||||||
|
serverContentServerOnly,
|
||||||
|
showServerOnlyToggle,
|
||||||
|
serverEnvironmentOverride,
|
||||||
hideSelectedServerInstalls,
|
hideSelectedServerInstalls,
|
||||||
installingProjectIds,
|
installingProjectIds,
|
||||||
optimisticallyInstalledProjectIds,
|
optimisticallyInstalledProjectIds,
|
||||||
@@ -378,10 +381,12 @@ const searchState = useBrowseSearch({
|
|||||||
projectType: projectTypeId,
|
projectType: projectTypeId,
|
||||||
tags,
|
tags,
|
||||||
providedFilters: serverFilters,
|
providedFilters: serverFilters,
|
||||||
|
environmentOverride: serverEnvironmentOverride,
|
||||||
search,
|
search,
|
||||||
persistentQueryParams: ['sid', 'wid', 'shi', 'from'],
|
persistentQueryParams: ['sid', 'wid', 'shi', 'so', 'from'],
|
||||||
getExtraQueryParams: () => ({
|
getExtraQueryParams: () => ({
|
||||||
shi: serverHideInstalled.value ? 'true' : undefined,
|
shi: serverHideInstalled.value ? 'true' : undefined,
|
||||||
|
so: showServerOnlyToggle.value && serverContentServerOnly.value ? 'true' : undefined,
|
||||||
}),
|
}),
|
||||||
maxResultsOptions: currentMaxResultsOptions,
|
maxResultsOptions: currentMaxResultsOptions,
|
||||||
displayMode: resultsDisplayMode,
|
displayMode: resultsDisplayMode,
|
||||||
@@ -469,6 +474,10 @@ provideBrowseManager({
|
|||||||
queuedServerInstallCount.value > 0,
|
queuedServerInstallCount.value > 0,
|
||||||
),
|
),
|
||||||
hideSelectedLabel: computed(() => formatMessage(commonMessages.hideSelectedContentLabel)),
|
hideSelectedLabel: computed(() => formatMessage(commonMessages.hideSelectedContentLabel)),
|
||||||
|
serverOnly: serverContentServerOnly,
|
||||||
|
showServerOnly: showServerOnlyToggle,
|
||||||
|
serverOnlyLabel: computed(() => formatMessage(commonMessages.serverOnlyLabel)),
|
||||||
|
hiddenFilterTypeIds: computed(() => (showServerOnlyToggle.value ? ['environment'] : [])),
|
||||||
displayMode: resultsDisplayMode,
|
displayMode: resultsDisplayMode,
|
||||||
cycleDisplayMode: cycleSearchDisplayMode,
|
cycleDisplayMode: cycleSearchDisplayMode,
|
||||||
maxResultsOptions: currentMaxResultsOptions,
|
maxResultsOptions: currentMaxResultsOptions,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<div v-if="divider && !!slots.title" class="flex items-center gap-4 mb-4">
|
<div v-if="divider && (!!slots.title || !!slots.button)" class="flex items-center gap-4 mb-4">
|
||||||
<button
|
<button
|
||||||
:class="
|
:class="
|
||||||
buttonClass ??
|
buttonClass ??
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<hr class="h-px w-full border-none bg-divider" aria-hidden="true" />
|
<hr class="h-px w-full border-none bg-divider" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-else-if="!!slots.title"
|
v-else-if="!!slots.title || !!slots.button"
|
||||||
:class="buttonClass ?? 'flex flex-col gap-2 bg-transparent m-0 p-0 border-none'"
|
:class="buttonClass ?? 'flex flex-col gap-2 bg-transparent m-0 p-0 border-none'"
|
||||||
@click="() => (forceOpen ? undefined : toggledOpen ? close() : open())"
|
@click="() => (forceOpen ? undefined : toggledOpen ? close() : open())"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,10 +7,16 @@
|
|||||||
title-wrapper-class="flex flex-col gap-2 justify-start items-start"
|
title-wrapper-class="flex flex-col gap-2 justify-start items-start"
|
||||||
:open-by-default="openByDefault !== undefined ? openByDefault : true"
|
:open-by-default="openByDefault !== undefined ? openByDefault : true"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #button="{ open }">
|
||||||
<slot name="header" :filter="filterType">
|
<div class="flex items-center gap-1 w-full text-contrast">
|
||||||
<h2 class="font-semibold">{{ filterType.formatted_name }}</h2>
|
<slot name="header" :filter="filterType">
|
||||||
</slot>
|
<h2 class="text-base font-semibold text-red">{{ filterType.formatted_name }}</h2>
|
||||||
|
</slot>
|
||||||
|
<DropdownIcon
|
||||||
|
class="ml-auto size-5 transition-transform duration-300 shrink-0 text-primary group-hover:text-contrast"
|
||||||
|
:class="{ 'rotate-180': open }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
v-if="
|
v-if="
|
||||||
@@ -39,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="locked" #default>
|
<template v-if="locked" #default>
|
||||||
<div class="flex flex-col gap-2 p-3 border-dashed border-2 rounded-2xl border-divider mx-2">
|
<div class="flex flex-col gap-2 p-3 border-dashed border-2 rounded-2xl border-divider">
|
||||||
<p class="m-0 font-bold items-center">
|
<p class="m-0 font-bold items-center">
|
||||||
<slot :name="`locked-${filterType.id}`">
|
<slot :name="`locked-${filterType.id}`">
|
||||||
{{ formatMessage(messages.lockedTitle, { type: filterType.formatted_name }) }}
|
{{ formatMessage(messages.lockedTitle, { type: filterType.formatted_name }) }}
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ import { computed, nextTick, ref, shallowRef, watch } from 'vue'
|
|||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
import { useDebugLogger } from '#ui/composables/debug-logger'
|
import { useDebugLogger } from '#ui/composables/debug-logger'
|
||||||
import type { FilterType, FilterValue, ProjectType, SortType } from '#ui/utils/search'
|
import type {
|
||||||
|
EnvironmentSearchOverride,
|
||||||
|
FilterType,
|
||||||
|
FilterValue,
|
||||||
|
ProjectType,
|
||||||
|
SortType,
|
||||||
|
} from '#ui/utils/search'
|
||||||
import { LOADER_FILTER_TYPES, useSearch } from '#ui/utils/search'
|
import { LOADER_FILTER_TYPES, useSearch } from '#ui/utils/search'
|
||||||
import { useServerSearch } from '#ui/utils/server-search'
|
import { useServerSearch } from '#ui/utils/server-search'
|
||||||
|
|
||||||
@@ -18,6 +24,7 @@ export interface UseBrowseSearchOptions {
|
|||||||
categories: Labrinth.Tags.v2.Category[]
|
categories: Labrinth.Tags.v2.Category[]
|
||||||
}>
|
}>
|
||||||
providedFilters?: ComputedRef<FilterValue[]>
|
providedFilters?: ComputedRef<FilterValue[]>
|
||||||
|
environmentOverride?: ComputedRef<EnvironmentSearchOverride | undefined>
|
||||||
search: (params: string) => Promise<BrowseSearchResponse>
|
search: (params: string) => Promise<BrowseSearchResponse>
|
||||||
persistentQueryParams: string[]
|
persistentQueryParams: string[]
|
||||||
getExtraQueryParams?: () => Record<string, string | undefined>
|
getExtraQueryParams?: () => Record<string, string | undefined>
|
||||||
@@ -82,7 +89,12 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt
|
|||||||
sortTypes,
|
sortTypes,
|
||||||
requestParams,
|
requestParams,
|
||||||
createPageParams,
|
createPageParams,
|
||||||
} = useSearch(projectTypes, options.tags, options.providedFilters ?? computed(() => []))
|
} = useSearch(
|
||||||
|
projectTypes,
|
||||||
|
options.tags,
|
||||||
|
options.providedFilters ?? computed(() => []),
|
||||||
|
options.environmentOverride ?? computed(() => undefined),
|
||||||
|
)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
serverCurrentSortType,
|
serverCurrentSortType,
|
||||||
|
|||||||
@@ -164,7 +164,11 @@ const messages = defineMessages({
|
|||||||
<SearchFilterControl
|
<SearchFilterControl
|
||||||
v-else
|
v-else
|
||||||
v-model:selected-filters="ctx.currentFilters.value"
|
v-model:selected-filters="ctx.currentFilters.value"
|
||||||
:filters="ctx.filters.value.filter((f) => f.display !== 'none')"
|
:filters="
|
||||||
|
ctx.filters.value.filter(
|
||||||
|
(f) => f.display !== 'none' && !(ctx.hiddenFilterTypes?.value ?? []).includes(f.id),
|
||||||
|
)
|
||||||
|
"
|
||||||
:provided-filters="ctx.providedFilters?.value ?? []"
|
:provided-filters="ctx.providedFilters?.value ?? []"
|
||||||
:overridden-provided-filter-types="ctx.overriddenProvidedFilterTypes.value"
|
:overridden-provided-filter-types="ctx.overriddenProvidedFilterTypes.value"
|
||||||
:provided-message="lockedMessages?.providedBy"
|
:provided-message="lockedMessages?.providedBy"
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ export interface BrowseManagerContext {
|
|||||||
hideSelected?: Ref<boolean>
|
hideSelected?: Ref<boolean>
|
||||||
showHideSelected?: ComputedRef<boolean>
|
showHideSelected?: ComputedRef<boolean>
|
||||||
hideSelectedLabel?: ComputedRef<string>
|
hideSelectedLabel?: ComputedRef<string>
|
||||||
|
serverOnly?: Ref<boolean>
|
||||||
|
showServerOnly?: ComputedRef<boolean>
|
||||||
|
serverOnlyLabel?: ComputedRef<string>
|
||||||
|
hiddenFilterTypes?: ComputedRef<string[]>
|
||||||
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'>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { InfoIcon, XIcon } from '@modrinth/assets'
|
|||||||
import { computed, toValue } from 'vue'
|
import { computed, toValue } from 'vue'
|
||||||
|
|
||||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||||
import Checkbox from '#ui/components/base/Checkbox.vue'
|
import Toggle from '#ui/components/base/Toggle.vue'
|
||||||
import SearchSidebarFilter from '#ui/components/search/SearchSidebarFilter.vue'
|
import SearchSidebarFilter from '#ui/components/search/SearchSidebarFilter.vue'
|
||||||
import { useVIntl } from '#ui/composables/i18n'
|
import { useVIntl } from '#ui/composables/i18n'
|
||||||
import { commonMessages } from '#ui/utils/common-messages'
|
import { commonMessages } from '#ui/utils/common-messages'
|
||||||
@@ -15,6 +15,7 @@ const { formatMessage } = useVIntl()
|
|||||||
|
|
||||||
const isApp = computed(() => ctx.variant === 'app')
|
const isApp = computed(() => ctx.variant === 'app')
|
||||||
const lockedMessages = computed(() => toValue(ctx.lockedFilterMessages))
|
const lockedMessages = computed(() => toValue(ctx.lockedFilterMessages))
|
||||||
|
const hiddenFilterTypes = computed(() => ctx.hiddenFilterTypes?.value ?? [])
|
||||||
|
|
||||||
function closeFiltersMenu() {
|
function closeFiltersMenu() {
|
||||||
if (ctx.filtersMenuOpen) {
|
if (ctx.filtersMenuOpen) {
|
||||||
@@ -37,7 +38,7 @@ const buttonClass = computed(() => {
|
|||||||
if (isApp.value) {
|
if (isApp.value) {
|
||||||
return 'button-animation flex flex-col gap-1 px-3 py-3 w-full bg-transparent cursor-pointer border-none hover:bg-button-bg'
|
return 'button-animation flex flex-col gap-1 px-3 py-3 w-full bg-transparent cursor-pointer border-none hover:bg-button-bg'
|
||||||
}
|
}
|
||||||
return 'button-animation flex flex-col gap-1 px-6 py-3 w-full bg-transparent cursor-pointer border-none'
|
return 'button-animation flex flex-col gap-1 px-4 py-3 w-full bg-transparent cursor-pointer border-none'
|
||||||
})
|
})
|
||||||
|
|
||||||
const contentClass = computed(() => (isApp.value ? 'mt-2 mb-3' : 'mb-4 mx-3'))
|
const contentClass = computed(() => (isApp.value ? 'mt-2 mb-3' : 'mb-4 mx-3'))
|
||||||
@@ -99,38 +100,60 @@ function getFilterOpenByDefault(filterId: string): boolean {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="ctx.showHideInstalled?.value || ctx.showHideSelected?.value"
|
v-if="
|
||||||
|
ctx.showHideInstalled?.value || ctx.showHideSelected?.value || ctx.showServerOnly?.value
|
||||||
|
"
|
||||||
:class="
|
:class="
|
||||||
isApp
|
isApp
|
||||||
? 'flex flex-col gap-3 border-0 border-b-[1px] p-4 last:border-b-0 border-[--brand-gradient-border] border-solid'
|
? 'flex flex-col gap-3 border-0 border-b-[1px] p-4 last:border-b-0 border-[--brand-gradient-border] border-solid'
|
||||||
: 'card-shadow flex flex-col gap-3 rounded-2xl bg-bg-raised p-4'
|
: 'card-shadow flex flex-col gap-3 rounded-2xl bg-bg-raised border-solid border-surface-4 border p-4'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<Checkbox
|
<label
|
||||||
|
v-if="ctx.showServerOnly?.value"
|
||||||
|
class="flex cursor-pointer items-center justify-between gap-3 text-contrast font-medium"
|
||||||
|
>
|
||||||
|
{{ ctx.serverOnlyLabel?.value ?? formatMessage(commonMessages.serverOnlyLabel) }}
|
||||||
|
<Toggle
|
||||||
|
v-model="ctx.serverOnly!.value"
|
||||||
|
small
|
||||||
|
class="shrink-0"
|
||||||
|
@update:model-value="ctx.onFilterChange()"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
v-if="ctx.showHideInstalled?.value"
|
v-if="ctx.showHideInstalled?.value"
|
||||||
v-model="ctx.hideInstalled!.value"
|
class="flex cursor-pointer items-center justify-between gap-3 text-contrast font-medium"
|
||||||
:label="
|
>
|
||||||
|
{{
|
||||||
ctx.hideInstalledLabel?.value ?? formatMessage(commonMessages.hideInstalledContentLabel)
|
ctx.hideInstalledLabel?.value ?? formatMessage(commonMessages.hideInstalledContentLabel)
|
||||||
"
|
}}
|
||||||
class="filter-checkbox"
|
<Toggle
|
||||||
@update:model-value="ctx.onFilterChange()"
|
v-model="ctx.hideInstalled!.value"
|
||||||
@click.prevent.stop
|
small
|
||||||
/>
|
class="shrink-0"
|
||||||
<Checkbox
|
@update:model-value="ctx.onFilterChange()"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
v-if="ctx.showHideSelected?.value"
|
v-if="ctx.showHideSelected?.value"
|
||||||
v-model="ctx.hideSelected!.value"
|
class="flex cursor-pointer items-center justify-between gap-3 text-contrast font-medium"
|
||||||
:label="
|
>
|
||||||
ctx.hideSelectedLabel?.value ?? formatMessage(commonMessages.hideSelectedContentLabel)
|
{{ ctx.hideSelectedLabel?.value ?? formatMessage(commonMessages.hideSelectedContentLabel) }}
|
||||||
"
|
<Toggle
|
||||||
class="filter-checkbox"
|
v-model="ctx.hideSelected!.value"
|
||||||
@update:model-value="ctx.onFilterChange()"
|
small
|
||||||
@click.prevent.stop
|
class="shrink-0"
|
||||||
/>
|
@update:model-value="ctx.onFilterChange()"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="ctx.isServerType.value">
|
<template v-if="ctx.isServerType.value">
|
||||||
<SearchSidebarFilter
|
<SearchSidebarFilter
|
||||||
v-for="filterType in ctx.serverFilterTypes.value.filter((f) => f.options.length > 0)"
|
v-for="filterType in ctx.serverFilterTypes.value.filter(
|
||||||
|
(f) => f.options.length > 0 && !hiddenFilterTypes.includes(f.id),
|
||||||
|
)"
|
||||||
:key="`server-filter-${filterType.id}`"
|
:key="`server-filter-${filterType.id}`"
|
||||||
v-model:selected-filters="ctx.serverCurrentFilters.value"
|
v-model:selected-filters="ctx.serverCurrentFilters.value"
|
||||||
v-model:toggled-groups="ctx.serverToggledGroups.value"
|
v-model:toggled-groups="ctx.serverToggledGroups.value"
|
||||||
@@ -143,7 +166,7 @@ function getFilterOpenByDefault(filterId: string): boolean {
|
|||||||
:open-by-default="getFilterOpenByDefault(filterType.id)"
|
:open-by-default="getFilterOpenByDefault(filterType.id)"
|
||||||
>
|
>
|
||||||
<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-base font-semibold'">
|
||||||
{{ filterType.formatted_name }}
|
{{ filterType.formatted_name }}
|
||||||
</h3>
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
@@ -151,7 +174,9 @@ function getFilterOpenByDefault(filterId: string): boolean {
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<SearchSidebarFilter
|
<SearchSidebarFilter
|
||||||
v-for="filter in ctx.filters.value.filter((f) => f.display !== 'none')"
|
v-for="filter in ctx.filters.value.filter(
|
||||||
|
(f) => f.display !== 'none' && !hiddenFilterTypes.includes(f.id),
|
||||||
|
)"
|
||||||
:key="`filter-${filter.id}`"
|
:key="`filter-${filter.id}`"
|
||||||
v-model:selected-filters="ctx.currentFilters.value"
|
v-model:selected-filters="ctx.currentFilters.value"
|
||||||
v-model:toggled-groups="ctx.toggledGroups.value"
|
v-model:toggled-groups="ctx.toggledGroups.value"
|
||||||
|
|||||||
@@ -2190,7 +2190,7 @@
|
|||||||
"defaultMessage": "Game version"
|
"defaultMessage": "Game version"
|
||||||
},
|
},
|
||||||
"label.hide-installed-content": {
|
"label.hide-installed-content": {
|
||||||
"defaultMessage": "Hide already installed content"
|
"defaultMessage": "Hide content already installed"
|
||||||
},
|
},
|
||||||
"label.hide-selected-content": {
|
"label.hide-selected-content": {
|
||||||
"defaultMessage": "Hide selected content"
|
"defaultMessage": "Hide selected content"
|
||||||
@@ -2294,6 +2294,9 @@
|
|||||||
"label.server": {
|
"label.server": {
|
||||||
"defaultMessage": "Server"
|
"defaultMessage": "Server"
|
||||||
},
|
},
|
||||||
|
"label.server-only": {
|
||||||
|
"defaultMessage": "Server only"
|
||||||
|
},
|
||||||
"label.servers": {
|
"label.servers": {
|
||||||
"defaultMessage": "Servers"
|
"defaultMessage": "Servers"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -429,12 +429,16 @@ export const commonMessages = defineMessages({
|
|||||||
},
|
},
|
||||||
hideInstalledContentLabel: {
|
hideInstalledContentLabel: {
|
||||||
id: 'label.hide-installed-content',
|
id: 'label.hide-installed-content',
|
||||||
defaultMessage: 'Hide already installed content',
|
defaultMessage: 'Hide content already installed',
|
||||||
},
|
},
|
||||||
hideSelectedContentLabel: {
|
hideSelectedContentLabel: {
|
||||||
id: 'label.hide-selected-content',
|
id: 'label.hide-selected-content',
|
||||||
defaultMessage: 'Hide selected content',
|
defaultMessage: 'Hide selected content',
|
||||||
},
|
},
|
||||||
|
serverOnlyLabel: {
|
||||||
|
id: 'label.server-only',
|
||||||
|
defaultMessage: 'Server only',
|
||||||
|
},
|
||||||
installedModpackTitle: {
|
installedModpackTitle: {
|
||||||
id: 'label.installed-modpack',
|
id: 'label.installed-modpack',
|
||||||
defaultMessage: 'Installed modpack',
|
defaultMessage: 'Installed modpack',
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ export type FilterValue = {
|
|||||||
negative?: boolean
|
negative?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type EnvironmentSearchOverride =
|
||||||
|
| { mode: 'include'; values: string[] }
|
||||||
|
| { mode: 'exclude'; values: string[] }
|
||||||
|
|
||||||
export const LOADER_FILTER_TYPES = [
|
export const LOADER_FILTER_TYPES = [
|
||||||
'mod_loader',
|
'mod_loader',
|
||||||
'plugin_loader',
|
'plugin_loader',
|
||||||
@@ -112,6 +116,7 @@ export function useSearch(
|
|||||||
projectTypes: Ref<ProjectType[]>,
|
projectTypes: Ref<ProjectType[]>,
|
||||||
tags: Ref<Tags>,
|
tags: Ref<Tags>,
|
||||||
providedFilters: Ref<FilterValue[]>,
|
providedFilters: Ref<FilterValue[]>,
|
||||||
|
environmentOverride: Ref<EnvironmentSearchOverride | undefined> = ref(undefined),
|
||||||
) {
|
) {
|
||||||
const query = ref('')
|
const query = ref('')
|
||||||
const maxResults = ref(20)
|
const maxResults = ref(20)
|
||||||
@@ -505,22 +510,34 @@ export function useSearch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Environment facets
|
// Environment facets
|
||||||
const client = filterValues.some(
|
const override = environmentOverride.value
|
||||||
(filter) => filter.type === 'environment' && filter.option === 'client',
|
if (override) {
|
||||||
)
|
if (override.values.length === 1) {
|
||||||
const server = filterValues.some(
|
const operator = override.mode === 'include' ? '=' : '!='
|
||||||
(filter) => filter.type === 'environment' && filter.option === 'server',
|
parts.push(`environment ${operator} ${formatSearchFilterValue(override.values[0])}`)
|
||||||
)
|
} else if (override.values.length > 1) {
|
||||||
for (const envGroup of getEnvironmentFilterGroups(client, server)) {
|
const operator = override.mode === 'include' ? 'IN' : 'NOT IN'
|
||||||
if (envGroup.length === 1) {
|
const quoted = override.values.map(formatSearchFilterValue).join(', ')
|
||||||
const [field, val] = envGroup[0].split(':')
|
parts.push(`environment ${operator} [${quoted}]`)
|
||||||
parts.push(`${field} = ${formatSearchFilterValue(val)}`)
|
}
|
||||||
} else if (envGroup.length > 1) {
|
} else {
|
||||||
const conditions = envGroup.map((f) => {
|
const client = filterValues.some(
|
||||||
const [field, val] = f.split(':')
|
(filter) => filter.type === 'environment' && filter.option === 'client',
|
||||||
return `${field} = ${formatSearchFilterValue(val)}`
|
)
|
||||||
})
|
const server = filterValues.some(
|
||||||
parts.push(`(${conditions.join(' OR ')})`)
|
(filter) => filter.type === 'environment' && filter.option === 'server',
|
||||||
|
)
|
||||||
|
for (const envGroup of getEnvironmentFilterGroups(client, server)) {
|
||||||
|
if (envGroup.length === 1) {
|
||||||
|
const [field, val] = envGroup[0].split(':')
|
||||||
|
parts.push(`${field} = ${formatSearchFilterValue(val)}`)
|
||||||
|
} else if (envGroup.length > 1) {
|
||||||
|
const conditions = envGroup.map((f) => {
|
||||||
|
const [field, val] = f.split(':')
|
||||||
|
return `${field} = ${formatSearchFilterValue(val)}`
|
||||||
|
})
|
||||||
|
parts.push(`(${conditions.join(' OR ')})`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,17 +794,28 @@ function mapProjectTypeToSearch(projectType: ProjectType): string {
|
|||||||
function getEnvironmentFilterGroups(client: boolean, server: boolean): string[][] {
|
function getEnvironmentFilterGroups(client: boolean, server: boolean): string[][] {
|
||||||
const groups: string[][] = []
|
const groups: string[][] = []
|
||||||
if (client && server) {
|
if (client && server) {
|
||||||
groups.push(
|
groups.push([
|
||||||
['client_side:required', 'client_side:optional', 'client_side:unsupported'],
|
'environment:client_only_server_optional',
|
||||||
['server_side:required', 'server_side:optional'],
|
'environment:server_only_client_optional',
|
||||||
)
|
'environment:client_and_server',
|
||||||
|
'environment:client_or_server',
|
||||||
|
'environment:client_or_server_prefers_both',
|
||||||
|
])
|
||||||
} else if (client) {
|
} else if (client) {
|
||||||
groups.push(
|
groups.push([
|
||||||
['client_side:optional', 'client_side:required'],
|
'environment:client_only',
|
||||||
['server_side:optional', 'server_side:unsupported'],
|
'environment:client_only_server_optional',
|
||||||
)
|
'environment:client_or_server_prefers_both',
|
||||||
|
'environment:client_or_server',
|
||||||
|
])
|
||||||
} else if (server) {
|
} else if (server) {
|
||||||
groups.push(['server_side:optional', 'server_side:required'])
|
groups.push([
|
||||||
|
'environment:server_only',
|
||||||
|
'environment:dedicated_server_only',
|
||||||
|
'environment:server_only_client_optional',
|
||||||
|
'environment:client_or_server_prefers_both',
|
||||||
|
'environment:client_or_server',
|
||||||
|
])
|
||||||
}
|
}
|
||||||
return groups
|
return groups
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user