mirror of
https://github.com/modrinth/code.git
synced 2026-09-04 22:10:15 +00:00
feat: polish depends on filter
This commit is contained in:
@@ -24,6 +24,7 @@
|
|||||||
:name="searchName"
|
:name="searchName"
|
||||||
:placeholder="searchPlaceholder || placeholder"
|
:placeholder="searchPlaceholder || placeholder"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
:clearable="clearable"
|
||||||
:autocomplete="searchAutocomplete"
|
:autocomplete="searchAutocomplete"
|
||||||
:autocorrect="searchAutocorrect"
|
:autocorrect="searchAutocorrect"
|
||||||
:autocapitalize="searchAutocapitalize"
|
:autocapitalize="searchAutocapitalize"
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
@focusin="handleSearchFocus"
|
@focusin="handleSearchFocus"
|
||||||
@focusout="handleSearchFocusout"
|
@focusout="handleSearchFocusout"
|
||||||
@click="handleSearchClick"
|
@click="handleSearchClick"
|
||||||
|
@clear="handleSearchInput"
|
||||||
>
|
>
|
||||||
<template v-if="showChevron" #right>
|
<template v-if="showChevron" #right>
|
||||||
<ChevronLeftIcon
|
<ChevronLeftIcon
|
||||||
@@ -274,6 +276,7 @@ const props = withDefaults(
|
|||||||
placeholder?: string
|
placeholder?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
searchable?: boolean
|
searchable?: boolean
|
||||||
|
clearable?: boolean
|
||||||
searchPlaceholder?: string
|
searchPlaceholder?: string
|
||||||
listbox?: boolean
|
listbox?: boolean
|
||||||
showChevron?: boolean
|
showChevron?: boolean
|
||||||
@@ -316,6 +319,7 @@ const props = withDefaults(
|
|||||||
placeholder: 'Select an option',
|
placeholder: 'Select an option',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
searchable: false,
|
searchable: false,
|
||||||
|
clearable: false,
|
||||||
searchPlaceholder: 'Search...',
|
searchPlaceholder: 'Search...',
|
||||||
listbox: true,
|
listbox: true,
|
||||||
showChevron: true,
|
showChevron: true,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
:no-options-message="searchLoading ? loadingMessage : noResultsMessage"
|
:no-options-message="searchLoading ? loadingMessage : noResultsMessage"
|
||||||
:disable-search-filter="true"
|
:disable-search-filter="true"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
:clearable="clearable"
|
||||||
show-icon-in-selected
|
show-icon-in-selected
|
||||||
@search-input="(query) => handleSearch(query)"
|
@search-input="(query) => handleSearch(query)"
|
||||||
/>
|
/>
|
||||||
@@ -55,6 +56,8 @@ const props = withDefaults(
|
|||||||
noResultsMessage?: string
|
noResultsMessage?: string
|
||||||
/** Whether the combobox is disabled */
|
/** Whether the combobox is disabled */
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
/** Whether to show a button for clearing the search input */
|
||||||
|
clearable?: boolean
|
||||||
/** Maximum number of results to show */
|
/** Maximum number of results to show */
|
||||||
limit?: number
|
limit?: number
|
||||||
/** Project IDs to exclude from results */
|
/** Project IDs to exclude from results */
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
:project-types="selectableProjectTypes"
|
:project-types="selectableProjectTypes"
|
||||||
:exclude-project-ids="dependencyProjectIds"
|
:exclude-project-ids="dependencyProjectIds"
|
||||||
:search-placeholder="formatMessage(messages.searchContentPlaceholder)"
|
:search-placeholder="formatMessage(messages.searchContentPlaceholder)"
|
||||||
|
:show-chevron="false"
|
||||||
|
clearable
|
||||||
|
show-search-icon
|
||||||
@update:model-value="addIncludedProject"
|
@update:model-value="addIncludedProject"
|
||||||
/>
|
/>
|
||||||
<div v-if="dependencyProjectIds.length > 0" class="flex flex-col gap-1">
|
<div v-if="dependencyProjectIds.length > 0" class="flex flex-col gap-1">
|
||||||
@@ -23,50 +26,67 @@
|
|||||||
<span class="min-w-0 flex-1 truncate font-medium text-contrast">
|
<span class="min-w-0 flex-1 truncate font-medium text-contrast">
|
||||||
{{ dependentProjectMap.get(projectId)?.title ?? projectId }}
|
{{ dependentProjectMap.get(projectId)?.title ?? projectId }}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<div class="flex items-center gap-1">
|
||||||
type="button"
|
<button
|
||||||
class="shrink-0 cursor-pointer border-0 bg-transparent p-0 text-sm transition-all"
|
v-tooltip="formatMessage(messages.removeIncludedProjectTooltip)"
|
||||||
:class="
|
type="button"
|
||||||
excludedProjectIds.has(projectId)
|
class="flex shrink-0 cursor-pointer items-center justify-center rounded-xl border-0 bg-transparent px-2 py-1 text-secondary transition-all [@media(hover:hover)]:opacity-0 group-hover:opacity-100 hover:bg-button-bg hover:text-contrast active:scale-[0.96]"
|
||||||
? 'text-secondary opacity-100'
|
:aria-label="
|
||||||
: 'text-secondary opacity-0 decoration-1 underline-offset-2 group-hover:opacity-100 group-focus-within:opacity-100 hover:text-contrast'
|
formatMessage(messages.removeIncludedProject, {
|
||||||
"
|
project: dependentProjectMap.get(projectId)?.title ?? projectId,
|
||||||
@click="toggleProjectExcluded(projectId)"
|
})
|
||||||
>
|
"
|
||||||
{{
|
@click="removeIncludedProject(projectId)"
|
||||||
excludedProjectIds.has(projectId)
|
>
|
||||||
? formatMessage(messages.excluded)
|
<XIcon class="size-4" aria-hidden="true" />
|
||||||
: formatMessage(messages.exclude)
|
</button>
|
||||||
}}
|
<button
|
||||||
</button>
|
v-tooltip="
|
||||||
<button
|
excludedProjectIds.has(projectId)
|
||||||
v-tooltip="formatMessage(messages.removeIncludedProjectTooltip)"
|
? formatMessage(messages.excluded)
|
||||||
type="button"
|
: formatMessage(messages.exclude)
|
||||||
class="flex shrink-0 cursor-pointer items-center border-0 bg-transparent p-0 text-secondary hover:text-contrast"
|
"
|
||||||
:aria-label="
|
type="button"
|
||||||
formatMessage(messages.removeIncludedProject, {
|
class="flex shrink-0 cursor-pointer items-center justify-center rounded-xl border-0 px-2 py-1 transition-all"
|
||||||
project: dependentProjectMap.get(projectId)?.title ?? projectId,
|
:class="
|
||||||
})
|
excludedProjectIds.has(projectId)
|
||||||
"
|
? 'text-red opacity-100 bg-highlight-red'
|
||||||
@click="removeIncludedProject(projectId)"
|
: 'text-secondary [@media(hover:hover)]:opacity-0 group-hover:opacity-100 bg-transparent hover:bg-button-bg hover:text-red active:scale-[0.96]'
|
||||||
>
|
"
|
||||||
<XIcon class="size-5" />
|
:aria-label="
|
||||||
</button>
|
excludedProjectIds.has(projectId)
|
||||||
|
? formatMessage(messages.excluded)
|
||||||
|
: formatMessage(messages.exclude)
|
||||||
|
"
|
||||||
|
@click="toggleProjectExcluded(projectId)"
|
||||||
|
>
|
||||||
|
<BanIcon class="size-4" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="innerPanelClass" class="flex flex-col gap-3">
|
<div v-else :class="innerPanelClass" class="flex flex-col gap-3">
|
||||||
<ProjectCombobox
|
<ProjectCombobox
|
||||||
v-show="!selectedProjectId || refreshing"
|
v-show="!selectedProjectId"
|
||||||
ref="projectCombobox"
|
ref="projectCombobox"
|
||||||
:model-value="selectedProjectId"
|
:model-value="selectedProjectId"
|
||||||
:project-types="selectableProjectTypes"
|
:project-types="selectableProjectTypes"
|
||||||
:search-placeholder="formatMessage(messages.searchProjectPlaceholder)"
|
:search-placeholder="formatMessage(messages.searchProjectPlaceholder)"
|
||||||
|
:show-chevron="false"
|
||||||
|
clearable
|
||||||
|
show-search-icon
|
||||||
@update:model-value="setSelectedProjectId"
|
@update:model-value="setSelectedProjectId"
|
||||||
/>
|
/>
|
||||||
<template v-if="selectedProjectId && !refreshing">
|
<template v-if="selectedProjectId && selectedProject">
|
||||||
<div class="flex items-center justify-between gap-3 px-2 text-secondary">
|
<div class="flex items-center justify-between gap-3 px-2 text-secondary">
|
||||||
<span>{{ formatMessage(messages.dependentCount, { count: resultCount ?? 0 }) }}</span>
|
<span>
|
||||||
|
{{
|
||||||
|
refreshing
|
||||||
|
? 'Loading...'
|
||||||
|
: formatMessage(messages.dependentCount, { count: resultCount ?? 0 })
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
<button
|
<button
|
||||||
class="border-none bg-transparent p-0 text-secondary cursor-pointer hover:text-contrast"
|
class="border-none bg-transparent p-0 text-secondary cursor-pointer hover:text-contrast"
|
||||||
@click="setSelectedProjectId(undefined)"
|
@click="setSelectedProjectId(undefined)"
|
||||||
@@ -86,27 +106,31 @@
|
|||||||
<div class="truncate text-base font-bold text-contrast">
|
<div class="truncate text-base font-bold text-contrast">
|
||||||
{{ selectedProject?.title ?? selectedProjectId }}
|
{{ selectedProject?.title ?? selectedProjectId }}
|
||||||
</div>
|
</div>
|
||||||
<MultiSelect
|
<div class="relative right-2.5">
|
||||||
:model-value="dependencyTypes"
|
<MultiSelect
|
||||||
:options="dependencyTypeOptions"
|
:model-value="draftDependencyTypes"
|
||||||
:clearable="false"
|
:options="dependencyTypeOptions"
|
||||||
fit-content
|
:clearable="false"
|
||||||
show-chevron
|
fit-content
|
||||||
trigger-class="!rounded-none !bg-transparent !p-0"
|
show-chevron
|
||||||
checkbox-position="right"
|
trigger-class="!rounded-none !bg-transparent !p-0"
|
||||||
:dropdown-min-width="220"
|
checkbox-position="right"
|
||||||
@update:model-value="setDependencyTypes"
|
:dropdown-min-width="220"
|
||||||
>
|
@open="resetDraftDependencyTypes"
|
||||||
<template #input-content="{ isOpen }">
|
@close="commitDependencyTypes"
|
||||||
<span class="flex items-center gap-0.5 text-sm text-secondary">
|
@update:model-value="setDraftDependencyTypes"
|
||||||
{{ dependencyTypeLabel }}
|
>
|
||||||
<DropdownIcon
|
<template #input-content="{ isOpen }">
|
||||||
class="size-4 transition-transform"
|
<span class="flex items-center gap-0.5 text-sm text-secondary ml-2.5">
|
||||||
:class="{ 'rotate-180': isOpen }"
|
{{ dependencyTypeLabel }}
|
||||||
/>
|
<DropdownIcon
|
||||||
</span>
|
class="size-4 transition-transform"
|
||||||
</template>
|
:class="{ 'rotate-180': isOpen }"
|
||||||
</MultiSelect>
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</MultiSelect>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -114,13 +138,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DropdownIcon, PackageIcon, XIcon } from '@modrinth/assets'
|
import { BanIcon, DropdownIcon, PackageIcon, XIcon } from '@modrinth/assets'
|
||||||
import { useQuery } from '@tanstack/vue-query'
|
import { useQuery } from '@tanstack/vue-query'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||||
import { injectModrinthClient } from '../../providers'
|
import { injectModrinthClient } from '../../providers'
|
||||||
import type { FilterValue } from '../../utils/search'
|
import {
|
||||||
|
type DependencyType,
|
||||||
|
type FilterValue,
|
||||||
|
formatDependencyProjectFilterOption,
|
||||||
|
parseDependencyProjectFilterOption,
|
||||||
|
} from '../../utils/search'
|
||||||
import MultiSelect, { type MultiSelectOption } from '../base/MultiSelect.vue'
|
import MultiSelect, { type MultiSelectOption } from '../base/MultiSelect.vue'
|
||||||
import ProjectCombobox, {
|
import ProjectCombobox, {
|
||||||
type ProjectType as ProjectComboboxProjectType,
|
type ProjectType as ProjectComboboxProjectType,
|
||||||
@@ -149,11 +178,13 @@ const selectableProjectTypes: ProjectComboboxProjectType[] = [
|
|||||||
'plugin',
|
'plugin',
|
||||||
]
|
]
|
||||||
const isModpack = computed(() => props.projectType === 'modpack')
|
const isModpack = computed(() => props.projectType === 'modpack')
|
||||||
const dependencyProjectIds = computed(() =>
|
const dependencyProjectIds = computed(() => [
|
||||||
selectedFilters.value
|
...new Set(
|
||||||
.filter((filter) => filter.type === FILTER_TYPE_ID)
|
selectedFilters.value
|
||||||
.map((filter) => filter.option),
|
.filter((filter) => filter.type === FILTER_TYPE_ID)
|
||||||
)
|
.map((filter) => parseDependencyProjectFilterOption(filter.option).projectId),
|
||||||
|
),
|
||||||
|
])
|
||||||
const pendingProjectId = ref<string>()
|
const pendingProjectId = ref<string>()
|
||||||
const excludedProjectIds = ref(new Set<string>())
|
const excludedProjectIds = ref(new Set<string>())
|
||||||
|
|
||||||
@@ -181,20 +212,37 @@ const dependentProjectMap = computed(
|
|||||||
)
|
)
|
||||||
const projectCombobox = ref<{ selectedProject: SearchHit | null } | null>(null)
|
const projectCombobox = ref<{ selectedProject: SearchHit | null } | null>(null)
|
||||||
const selectedProject = computed(() => projectCombobox.value?.selectedProject ?? null)
|
const selectedProject = computed(() => projectCombobox.value?.selectedProject ?? null)
|
||||||
const selectedProjectId = computed(
|
const selectedDependencyFilter = computed(() =>
|
||||||
() => selectedFilters.value.find((filter) => filter.type === FILTER_TYPE_ID)?.option,
|
selectedFilters.value.find((filter) => filter.type === FILTER_TYPE_ID),
|
||||||
|
)
|
||||||
|
const selectedProjectId = computed(() =>
|
||||||
|
selectedDependencyFilter.value
|
||||||
|
? parseDependencyProjectFilterOption(selectedDependencyFilter.value.option).projectId
|
||||||
|
: undefined,
|
||||||
)
|
)
|
||||||
|
|
||||||
type DependencyType = 'required' | 'optional' | 'embedded'
|
const dependencyTypes = computed<DependencyType[]>(() =>
|
||||||
|
selectedDependencyFilter.value
|
||||||
|
? parseDependencyProjectFilterOption(selectedDependencyFilter.value.option).dependencyTypes
|
||||||
|
: ['required'],
|
||||||
|
)
|
||||||
|
const draftDependencyTypes = ref<DependencyType[]>([...dependencyTypes.value])
|
||||||
|
|
||||||
|
watch(
|
||||||
|
dependencyTypes,
|
||||||
|
(types) => {
|
||||||
|
draftDependencyTypes.value = [...types]
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
const dependencyTypes = ref<DependencyType[]>(['required'])
|
|
||||||
const dependencyTypeOptions = computed<MultiSelectOption<DependencyType>[]>(() => [
|
const dependencyTypeOptions = computed<MultiSelectOption<DependencyType>[]>(() => [
|
||||||
{ value: 'required', label: formatMessage(messages.required) },
|
{ value: 'required', label: formatMessage(messages.required) },
|
||||||
{ value: 'optional', label: formatMessage(messages.optional) },
|
{ value: 'optional', label: formatMessage(messages.optional) },
|
||||||
{ value: 'embedded', label: formatMessage(messages.embedded) },
|
{ value: 'embedded', label: formatMessage(messages.embedded) },
|
||||||
])
|
])
|
||||||
const dependencyTypeLabel = computed(() => {
|
const dependencyTypeLabel = computed(() => {
|
||||||
const selectedTypes = new Set(dependencyTypes.value)
|
const selectedTypes = new Set(draftDependencyTypes.value)
|
||||||
if (selectedTypes.size === dependencyTypeOptions.value.length) {
|
if (selectedTypes.size === dependencyTypeOptions.value.length) {
|
||||||
return formatMessage(messages.anyDependencyType)
|
return formatMessage(messages.anyDependencyType)
|
||||||
}
|
}
|
||||||
@@ -207,15 +255,23 @@ const dependencyTypeLabel = computed(() => {
|
|||||||
if (selectedTypes.has('optional') && selectedTypes.has('embedded')) {
|
if (selectedTypes.has('optional') && selectedTypes.has('embedded')) {
|
||||||
return formatMessage(messages.optionalOrEmbedded)
|
return formatMessage(messages.optionalOrEmbedded)
|
||||||
}
|
}
|
||||||
return dependencyTypeOptions.value.find((option) => selectedTypes.has(option.value))?.label ?? ''
|
return (
|
||||||
|
dependencyTypeOptions.value.find((option) => selectedTypes.has(option.value))?.label ??
|
||||||
|
'Select type'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
function setSelectedProjectId(projectId: string | undefined) {
|
function setSelectedProjectId(projectId: string | undefined) {
|
||||||
const otherFilters = selectedFilters.value.filter((filter) => filter.type !== FILTER_TYPE_ID)
|
const otherFilters = selectedFilters.value.filter((filter) => filter.type !== FILTER_TYPE_ID)
|
||||||
selectedFilters.value = projectId
|
selectedFilters.value = projectId
|
||||||
? [...otherFilters, { type: FILTER_TYPE_ID, option: projectId }]
|
? [
|
||||||
|
...otherFilters,
|
||||||
|
{
|
||||||
|
type: FILTER_TYPE_ID,
|
||||||
|
option: formatDependencyProjectFilterOption(projectId, ['required']),
|
||||||
|
},
|
||||||
|
]
|
||||||
: otherFilters
|
: otherFilters
|
||||||
dependencyTypes.value = ['required']
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addIncludedProject(projectId: string | undefined) {
|
function addIncludedProject(projectId: string | undefined) {
|
||||||
@@ -227,7 +283,9 @@ function addIncludedProject(projectId: string | undefined) {
|
|||||||
|
|
||||||
function removeIncludedProject(projectId: string) {
|
function removeIncludedProject(projectId: string) {
|
||||||
selectedFilters.value = selectedFilters.value.filter(
|
selectedFilters.value = selectedFilters.value.filter(
|
||||||
(filter) => filter.type !== FILTER_TYPE_ID || filter.option !== projectId,
|
(filter) =>
|
||||||
|
filter.type !== FILTER_TYPE_ID ||
|
||||||
|
parseDependencyProjectFilterOption(filter.option).projectId !== projectId,
|
||||||
)
|
)
|
||||||
const nextExcludedProjectIds = new Set(excludedProjectIds.value)
|
const nextExcludedProjectIds = new Set(excludedProjectIds.value)
|
||||||
nextExcludedProjectIds.delete(projectId)
|
nextExcludedProjectIds.delete(projectId)
|
||||||
@@ -244,9 +302,32 @@ function toggleProjectExcluded(projectId: string) {
|
|||||||
excludedProjectIds.value = nextExcludedProjectIds
|
excludedProjectIds.value = nextExcludedProjectIds
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDependencyTypes(types: DependencyType[]) {
|
function resetDraftDependencyTypes() {
|
||||||
if (types.length > 0) {
|
draftDependencyTypes.value = [...dependencyTypes.value]
|
||||||
dependencyTypes.value = types
|
}
|
||||||
|
|
||||||
|
function setDraftDependencyTypes(types: DependencyType[]) {
|
||||||
|
draftDependencyTypes.value = types
|
||||||
|
}
|
||||||
|
|
||||||
|
function commitDependencyTypes() {
|
||||||
|
const projectId = selectedProjectId.value
|
||||||
|
const types: DependencyType[] =
|
||||||
|
draftDependencyTypes.value.length > 0 ? draftDependencyTypes.value : ['required']
|
||||||
|
draftDependencyTypes.value = types
|
||||||
|
const selectedTypes = new Set(dependencyTypes.value)
|
||||||
|
const changed =
|
||||||
|
types.length !== selectedTypes.size || types.some((type) => !selectedTypes.has(type))
|
||||||
|
|
||||||
|
if (changed && projectId) {
|
||||||
|
selectedFilters.value = selectedFilters.value.map((filter) =>
|
||||||
|
filter.type === FILTER_TYPE_ID
|
||||||
|
? {
|
||||||
|
...filter,
|
||||||
|
option: formatDependencyProjectFilterOption(projectId, types),
|
||||||
|
}
|
||||||
|
: filter,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import {
|
|||||||
type FilterType,
|
type FilterType,
|
||||||
type FilterValue,
|
type FilterValue,
|
||||||
flattenFilterOptions,
|
flattenFilterOptions,
|
||||||
|
parseDependencyProjectFilterOption,
|
||||||
} from '../../utils/search'
|
} from '../../utils/search'
|
||||||
import TagItem from '../base/TagItem.vue'
|
import TagItem from '../base/TagItem.vue'
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ const dependentProjectIds = computed(() =>
|
|||||||
...new Set(
|
...new Set(
|
||||||
[...selectedFilters.value, ...props.providedFilters]
|
[...selectedFilters.value, ...props.providedFilters]
|
||||||
.filter((filter) => filter.type === 'compatible_dependency_project_ids')
|
.filter((filter) => filter.type === 'compatible_dependency_project_ids')
|
||||||
.map((filter) => filter.option),
|
.map((filter) => parseDependencyProjectFilterOption(filter.option).projectId),
|
||||||
),
|
),
|
||||||
].sort(),
|
].sort(),
|
||||||
)
|
)
|
||||||
@@ -150,17 +151,20 @@ const items: ComputedRef<Item[]> = computed(() => {
|
|||||||
)
|
)
|
||||||
return [
|
return [
|
||||||
...optionItems,
|
...optionItems,
|
||||||
...customValues.map((filter) => ({
|
...customValues.map((filter) => {
|
||||||
type: type.id,
|
const projectId = parseDependencyProjectFilterOption(filter.option).projectId
|
||||||
option: filter.option,
|
return {
|
||||||
negative: filter.negative,
|
type: type.id,
|
||||||
provided: props.providedFilters.some(
|
option: filter.option,
|
||||||
(provided) => provided.type === type.id && provided.option === filter.option,
|
negative: filter.negative,
|
||||||
),
|
provided: props.providedFilters.some(
|
||||||
formatted_name: formatMessage(dependentProjectMessage, {
|
(provided) => provided.type === type.id && provided.option === filter.option,
|
||||||
project: dependentProjectNames.value.get(filter.option) ?? filter.option,
|
),
|
||||||
}),
|
formatted_name: formatMessage(dependentProjectMessage, {
|
||||||
})),
|
project: dependentProjectNames.value.get(projectId) ?? projectId,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
:project-type="projectType"
|
:project-type="projectType"
|
||||||
:inner-panel-class="innerPanelClass"
|
:inner-panel-class="innerPanelClass"
|
||||||
:result-count="resultCount"
|
:result-count="resultCount"
|
||||||
|
:loading="loading"
|
||||||
:refreshing="refreshing"
|
:refreshing="refreshing"
|
||||||
/>
|
/>
|
||||||
<template v-if="filterType.display !== 'depends-on-project'">
|
<template v-if="filterType.display !== 'depends-on-project'">
|
||||||
@@ -253,6 +254,7 @@ const props = defineProps<{
|
|||||||
openByDefault?: boolean
|
openByDefault?: boolean
|
||||||
providedFilters: FilterValue[]
|
providedFilters: FilterValue[]
|
||||||
resultCount?: number
|
resultCount?: number
|
||||||
|
loading?: boolean
|
||||||
refreshing?: boolean
|
refreshing?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ function getFilterOpenByDefault(filterId: string): boolean {
|
|||||||
:filter-type="filter"
|
:filter-type="filter"
|
||||||
:project-type="ctx.projectType.value"
|
:project-type="ctx.projectType.value"
|
||||||
:result-count="ctx.totalHits.value"
|
:result-count="ctx.totalHits.value"
|
||||||
|
:loading="ctx.loading.value"
|
||||||
:refreshing="ctx.refreshing.value"
|
:refreshing="ctx.refreshing.value"
|
||||||
:class="filterClass"
|
:class="filterClass"
|
||||||
:button-class="buttonClass"
|
:button-class="buttonClass"
|
||||||
|
|||||||
@@ -123,6 +123,45 @@ export type FilterValue = {
|
|||||||
negative?: boolean
|
negative?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DependencyType = 'required' | 'optional' | 'embedded'
|
||||||
|
|
||||||
|
const DEPENDENCY_TYPE_FIELDS: Record<DependencyType, string> = {
|
||||||
|
required: 'required_dependency_project_ids',
|
||||||
|
optional: 'optional_dependency_project_ids',
|
||||||
|
embedded: 'embedded_dependency_project_ids',
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEPENDENCY_TYPES = Object.keys(DEPENDENCY_TYPE_FIELDS) as DependencyType[]
|
||||||
|
|
||||||
|
export function formatDependencyProjectFilterOption(
|
||||||
|
projectId: string,
|
||||||
|
dependencyTypes: readonly DependencyType[],
|
||||||
|
): string {
|
||||||
|
return `${dependencyTypes.join(',')}:${projectId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseDependencyProjectFilterOption(option: string): {
|
||||||
|
projectId: string
|
||||||
|
dependencyTypes: DependencyType[]
|
||||||
|
} {
|
||||||
|
const separatorIndex = option.indexOf(':')
|
||||||
|
if (separatorIndex === -1) {
|
||||||
|
return { projectId: option, dependencyTypes: ['required'] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencyTypes = option
|
||||||
|
.slice(0, separatorIndex)
|
||||||
|
.split(',')
|
||||||
|
.filter((type): type is DependencyType => DEPENDENCY_TYPES.includes(type as DependencyType))
|
||||||
|
const projectId = option.slice(separatorIndex + 1)
|
||||||
|
|
||||||
|
if (dependencyTypes.length === 0 || !projectId) {
|
||||||
|
return { projectId: option, dependencyTypes: ['required'] }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { projectId, dependencyTypes }
|
||||||
|
}
|
||||||
|
|
||||||
export type EnvironmentSearchOverride =
|
export type EnvironmentSearchOverride =
|
||||||
| { mode: 'include'; values: string[] }
|
| { mode: 'include'; values: string[] }
|
||||||
| { mode: 'exclude'; values: string[] }
|
| { mode: 'exclude'; values: string[] }
|
||||||
@@ -728,6 +767,26 @@ export function useSearch(
|
|||||||
const negativeByType: Record<string, string[]> = {}
|
const negativeByType: Record<string, string[]> = {}
|
||||||
|
|
||||||
for (const filterValue of filterValues) {
|
for (const filterValue of filterValues) {
|
||||||
|
if (filterValue.type === 'compatible_dependency_project_ids') {
|
||||||
|
const { projectId, dependencyTypes } = parseDependencyProjectFilterOption(
|
||||||
|
filterValue.option,
|
||||||
|
)
|
||||||
|
const fields = projectTypes.value.includes('modpack')
|
||||||
|
? ['compatible_dependency_project_ids']
|
||||||
|
: dependencyTypes.map((type) => DEPENDENCY_TYPE_FIELDS[type])
|
||||||
|
const operator = filterValue.negative ? '!=' : '='
|
||||||
|
const conditions = fields.map(
|
||||||
|
(field) => `${field} ${operator} ${formatSearchFilterValue(projectId)}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (conditions.length === 1) {
|
||||||
|
parts.push(conditions[0])
|
||||||
|
} else {
|
||||||
|
parts.push(`(${conditions.join(filterValue.negative ? ' AND ' : ' OR ')})`)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
const type = filters.value.find((type) => type.id === filterValue.type)
|
const type = filters.value.find((type) => type.id === filterValue.type)
|
||||||
if (!type) {
|
if (!type) {
|
||||||
console.error(`Filter type ${filterValue.type} not found`)
|
console.error(`Filter type ${filterValue.type} not found`)
|
||||||
|
|||||||
Reference in New Issue
Block a user