From 1613531b67179102a6812966d4589d68ba62904f Mon Sep 17 00:00:00 2001 From: Prospector <6166773+Prospector@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:13:16 -0800 Subject: [PATCH] Refactor search page, migrate to /discover/ --- apps/frontend/nuxt.config.ts | 19 +- apps/frontend/src/composables/featureFlags.ts | 1 + apps/frontend/src/composables/generated.ts | 6 +- apps/frontend/src/layouts/default.vue | 93 +- apps/frontend/src/locales/en-US/index.json | 3 + .../src/middleware/search-redirect.global.ts | 13 + apps/frontend/src/pages/collection/[id].vue | 2 +- apps/frontend/src/pages/discover.vue | 65 ++ apps/frontend/src/pages/discover/[type].vue | 35 + .../src/pages/discover/[type]/index.vue | 896 ++++++++++++++++++ apps/frontend/src/pages/discover/index.vue | 6 + apps/frontend/src/pages/index.vue | 2 +- .../src/pages/search/[searchProjectType].vue | 891 ----------------- apps/frontend/src/public/opensearch.xml | 8 +- apps/frontend/src/utils/router.ts | 13 + .../api-client/src/modules/labrinth/types.ts | 116 +++ packages/ui/src/components/base/Accordion.vue | 10 + .../src/components/base/ScrollablePanel.vue | 1 - packages/ui/src/locales/en-US/index.json | 9 + packages/ui/src/utils/common-messages.ts | 12 + packages/ui/src/utils/search.ts | 22 +- packages/utils/utils.ts | 2 +- 22 files changed, 1252 insertions(+), 973 deletions(-) create mode 100644 apps/frontend/src/middleware/search-redirect.global.ts create mode 100644 apps/frontend/src/pages/discover.vue create mode 100644 apps/frontend/src/pages/discover/[type].vue create mode 100644 apps/frontend/src/pages/discover/[type]/index.vue create mode 100644 apps/frontend/src/pages/discover/index.vue delete mode 100644 apps/frontend/src/pages/search/[searchProjectType].vue create mode 100644 apps/frontend/src/utils/router.ts diff --git a/apps/frontend/nuxt.config.ts b/apps/frontend/nuxt.config.ts index beaf5a9982..06b9ff34c2 100644 --- a/apps/frontend/nuxt.config.ts +++ b/apps/frontend/nuxt.config.ts @@ -7,7 +7,7 @@ import { consola } from 'consola' import { promises as fs } from 'fs' import { globIterate } from 'glob' import { defineNuxtConfig } from 'nuxt/config' -import { basename, relative, resolve } from 'pathe' +import { basename, relative } from 'pathe' import svgLoader from 'vite-svg-loader' const STAGING_API_URL = 'https://staging-api.modrinth.com/v2/' @@ -176,23 +176,6 @@ export default defineNuxtConfig({ console.log('Tags generated!') }, - 'pages:extend'(routes) { - routes.splice( - routes.findIndex((x) => x.name === 'search-searchProjectType'), - 1, - ) - - const types = ['mods', 'modpacks', 'plugins', 'resourcepacks', 'shaders', 'datapacks'] - - types.forEach((type) => - routes.push({ - name: `search-${type}`, - path: `/${type}`, - file: resolve(__dirname, 'src/pages/search/[searchProjectType].vue'), - children: [], - }), - ) - }, async 'vintl:extendOptions'(opts) { opts.locales ??= [] diff --git a/apps/frontend/src/composables/featureFlags.ts b/apps/frontend/src/composables/featureFlags.ts index 6951fd4221..d681176aed 100644 --- a/apps/frontend/src/composables/featureFlags.ts +++ b/apps/frontend/src/composables/featureFlags.ts @@ -40,6 +40,7 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({ newProjectGeneralSettings: false, newProjectEnvironmentSettings: true, hideRussiaCensorshipBanner: false, + serverDiscovery: false, // advancedRendering: true, // externalLinksNewTab: true, // notUsingBlockers: false, diff --git a/apps/frontend/src/composables/generated.ts b/apps/frontend/src/composables/generated.ts index 12629657bd..931c8aeef7 100644 --- a/apps/frontend/src/composables/generated.ts +++ b/apps/frontend/src/composables/generated.ts @@ -1,10 +1,12 @@ import type { ISO3166, Labrinth } from '@modrinth/api-client' +import type { DisplayProjectType } from '@modrinth/utils' import generatedState from '~/generated/state.json' +import type { DisplayMode } from '~/plugins/cosmetics' export interface ProjectType { actual: string - id: string + id: DisplayProjectType display: string } @@ -25,7 +27,7 @@ export interface GeneratedState extends Labrinth.State.GeneratedState { // Additional runtime-defined fields not from the API projectTypes: ProjectType[] loaderData: LoaderData - projectViewModes: string[] + projectViewModes: DisplayMode[] approvedStatuses: string[] rejectedStatuses: string[] staffRoles: string[] diff --git a/apps/frontend/src/layouts/default.vue b/apps/frontend/src/layouts/default.vue index a3012eb6ad..30a2203484 100644 --- a/apps/frontend/src/layouts/default.vue +++ b/apps/frontend/src/layouts/default.vue @@ -237,12 +237,12 @@ + { + if ( + to.path.startsWith('/mods') || + to.path.startsWith('/modpacks') || + to.path.startsWith('/plugins') || + to.path.startsWith('/datapacks') || + to.path.startsWith('/resourcepacks') || + to.path.startsWith('/shaders') + ) { + const target = '/discover' + to.fullPath + return navigateTo(target, { redirectCode: 301 }) + } +}) diff --git a/apps/frontend/src/pages/collection/[id].vue b/apps/frontend/src/pages/collection/[id].vue index 1d01bb5fa7..6fbcdcefd3 100644 --- a/apps/frontend/src/pages/collection/[id].vue +++ b/apps/frontend/src/pages/collection/[id].vue @@ -364,7 +364,7 @@ diff --git a/apps/frontend/src/pages/discover.vue b/apps/frontend/src/pages/discover.vue new file mode 100644 index 0000000000..3df3ffe9e1 --- /dev/null +++ b/apps/frontend/src/pages/discover.vue @@ -0,0 +1,65 @@ + + diff --git a/apps/frontend/src/pages/discover/[type].vue b/apps/frontend/src/pages/discover/[type].vue new file mode 100644 index 0000000000..a14e552dc8 --- /dev/null +++ b/apps/frontend/src/pages/discover/[type].vue @@ -0,0 +1,35 @@ + + diff --git a/apps/frontend/src/pages/discover/[type]/index.vue b/apps/frontend/src/pages/discover/[type]/index.vue new file mode 100644 index 0000000000..c3c4a9d123 --- /dev/null +++ b/apps/frontend/src/pages/discover/[type]/index.vue @@ -0,0 +1,896 @@ + + + diff --git a/apps/frontend/src/pages/discover/index.vue b/apps/frontend/src/pages/discover/index.vue new file mode 100644 index 0000000000..6b66f9f79f --- /dev/null +++ b/apps/frontend/src/pages/discover/index.vue @@ -0,0 +1,6 @@ + diff --git a/apps/frontend/src/pages/index.vue b/apps/frontend/src/pages/index.vue index 7e42891c53..2a2078c04d 100644 --- a/apps/frontend/src/pages/index.vue +++ b/apps/frontend/src/pages/index.vue @@ -27,7 +27,7 @@
- + diff --git a/apps/frontend/src/pages/search/[searchProjectType].vue b/apps/frontend/src/pages/search/[searchProjectType].vue deleted file mode 100644 index c2ab6ed1ff..0000000000 --- a/apps/frontend/src/pages/search/[searchProjectType].vue +++ /dev/null @@ -1,891 +0,0 @@ - - - - diff --git a/apps/frontend/src/public/opensearch.xml b/apps/frontend/src/public/opensearch.xml index ff7cd73b8f..8079451dbe 100644 --- a/apps/frontend/src/public/opensearch.xml +++ b/apps/frontend/src/public/opensearch.xml @@ -7,8 +7,12 @@ Search for mods on Modrinth, the open source modding platform. UTF-8 https://modrinth.com/favicon.ico - + Rinth, Inc. Rinth, Inc. - https://modrinth.com/mods + https://modrinth.com/discover/mods diff --git a/apps/frontend/src/utils/router.ts b/apps/frontend/src/utils/router.ts new file mode 100644 index 0000000000..7ee077b12a --- /dev/null +++ b/apps/frontend/src/utils/router.ts @@ -0,0 +1,13 @@ +import type { LocationQueryValue, RouteRecordNameGeneric } from 'vue-router' + +export function queryAsStringOrEmpty(query: LocationQueryValue | LocationQueryValue[]): string { + return Array.isArray(query) ? (query[0] ?? '') : (query ?? '') +} + +export function queryAsString(query: LocationQueryValue | LocationQueryValue[]): string | null { + return Array.isArray(query) ? (query[0] ?? null) : (query ?? null) +} + +export function routeNameAsString(name: RouteRecordNameGeneric | undefined): string | undefined { + return name && typeof name === 'string' ? (name as string) : undefined +} diff --git a/packages/api-client/src/modules/labrinth/types.ts b/packages/api-client/src/modules/labrinth/types.ts index f5e6476226..2b3a4f772e 100644 --- a/packages/api-client/src/modules/labrinth/types.ts +++ b/packages/api-client/src/modules/labrinth/types.ts @@ -361,6 +361,122 @@ export namespace Labrinth { } } + export namespace Versions { + export namespace v2 { + export type VersionType = 'release' | 'beta' | 'alpha' + + export type VersionStatus = + | 'listed' + | 'archived' + | 'draft' + | 'unlisted' + | 'scheduled' + | 'unknown' + + export type DependencyType = 'required' | 'optional' | 'incompatible' | 'embedded' + + export type FileType = 'required-resource-pack' | 'optional-resource-pack' | 'unknown' + + export type VersionFile = { + hashes: Record + url: string + filename: string + primary: boolean + size: number + file_type?: FileType + } + + export type Dependency = { + file_name?: string + dependency_type: DependencyType + } & ( + | { + project_id: string + } + | { + version_id: string + project_id?: string + } + ) + + export type Version = { + id: string + project_id: string + author_id: string + featured: boolean + name: string + version_number: string + changelog: string + changelog_url?: string | null + date_published: string + downloads: number + version_type: VersionType + status: VersionStatus + requested_status?: VersionStatus | null + files: VersionFile[] + dependencies: Dependency[] + game_versions: string[] + loaders: string[] + } + } + + export namespace v3 { + export type VersionType = 'release' | 'beta' | 'alpha' + + export type VersionStatus = + | 'listed' + | 'archived' + | 'draft' + | 'unlisted' + | 'scheduled' + | 'unknown' + + export type DependencyType = 'required' | 'optional' | 'incompatible' | 'embedded' + + export type FileType = 'required-resource-pack' | 'optional-resource-pack' | 'unknown' + + export type VersionFile = { + hashes: Record + url: string + filename: string + primary: boolean + size: number + file_type?: FileType + } + + export type Dependency = { + version_id?: string + project_id?: string + file_name?: string + dependency_type: DependencyType + } + + export type Version = { + id: string + project_id: string + author_id: string + featured: boolean + name: string + version_number: string + project_types: string[] + games: string[] + changelog: string + date_published: string + downloads: number + version_type: VersionType + status: VersionStatus + requested_status?: VersionStatus | null + files: VersionFile[] + dependencies: Dependency[] + loaders: string[] + ordering?: number | null + game_versions?: string[] + mrpack_loaders?: string[] + environment?: string + } + } + } + export namespace Tags { export namespace v2 { export interface Category { diff --git a/packages/ui/src/components/base/Accordion.vue b/packages/ui/src/components/base/Accordion.vue index ed6331d380..b4c309f0e7 100644 --- a/packages/ui/src/components/base/Accordion.vue +++ b/packages/ui/src/components/base/Accordion.vue @@ -56,6 +56,16 @@ const emit = defineEmits(['onOpen', 'onClose']) const slots = useSlots() +watch( + () => props.openByDefault, + (newValue) => { + if (newValue !== toggledOpen.value) { + toggledOpen.value = newValue + } + }, + { immediate: true }, +) + function open() { toggledOpen.value = true emit('onOpen') diff --git a/packages/ui/src/components/base/ScrollablePanel.vue b/packages/ui/src/components/base/ScrollablePanel.vue index f4567503db..d7f9ec96d7 100644 --- a/packages/ui/src/components/base/ScrollablePanel.vue +++ b/packages/ui/src/components/base/ScrollablePanel.vue @@ -55,7 +55,6 @@ onUnmounted(() => { } }) function updateFade(scrollTop, offsetHeight, scrollHeight) { - console.log(scrollTop, offsetHeight, scrollHeight) scrollableAtBottom.value = Math.ceil(scrollTop + offsetHeight) >= scrollHeight scrollableAtTop.value = scrollTop <= 0 } diff --git a/packages/ui/src/locales/en-US/index.json b/packages/ui/src/locales/en-US/index.json index 0924f9071a..587d848839 100644 --- a/packages/ui/src/locales/en-US/index.json +++ b/packages/ui/src/locales/en-US/index.json @@ -560,6 +560,15 @@ "project-type.resourcepack.lowercase": { "defaultMessage": "{count, plural, one {resource pack} other {resource packs}}" }, + "project-type.server.capital": { + "defaultMessage": "{count, plural, one {Server} other {Servers}}" + }, + "project-type.server.category": { + "defaultMessage": "Servers" + }, + "project-type.server.lowercase": { + "defaultMessage": "{count, plural, one {server} other {servers}}" + }, "project-type.shader.capital": { "defaultMessage": "{count, plural, one {Shader} other {Shaders}}" }, diff --git a/packages/ui/src/utils/common-messages.ts b/packages/ui/src/utils/common-messages.ts index 30d0416318..36752e84d5 100644 --- a/packages/ui/src/utils/common-messages.ts +++ b/packages/ui/src/utils/common-messages.ts @@ -433,6 +433,10 @@ export const commonProjectTypeCategoryMessages = defineMessages({ id: 'project-type.shader.category', defaultMessage: 'Shaders', }, + server: { + id: 'project-type.server.category', + defaultMessage: 'Servers', + }, }) export const commonProjectTypeTitleMessages = defineMessages({ @@ -460,6 +464,10 @@ export const commonProjectTypeTitleMessages = defineMessages({ id: 'project-type.shader.capital', defaultMessage: '{count, plural, one {Shader} other {Shaders}}', }, + server: { + id: 'project-type.server.capital', + defaultMessage: '{count, plural, one {Server} other {Servers}}', + }, }) export const commonProjectTypeSentenceMessages = defineMessages({ @@ -487,6 +495,10 @@ export const commonProjectTypeSentenceMessages = defineMessages({ id: 'project-type.shader.lowercase', defaultMessage: '{count, plural, one {shader} other {shaders}}', }, + server: { + id: 'project-type.server.lowercase', + defaultMessage: '{count, plural, one {server} other {servers}}', + }, }) export const commonSettingsMessages = defineMessages({ diff --git a/packages/ui/src/utils/search.ts b/packages/ui/src/utils/search.ts index af186e4cd1..e06c407695 100644 --- a/packages/ui/src/utils/search.ts +++ b/packages/ui/src/utils/search.ts @@ -1,3 +1,4 @@ +import type { Labrinth } from '@modrinth/api-client' import { ClientIcon, ServerIcon } from '@modrinth/assets' import { formatCategory, formatCategoryHeader, sortByNameOrNumber } from '@modrinth/utils' import { defineMessage, useVIntl } from '@vintl/vintl' @@ -67,25 +68,10 @@ const ALL_PROJECT_TYPES: ProjectType[] = [ 'plugin', ] -export interface Platform { - name: string - icon: string - supported_project_types: ProjectType[] - default: boolean - formatted_name: string -} - -export interface Category { - icon: string - name: string - project_type: ProjectType - header: string -} - export interface Tags { - gameVersions: GameVersion[] - loaders: Platform[] - categories: Category[] + gameVersions: Labrinth.Tags.v2.GameVersion[] + loaders: Labrinth.Tags.v2.Loader[] + categories: Labrinth.Tags.v2.Category[] } export interface SortType { diff --git a/packages/utils/utils.ts b/packages/utils/utils.ts index e820c96e4a..868821bbe0 100644 --- a/packages/utils/utils.ts +++ b/packages/utils/utils.ts @@ -287,7 +287,7 @@ export const formatVersions = (versionArray, gameVersions) => { return (output.length === 0 ? versionArray : output).join(', ') } -export function cycleValue(value, values) { +export function cycleValue(value: T, values: T[]): T { const index = values.indexOf(value) + 1 return values[index % values.length] }