diff --git a/apps/app-frontend/src/pages/Browse.vue b/apps/app-frontend/src/pages/Browse.vue index dcbe484463..4b721a424a 100644 --- a/apps/app-frontend/src/pages/Browse.vue +++ b/apps/app-frontend/src/pages/Browse.vue @@ -211,9 +211,8 @@ const instanceFilters = computed(() => { } const platform = instance.value.loader - const supportedModLoaders = ['fabric', 'forge', 'quilt', 'neoforge'] - if (platform && projectType.value === 'mod' && supportedModLoaders.includes(platform)) { + if (platform && projectType.value === 'mod' && MOD_LOADER_SET.has(platform)) { filters.push({ type: 'mod_loader', option: platform }) } @@ -250,7 +249,7 @@ const serverContextFilters = computed(() => { if (gameVersion) filters.push({ type: 'game_version', option: gameVersion }) const platform = serverContextServerData.value.loader?.toLowerCase() - if (platform && ['fabric', 'forge', 'quilt', 'neoforge'].includes(platform)) + if (platform && MOD_LOADER_SET.has(platform)) filters.push({ type: 'mod_loader', option: platform }) if (platform && ['paper', 'purpur'].includes(platform)) filters.push({ type: 'plugin_loader', option: platform }) diff --git a/apps/frontend/src/composables/generated.ts b/apps/frontend/src/composables/generated.ts index b47e9fec13..0fdba0582b 100644 --- a/apps/frontend/src/composables/generated.ts +++ b/apps/frontend/src/composables/generated.ts @@ -17,6 +17,7 @@ export interface LoaderData { dataPackLoaders: string[] modLoaders: string[] hiddenModLoaders: string[] + hiddenModLoaderSet: ReadonlySet } // Re-export types from api-client for convenience @@ -115,6 +116,7 @@ export const useGeneratedState = () => dataPackLoaders: ['datapack'], modLoaders: ['forge', 'fabric', 'quilt', 'liteloader', 'modloader', 'rift', 'neoforge'], hiddenModLoaders: ['liteloader', 'modloader', 'rift'], + hiddenModLoaderSet: new Set(['liteloader', 'modloader', 'rift']), }, projectViewModes: ['list', 'grid', 'gallery'], approvedStatuses: ['approved', 'archived', 'unlisted', 'private'], diff --git a/apps/frontend/src/pages/discover/[type]/index.vue b/apps/frontend/src/pages/discover/[type]/index.vue index 3fdd1a72a0..f513767264 100644 --- a/apps/frontend/src/pages/discover/[type]/index.vue +++ b/apps/frontend/src/pages/discover/[type]/index.vue @@ -228,8 +228,7 @@ const serverFilters = computed(() => { const platform = serverData.value.loader?.toLowerCase() - const modLoaders = ['fabric', 'forge', 'quilt', 'neoforge'] - if (platform && modLoaders.includes(platform)) { + if (platform && MOD_LOADER_SET.has(platform)) { filters.push({ type: 'mod_loader', option: platform }) } diff --git a/packages/assets/generated-icons.ts b/packages/assets/generated-icons.ts index 7e815d7931..03015155f8 100644 --- a/packages/assets/generated-icons.ts +++ b/packages/assets/generated-icons.ts @@ -3,6 +3,8 @@ import type { FunctionalComponent, SVGAttributes } from 'vue' +export type IconComponent = FunctionalComponent + import _AffiliateIcon from './icons/affiliate.svg?component' import _AlignLeftIcon from './icons/align-left.svg?component' import _ArchiveIcon from './icons/archive.svg?component' @@ -392,8 +394,6 @@ import _XCircleIcon from './icons/x-circle.svg?component' import _ZoomInIcon from './icons/zoom-in.svg?component' import _ZoomOutIcon from './icons/zoom-out.svg?component' -export type IconComponent = FunctionalComponent - export const AffiliateIcon = _AffiliateIcon export const AlignLeftIcon = _AlignLeftIcon export const ArchiveIcon = _ArchiveIcon diff --git a/packages/ui/src/layouts/shared/browse-tab/composables/use-browse-search.ts b/packages/ui/src/layouts/shared/browse-tab/composables/use-browse-search.ts index 0ccf0b9b42..38b0262d83 100644 --- a/packages/ui/src/layouts/shared/browse-tab/composables/use-browse-search.ts +++ b/packages/ui/src/layouts/shared/browse-tab/composables/use-browse-search.ts @@ -150,16 +150,24 @@ export function useBrowseSearch(options: UseBrowseSearchOptions): BrowseSearchSt LOADER_FILTER_TYPES.includes(f.type as (typeof LOADER_FILTER_TYPES)[number]), ) || ['resourcepack', 'datapack'].includes(options.projectType.value), ) - const loadersNotForThisType = computed( - () => - options.tags.value?.loaders - ?.filter((loader) => !loader.supported_project_types.includes(options.projectType.value)) - ?.map((loader) => loader.name) ?? [], - ) - const deprioritizedTags = computed(() => [ - ...selectedFilterTags.value, - ...loadersNotForThisType.value, - ]) + const loadersNotForThisType = computed(() => { + const loaders = options.tags.value?.loaders + if (!loaders) return [] + const result: string[] = [] + for (const loader of loaders) { + if (!loader.supported_project_types.includes(options.projectType.value)) { + result.push(loader.name) + } + } + return result + }) + const deprioritizedTags = computed(() => { + const selected = selectedFilterTags.value + const loaders = loadersNotForThisType.value + if (selected.length === 0) return loaders + if (loaders.length === 0) return selected + return [...selected, ...loaders] + }) const loading = ref(true) const projectHits = shallowRef([]) diff --git a/packages/ui/src/layouts/shared/server-settings/pages/installation.vue b/packages/ui/src/layouts/shared/server-settings/pages/installation.vue index 0ac0551aaf..83a05b5265 100644 --- a/packages/ui/src/layouts/shared/server-settings/pages/installation.vue +++ b/packages/ui/src/layouts/shared/server-settings/pages/installation.vue @@ -246,7 +246,7 @@ const editingGameVersion = ref(server.value?.mc_version ?? '') const resetToOnboardingModal = ref>() const isResettingToOnboarding = ref(false) -const modLoaders = ['fabric', 'forge', 'quilt', 'neoforge'] +const modLoaders = SUPPORTED_MOD_LOADERS function toApiLoaderName(loader: string): string { return loader === 'neoforge' ? 'neo' : loader diff --git a/packages/ui/src/utils/search.ts b/packages/ui/src/utils/search.ts index 2cdfa0dce3..fa992d4827 100644 --- a/packages/ui/src/utils/search.ts +++ b/packages/ui/src/utils/search.ts @@ -129,6 +129,75 @@ export function useSearch( return formatCategory(formatMessage, categoryName) } + const modLoaderOptions = computed(() => + tags.value.loaders + .filter( + (loader) => + loader.supported_project_types.includes('mod') && + !loader.supported_project_types.includes('plugin') && + !loader.supported_project_types.includes('datapack'), + ) + .map((loader) => ({ + id: loader.name, + formatted_name: formatLoader(formatMessage, loader.name), + icon: getLoaderIcon(loader.name), + method: 'or' as const, + value: `categories:${loader.name}`, + })), + ) + + const modpackLoaderOptions = computed(() => + tags.value.loaders + .filter((loader) => loader.supported_project_types.includes('modpack')) + .map((loader) => ({ + id: loader.name, + formatted_name: formatLoader(formatMessage, loader.name), + icon: getLoaderIcon(loader.name), + method: 'or' as const, + value: `categories:${loader.name}`, + })), + ) + + const pluginLoaderOptions = computed(() => + tags.value.loaders + .filter( + (loader) => + loader.supported_project_types.includes('plugin') && + !PLUGIN_PLATFORMS.includes(loader.name), + ) + .map((loader) => ({ + id: loader.name, + formatted_name: formatLoader(formatMessage, loader.name), + icon: getLoaderIcon(loader.name), + method: 'or' as const, + value: `categories:${loader.name}`, + })), + ) + + const pluginPlatformOptions = computed(() => + tags.value.loaders + .filter((loader) => PLUGIN_PLATFORMS.includes(loader.name)) + .map((loader) => ({ + id: loader.name, + formatted_name: formatLoader(formatMessage, loader.name), + icon: getLoaderIcon(loader.name), + method: 'or' as const, + value: `categories:${loader.name}`, + })), + ) + + const shaderLoaderOptions = computed(() => + tags.value.loaders + .filter((loader) => loader.supported_project_types.includes('shader')) + .map((loader) => ({ + id: loader.name, + formatted_name: formatLoader(formatMessage, loader.name), + icon: getLoaderIcon(loader.name), + method: 'or' as const, + value: `categories:${loader.name}`, + })), + ) + const filters = computed(() => { const categoryFilters: Record = {} for (const category of sortedCategories(tags.value, formatCategoryName, locale.value)) { @@ -251,22 +320,7 @@ export function useSearch( supports_negative_filter: true, default_values: DEFAULT_MOD_LOADERS, searchable: false, - options: tags.value.loaders - .filter( - (loader) => - loader.supported_project_types.includes('mod') && - !loader.supported_project_types.includes('plugin') && - !loader.supported_project_types.includes('datapack'), - ) - .map((loader) => { - return { - id: loader.name, - formatted_name: formatLoader(formatMessage, loader.name), - icon: getLoaderIcon(loader.name), - method: 'or', - value: `categories:${loader.name}`, - } - }), + options: modLoaderOptions.value, ordering: projectTypes.value.includes('mod') ? 1 : undefined, }, { @@ -282,17 +336,7 @@ export function useSearch( query_param: 'g', supports_negative_filter: true, searchable: false, - options: tags.value.loaders - .filter((loader) => loader.supported_project_types.includes('modpack')) - .map((loader) => { - return { - id: loader.name, - formatted_name: formatLoader(formatMessage, loader.name), - icon: getLoaderIcon(loader.name), - method: 'or', - value: `categories:${loader.name}`, - } - }), + options: modpackLoaderOptions.value, }, { id: 'plugin_loader', @@ -307,21 +351,7 @@ export function useSearch( query_param: 'g', supports_negative_filter: true, searchable: false, - options: tags.value.loaders - .filter( - (loader) => - loader.supported_project_types.includes('plugin') && - !PLUGIN_PLATFORMS.includes(loader.name), - ) - .map((loader) => { - return { - id: loader.name, - formatted_name: formatLoader(formatMessage, loader.name), - icon: getLoaderIcon(loader.name), - method: 'or', - value: `categories:${loader.name}`, - } - }), + options: pluginLoaderOptions.value, }, { id: 'plugin_platform', @@ -336,17 +366,7 @@ export function useSearch( query_param: 'g', supports_negative_filter: true, searchable: false, - options: tags.value.loaders - .filter((loader) => PLUGIN_PLATFORMS.includes(loader.name)) - .map((loader) => { - return { - id: loader.name, - formatted_name: formatLoader(formatMessage, loader.name), - icon: getLoaderIcon(loader.name), - method: 'or', - value: `categories:${loader.name}`, - } - }), + options: pluginPlatformOptions.value, }, { id: 'shader_loader', @@ -362,17 +382,7 @@ export function useSearch( searchable: false, display: 'expandable', default_values: DEFAULT_SHADER_LOADERS, - options: tags.value.loaders - .filter((loader) => loader.supported_project_types.includes('shader')) - .map((loader) => { - return { - id: loader.name, - formatted_name: formatLoader(formatMessage, loader.name), - icon: getLoaderIcon(loader.name), - method: 'or', - value: `categories:${loader.name}`, - } - }), + options: shaderLoaderOptions.value, }, { id: 'license', @@ -436,8 +446,8 @@ export function useSearch( const filterValues = [...filteredFilters, ...validProvidedFilters] const parts: string[] = [] - const orGroups: Record = {} - const negativeByType: Record = {} + const orGroups = new Map() + const negativeByType = new Map() for (const filterValue of filterValues) { const type = filters.value.find((type) => type.id === filterValue.type) @@ -464,22 +474,26 @@ export function useSearch( if (!field || !val) continue if (filterValue.negative) { - if (!negativeByType[field]) { - negativeByType[field] = [] + const existing = negativeByType.get(field) + if (existing) { + existing.push(val) + } else { + negativeByType.set(field, [val]) } - negativeByType[field].push(val) } else if (option.method === 'or') { - if (!orGroups[field]) { - orGroups[field] = [] + const existing = orGroups.get(field) + if (existing) { + existing.push(val) + } else { + orGroups.set(field, [val]) } - orGroups[field].push(val) } else { parts.push(`${field} = ${val === 'true' || val === 'false' ? val : `"${val}"`}`) } } } - for (const [field, values] of Object.entries(orGroups)) { + for (const [field, values] of orGroups) { if (values.length === 1) { parts.push(`${field} = "${values[0]}"`) } else { @@ -488,7 +502,7 @@ export function useSearch( } } - for (const [field, values] of Object.entries(negativeByType)) { + for (const [field, values] of negativeByType) { const quoted = values.map((v) => `"${v}"`).join(', ') parts.push(`${field} NOT IN [${quoted}]`) } diff --git a/packages/ui/src/utils/tag-messages.ts b/packages/ui/src/utils/tag-messages.ts index 13eff7b56a..d5e5e076c7 100644 --- a/packages/ui/src/utils/tag-messages.ts +++ b/packages/ui/src/utils/tag-messages.ts @@ -574,6 +574,10 @@ export const categoryMessages = defineMessages({ export const DEFAULT_MOD_LOADERS: string[] = ['fabric', 'forge', 'neoforge'] export const DEFAULT_SHADER_LOADERS: string[] = ['iris', 'optifine', 'vanilla'] +export const SUPPORTED_MOD_LOADERS: string[] = ['fabric', 'forge', 'quilt', 'neoforge'] +export const MOD_LOADER_SET: ReadonlySet = new Set(SUPPORTED_MOD_LOADERS) +export const HIDDEN_MOD_LOADERS: string[] = ['liteloader', 'modloader', 'rift'] +export const HIDDEN_MOD_LOADER_SET: ReadonlySet = new Set(HIDDEN_MOD_LOADERS) const DEFAULT_LOADER_NAMES = new Set([...DEFAULT_MOD_LOADERS, ...DEFAULT_SHADER_LOADERS])