Compare commits

...
Author SHA1 Message Date
Calum H. (IMB11) 7b8fdfd879 fix: make search use v3 everywhere 2026-07-23 12:23:11 +01:00
17 changed files with 241 additions and 149 deletions
@@ -27,14 +27,14 @@ interface BrowseServerInstance {
interface ContextMenuHandle {
showMenu: (
event: MouseEvent,
result: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
result: Labrinth.Search.v3.ResultSearchProject,
options: { name: string }[],
) => void
}
interface ContextMenuOptionClick {
option: 'open_link' | 'copy_link'
item: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject
item: Labrinth.Search.v3.ResultSearchProject
}
export interface UseAppServerBrowseOptions {
@@ -263,10 +263,7 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
return actions
}
function handleRightClick(
event: MouseEvent,
result: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
) {
function handleRightClick(event: MouseEvent, result: Labrinth.Search.v3.ResultSearchProject) {
contextMenuRef.value?.showMenu(event, result, [{ name: 'open_link' }, { name: 'copy_link' }])
}
@@ -315,9 +312,7 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
}
}
function getProjectUrl(
item: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
) {
const projectType = 'project_types' in item ? item.project_types?.[0] : item.project_type
function getProjectUrl(item: Labrinth.Search.v3.ResultSearchProject) {
const projectType = item.project_types?.[0]
return `https://modrinth.com/${projectType ?? 'project'}/${item.slug ?? item.project_id}`
}
+7 -11
View File
@@ -670,7 +670,7 @@ async function getInstallProjectVersions(projectId: string) {
}
async function chooseInstanceInstallVersion(
project: Labrinth.Search.v2.ResultSearchProject & Labrinth.Search.v3.ResultSearchProject,
project: Labrinth.Search.v3.ResultSearchProject,
projectTypeValue: string,
) {
const targetInstance = instance.value
@@ -697,16 +697,14 @@ async function chooseInstanceInstallVersion(
}
function getCardActions(
result: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
result: Labrinth.Search.v3.ResultSearchProject,
currentProjectType: string,
): CardAction[] {
if (currentProjectType === 'server') {
return getServerCardActions(result as Labrinth.Search.v3.ResultSearchProject)
return getServerCardActions(result)
}
// Non-server project actions
const projectResult = result as (Labrinth.Search.v2.ResultSearchProject &
Labrinth.Search.v3.ResultSearchProject) & {
const projectResult = result as Labrinth.Search.v3.ResultSearchProject & {
installed?: boolean
installing?: boolean
}
@@ -917,11 +915,9 @@ async function search(requestParams: string) {
}
const hits = rawResults.result.hits.map((hit) => {
const mapped = {
const mapped: Labrinth.Search.v3.ResultSearchProject & { installed?: boolean } = {
...hit,
title: hit.name,
description: hit.summary,
} as unknown as Labrinth.Search.v2.ResultSearchProject & { installed?: boolean }
}
if (instance.value || isServerContext.value) {
const installedIds = instance.value
@@ -1068,7 +1064,7 @@ provideBrowseManager({
projectType,
...searchState,
advancedFiltersCollapsed,
getProjectLink: (result: Labrinth.Search.v2.ResultSearchProject) => ({
getProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) => ({
path: `/project/${result.project_id ?? result.slug}`,
query: getProjectBrowseQuery(),
}),
@@ -9,6 +9,7 @@ import {
getStoredServerAddonInstallQueue,
injectModrinthClient,
injectNotificationManager,
type ModpackSearchResult,
type PendingServerContentInstall,
type PendingServerContentInstallType,
readPendingServerContentInstalls,
@@ -24,7 +25,6 @@ import { useRoute, useRouter } from 'vue-router'
type ServerFlowFrom = 'onboarding' | 'reset-server'
type InstallableSearchResult = Labrinth.Search.v3.ResultSearchProject & {
title?: string
installing?: boolean
installed?: boolean
}
@@ -69,10 +69,7 @@ export interface ServerInstallContentContext {
installQueuedServerInstallsAndBack: () => Promise<boolean>
initServerContext: () => Promise<void>
watchServerContextChanges: () => void
searchServerModpacks: (
query: string,
limit?: number,
) => Promise<Labrinth.Projects.v2.SearchResult>
searchServerModpacks: (query: string, limit?: number) => Promise<ModpackSearchResult>
getServerProjectVersions: (projectId: string) => Promise<{ id: string }[]>
enforceSetupModpackRoute: (currentProjectType: string | undefined) => void
getQueuedServerInstallPlans: () => Map<string, BrowseInstallPlan<InstallableSearchResult>>
@@ -172,7 +169,7 @@ function getQueuedInstallPlaceholder(
projectId: plan.projectId,
versionId: plan.versionId,
contentType: plan.contentType as PendingServerContentInstallType,
title: project.title ?? project.name ?? 'Project',
title: project.name ?? 'Project',
versionName: plan.versionName ?? null,
versionNumber: plan.versionNumber ?? null,
fileName: plan.fileName ?? null,
@@ -236,7 +233,7 @@ export function createServerInstallContent(opts: {
const selectedServerInstallProjects = computed<BrowseSelectedProject[]>(() =>
Array.from(queuedServerInstalls.value.values()).map((plan) => ({
id: plan.projectId,
name: plan.project.title ?? plan.project.name ?? 'Project',
name: plan.project.name ?? 'Project',
iconUrl: plan.project.icon_url ?? null,
})),
)
@@ -359,12 +356,24 @@ export function createServerInstallContent(opts: {
}
async function searchServerModpacks(query: string, limit: number = 10) {
return client.labrinth.projects_v2.search({
const results = await client.labrinth.projects_v3.search({
query: query || undefined,
new_filters:
'project_types = "modpack" AND (client_side = "optional" OR client_side = "required") AND server_side = "required"',
'project_types = "modpack" AND environment IN ["client_and_server", "server_only_client_optional"]',
limit,
})
return {
hits: results.hits.map((hit) => ({
project_id: hit.project_id,
title: hit.name,
icon_url: hit.icon_url ?? '',
latest_version: hit.version_id,
})),
total_hits: results.total_hits,
offset: (results.page - 1) * results.hits_per_page,
limit: results.hits_per_page,
}
}
async function getServerProjectVersions(projectId: string) {
@@ -51,7 +51,7 @@ export interface ServerInstallModalHandle {
ctx?: CreationFlowContextValue | null
}
export interface ServerInstallSearchResult extends Labrinth.Search.v2.ResultSearchProject {
export interface ServerInstallSearchResult extends Labrinth.Search.v3.ResultSearchProject {
installed?: boolean
}
@@ -128,6 +128,11 @@ export function useServerInstallContent({
const { handleError } = injectNotificationManager()
let browseSearchState: ServerInstallBrowseSearchState | null = null
function getInstallProjectName(project: ServerInstallSearchResult) {
const legacyTitle = (project as ServerInstallSearchResult & { title?: string }).title
return project.name || legacyTitle || formatMessage(commonMessages.projectLabel)
}
const currentServerId = computed(() => queryAsString(route.query.sid) || null)
const fromContext = computed(() => queryAsString(route.query.from) || null)
const currentWorldId = computed(() => queryAsString(route.query.wid) || null)
@@ -177,7 +182,7 @@ export function useServerInstallContent({
const selectedServerInstallProjects = computed(() =>
Array.from(queuedServerInstalls.value.values()).map((plan) => ({
id: plan.projectId,
name: plan.project.title ?? formatMessage(commonMessages.projectLabel),
name: getInstallProjectName(plan.project),
iconUrl: plan.project.icon_url ?? null,
})),
)
@@ -261,7 +266,7 @@ export function useServerInstallContent({
projectId: plan.projectId,
versionId: plan.versionId,
contentType: plan.contentType as PendingServerContentInstallType,
title: plan.project.title ?? formatMessage(commonMessages.projectLabel),
title: getInstallProjectName(plan.project),
versionName: plan.versionName ?? null,
versionNumber: plan.versionNumber ?? null,
fileName: plan.fileName ?? null,
@@ -635,7 +640,7 @@ export function useServerInstallContent({
ctx.modpackSelection.value = {
projectId: plan.projectId,
versionId: plan.versionId,
name: plan.project.title,
name: getInstallProjectName(plan.project),
iconUrl: plan.project.icon_url ?? undefined,
}
ctx.modal.value?.setStage('final-config')
@@ -66,7 +66,7 @@ const auth = await useAuth()
let prefetchTimeout: ReturnType<typeof useTimeoutFn> | null = null
const HOVER_DURATION_TO_PREFETCH_MS = 500
const handleProjectMouseEnter = (result: Labrinth.Search.v2.ResultSearchProject) => {
const handleProjectMouseEnter = (result: Labrinth.Search.v3.ResultSearchProject) => {
const slug = result.slug || result.project_id
prefetchTimeout = useTimeoutFn(
() => {
@@ -199,24 +199,6 @@ function getServerModpackContent(project: Labrinth.Search.v3.ResultSearchProject
return undefined
}
type DiscoverProjectSearchHit = Labrinth.Search.v2.ResultSearchProject & {
version_id?: string | null
}
function mapV3ProjectHit(hit: Labrinth.Search.v3.ResultSearchProject): DiscoverProjectSearchHit {
return {
...hit,
project_type: hit.project_types[0] ?? projectTypeId.value,
title: hit.name,
description: hit.summary,
versions: hit.version_id ? [hit.version_id] : [],
latest_version: hit.version_id,
icon_url: hit.icon_url ?? '',
client_side: 'unknown',
server_side: 'unknown',
}
}
const hostingContextQuery = computed(() => {
const query: LocationQueryRaw = {}
@@ -234,6 +216,18 @@ function withHostingContext(path: string) {
return hostingContextQuery.value ? { path, query: hostingContextQuery.value } : path
}
function parseSearchParams(requestParams: string): Labrinth.Search.SearchParams {
const params = new URLSearchParams(requestParams.replace(/^\?/, ''))
return {
query: params.get('query') ?? undefined,
offset: params.get('offset') ?? undefined,
index: params.get('index') ?? undefined,
limit: params.get('limit') ?? undefined,
new_filters: params.get('new_filters') ?? undefined,
}
}
async function fetchSearch(requestParams: string) {
debug('search() called', {
requestParams: requestParams.substring(0, 100),
@@ -241,11 +235,7 @@ async function fetchSearch(requestParams: string) {
projectTypeId: projectTypeId.value,
})
const raw = await client.request<Labrinth.Search.v3.SearchResults>('/search', {
api: 'labrinth',
version: 3,
method: 'GET',
params: Object.fromEntries(new URLSearchParams(requestParams.replace(/^\?/, ''))),
const raw = await client.labrinth.projects_v3.search(parseSearchParams(requestParams), {
headers: withLabrinthCanaryHeader(),
})
@@ -261,7 +251,7 @@ async function fetchSearch(requestParams: string) {
}
return {
projectHits: raw.hits.map(mapV3ProjectHit),
projectHits: raw.hits,
serverHits: [],
total_hits: raw.total_hits,
per_page: raw.hits_per_page,
@@ -277,7 +267,7 @@ async function search(requestParams: string) {
}
function getCardActions(
result: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
result: Labrinth.Search.v3.ResultSearchProject,
currentProjectType: string,
): CardAction[] {
if (currentProjectType === 'server') return []
@@ -492,7 +482,7 @@ provideBrowseManager({
tags,
projectType: projectTypeId,
...searchState,
getProjectLink: (result: Labrinth.Search.v2.ResultSearchProject) =>
getProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) =>
withHostingContext(
`/${projectType.value?.id ?? 'project'}/${result.slug ? result.slug : result.project_id}`,
),
@@ -183,14 +183,7 @@
:downloads="project.downloads"
:followers="project.followers"
:tags="project.categories"
:environment="
project.client_side && project.server_side
? {
clientSide: project.client_side,
serverSide: project.server_side,
}
: undefined
"
:environment="project.environment?.[0]"
:status="
auth.user && (auth.user.id! === user.id || tags.staffRoles.includes(auth.user.role))
? (project.status as ProjectStatus)
@@ -266,10 +259,7 @@ import {
import { isPermission } from '~/utils/permissions.ts'
import { projectUserSorting } from '~/utils/projects.ts'
type ProjectV3 = Labrinth.Projects.v3.Project & {
client_side: 'required' | 'optional' | 'unsupported'
server_side: 'required' | 'optional' | 'unsupported'
}
type ProjectV3 = Labrinth.Projects.v3.Project
const vintl = useVIntl()
const { formatMessage } = vintl
@@ -353,31 +343,7 @@ const {
categories = categories.concat(project.mrpack_loaders as string[])
}
const singleplayer = project.singleplayer && (project.singleplayer as string[])[0]
const clientAndServer =
project.client_and_server && (project.client_and_server as string[])[0]
const clientOnly = project.client_only && (project.client_only as string[])[0]
const serverOnly = project.server_only && (project.server_only as string[])[0]
let client_side: ProjectV3['client_side'] | undefined
let server_side: ProjectV3['server_side'] | undefined
// quick and dirty hack to show envs as legacy
if (singleplayer && clientAndServer && !clientOnly && !serverOnly) {
client_side = 'required'
server_side = 'required'
} else if (singleplayer && clientAndServer && clientOnly && !serverOnly) {
client_side = 'required'
server_side = 'unsupported'
} else if (singleplayer && clientAndServer && !clientOnly && serverOnly) {
client_side = 'unsupported'
server_side = 'required'
} else if (singleplayer && clientAndServer && clientOnly && serverOnly) {
client_side = 'optional'
server_side = 'optional'
}
return { ...project, categories, client_side, server_side }
return { ...project, categories }
})
},
placeholderData: [],
@@ -73,6 +73,30 @@ export class LabrinthProjectsV3Module extends AbstractModule {
})
}
/**
* Search projects (v3)
*
* @param params - Search parameters
* @returns Promise resolving to v3 search results
*/
public async search(
params: Labrinth.Search.SearchParams,
options?: {
headers?: Record<string, string>
},
): Promise<Labrinth.Search.v3.SearchResults> {
return this.client.request<Labrinth.Search.v3.SearchResults>(`/search`, {
api: 'labrinth',
version: 3,
method: 'GET',
headers: options?.headers,
params: {
...params,
facets: params.facets ? JSON.stringify(params.facets) : undefined,
},
})
}
/**
* Edit a project (v3)
*
@@ -1750,6 +1750,17 @@ export namespace Labrinth {
}
export namespace Search {
export type SearchParams = {
query?: string
offset?: string | number
index?: string
limit?: string | number
new_filters?: string
facets?: string[][]
filters?: string
version?: string
}
export namespace v2 {
export interface ResultSearchProject {
project_id: string
@@ -1812,7 +1823,9 @@ export namespace Labrinth {
featured_gallery: string | null
color: number | null
loaders: string[]
project_loader_fields?: Record<string, unknown[]>
project_loader_fields?: Record<string, unknown[]> & {
environment?: Projects.v3.Environment[]
}
minecraft_server?: Projects.v3.MinecraftServer | null
minecraft_java_server?: Projects.v3.MinecraftJavaServer | null
minecraft_bedrock_server?: Projects.v3.MinecraftBedrockServer | null
+3 -3
View File
@@ -250,9 +250,9 @@ pub struct SearchResultsV3 {
pub struct SearchResultV3 {
pub hits: Vec<serde_json::Value>,
#[serde(default)]
pub offset: u32,
#[serde(default)]
pub limit: u32,
pub page: u32,
#[serde(default, alias = "limit")]
pub hits_per_page: u32,
#[serde(default)]
pub total_hits: u32,
}
@@ -68,11 +68,7 @@
class="smart-clickable:allow-pointer-events"
/>
</template>
<ProjectCardEnvironment
v-if="environment"
:client-side="environment.clientSide"
:server-side="environment.serverSide"
/>
<ProjectCardEnvironment v-if="environment" :environment="environment" />
<ProjectCardTags
v-if="tags"
:tags="tags"
@@ -168,11 +164,7 @@
class="smart-clickable:allow-pointer-events"
/>
</template>
<ProjectCardEnvironment
v-if="environment"
:client-side="environment.clientSide"
:server-side="environment.serverSide"
/>
<ProjectCardEnvironment v-if="environment" :environment="environment" />
<ProjectCardTags
v-if="tags"
:tags="tags"
@@ -213,7 +205,7 @@ import ServerRegion from '../server/ServerRegion.vue'
import ProjectCardAuthor from './ProjectCardAuthor.vue'
import ProjectCardDate from './ProjectCardDate.vue'
import ProjectCardEnvironment, {
type ProjectCardEnvironmentProps,
type ProjectCardEnvironmentValue,
} from './ProjectCardEnvironment.vue'
import ProjectCardStats from './ProjectCardStats.vue'
import ProjectCardTags from './ProjectCardTags.vue'
@@ -257,7 +249,7 @@ const props = defineProps<{
isServerProject?: boolean
banner?: string
color?: string | number
environment?: ProjectCardEnvironmentProps
environment?: ProjectCardEnvironmentValue
status?: ProjectStatus
maxTags?: number
}>()
@@ -1,18 +1,29 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { ClientIcon, GlobeIcon, ServerIcon } from '@modrinth/assets'
import { ClientIcon, GlobeIcon, ServerIcon, UserIcon } from '@modrinth/assets'
import { computed } from 'vue'
import { defineMessages, useVIntl } from '../../../composables'
import { TagItem } from '../../base'
const { formatMessage } = useVIntl()
export type ProjectCardEnvironmentProps = {
export type LegacyProjectEnvironment = {
clientSide: Labrinth.Projects.v2.Environment
serverSide: Labrinth.Projects.v2.Environment
}
defineProps<ProjectCardEnvironmentProps>()
export type ProjectCardEnvironmentValue =
| Labrinth.Projects.v3.Environment
| LegacyProjectEnvironment
export type ProjectCardEnvironmentProps = {
environment?: ProjectCardEnvironmentValue
clientSide?: Labrinth.Projects.v2.Environment
serverSide?: Labrinth.Projects.v2.Environment
}
const props = defineProps<ProjectCardEnvironmentProps>()
const messages = defineMessages({
clientOrServer: {
@@ -31,36 +42,99 @@ const messages = defineMessages({
id: 'project-card.environment.server',
defaultMessage: 'Server',
},
singleplayer: {
id: 'project-card.environment.singleplayer',
defaultMessage: 'Singleplayer',
},
dedicatedServer: {
id: 'project-card.environment.dedicated-server',
defaultMessage: 'Dedicated server',
},
})
const displayEnvironment = computed(() => {
const environment =
props.environment ??
(props.clientSide && props.serverSide
? { clientSide: props.clientSide, serverSide: props.serverSide }
: undefined)
if (typeof environment === 'string') {
switch (environment) {
case 'client_or_server':
case 'client_or_server_prefers_both':
return 'client-or-server'
case 'client_and_server':
return 'client-and-server'
case 'client_only':
case 'client_only_server_optional':
return 'client'
case 'server_only':
case 'server_only_client_optional':
return 'server'
case 'singleplayer_only':
return 'singleplayer'
case 'dedicated_server_only':
return 'dedicated-server'
default:
return undefined
}
}
if (!environment) {
return undefined
}
const { clientSide, serverSide } = environment
if (clientSide === 'optional' && serverSide === 'optional') {
return 'client-or-server'
}
if (clientSide === 'required' && serverSide === 'required') {
return 'client-and-server'
}
if (
(clientSide === 'optional' || clientSide === 'required') &&
(serverSide === 'optional' || serverSide === 'unsupported')
) {
return 'client'
}
if (
(serverSide === 'optional' || serverSide === 'required') &&
(clientSide === 'optional' || clientSide === 'unsupported')
) {
return 'server'
}
return undefined
})
</script>
<template>
<TagItem class="empty:hidden">
<template v-if="clientSide === 'optional' && serverSide === 'optional'">
<template v-if="displayEnvironment === 'client-or-server'">
<GlobeIcon aria-hidden="true" />
{{ formatMessage(messages.clientOrServer) }}
</template>
<template v-else-if="clientSide === 'required' && serverSide === 'required'">
<template v-else-if="displayEnvironment === 'client-and-server'">
<GlobeIcon aria-hidden="true" />
{{ formatMessage(messages.clientAndServer) }}
</template>
<template
v-else-if="
(clientSide === 'optional' || clientSide === 'required') &&
(serverSide === 'optional' || serverSide === 'unsupported')
"
>
<template v-else-if="displayEnvironment === 'client'">
<ClientIcon aria-hidden="true" />
{{ formatMessage(messages.client) }}
</template>
<template
v-else-if="
(serverSide === 'optional' || serverSide === 'required') &&
(clientSide === 'optional' || clientSide === 'unsupported')
"
>
<template v-else-if="displayEnvironment === 'server'">
<ServerIcon aria-hidden="true" />
{{ formatMessage(messages.server) }}
</template>
<template v-else-if="displayEnvironment === 'singleplayer'">
<UserIcon aria-hidden="true" />
{{ formatMessage(messages.singleplayer) }}
</template>
<template v-else-if="displayEnvironment === 'dedicated-server'">
<ServerIcon aria-hidden="true" />
{{ formatMessage(messages.dedicatedServer) }}
</template>
</TagItem>
</template>
@@ -6,6 +6,7 @@ export type {
Gamemode,
GeneratorSettingsMode,
LoaderVersionType,
ModpackSearchResult,
SetupType,
} from '../../flows/creation-flow-modal/creation-flow-context'
export { default as CreationFlowModal } from '../../flows/creation-flow-modal/index.vue'
@@ -66,6 +66,33 @@ const messages = defineMessages({
defaultMessage: 'No results found for your query!',
},
})
function getLoaderFieldValues(
result: Labrinth.Search.v3.ResultSearchProject,
field: string,
): string[] {
return (result.project_loader_fields?.[field] ?? []).filter(
(value): value is string => typeof value === 'string',
)
}
function getProjectCardTags(result: Labrinth.Search.v3.ResultSearchProject, displayOnly: boolean) {
const tags = new Set(displayOnly ? result.display_categories : result.categories)
for (const loader of result.loaders) {
if (loader !== 'mrpack') {
tags.add(loader)
}
}
if (result.loaders.includes('mrpack')) {
for (const loader of getLoaderFieldValues(result, 'mrpack_loaders')) {
tags.add(loader)
}
}
return Array.from(tags)
}
</script>
<template>
@@ -247,8 +274,8 @@ const messages = defineMessages({
v-for="result in ctx.projectHits.value"
:key="result.project_id"
:link="ctx.getProjectLink(result)"
:title="result.title"
:icon-url="result.icon_url"
:title="result.name"
:icon-url="result.icon_url ?? undefined"
:author="{
name: result.organization == null ? result.author : result.organization,
link:
@@ -266,9 +293,9 @@ const messages = defineMessages({
ctx.effectiveCurrentSortType.value.name === 'newest' ? 'published' : 'updated'
"
:downloads="result.downloads"
:summary="result.description"
:tags="result.display_categories"
:all-tags="result.categories"
:summary="result.summary"
:tags="getProjectCardTags(result, true)"
:all-tags="getProjectCardTags(result, false)"
:deprioritized-tags="ctx.deprioritizedTags.value"
:exclude-loaders="ctx.excludeLoaders.value"
:followers="result.follows"
@@ -276,10 +303,7 @@ const messages = defineMessages({
:color="result.color ?? undefined"
:environment="
['mod', 'modpack'].includes(ctx.projectType.value)
? {
clientSide: result.client_side as Labrinth.Projects.v2.Environment,
serverSide: result.server_side as Labrinth.Projects.v2.Environment,
}
? result.project_loader_fields?.environment?.[0]
: undefined
"
:layout="ctx.effectiveLayout.value"
@@ -46,7 +46,7 @@ export interface BrowseManagerContext {
clearSearch: () => void
onFilterChange: () => void
getProjectLink: (result: Labrinth.Search.v2.ResultSearchProject) => string | RouteLocationRaw
getProjectLink: (result: Labrinth.Search.v3.ResultSearchProject) => string | RouteLocationRaw
getServerProjectLink: (
result: Labrinth.Search.v3.ResultSearchProject,
) => string | RouteLocationRaw
@@ -57,7 +57,7 @@ export interface BrowseManagerContext {
variant: 'app' | 'web'
getCardActions?: (
result: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
result: Labrinth.Search.v3.ResultSearchProject,
projectType: string,
) => CardAction[]
@@ -85,13 +85,10 @@ export interface BrowseManagerContext {
result: Labrinth.Search.v3.ResultSearchProject,
) => ServerModpackContent | undefined
onProjectHover?: (result: Labrinth.Search.v2.ResultSearchProject) => void
onProjectHover?: (result: Labrinth.Search.v3.ResultSearchProject) => void
onServerProjectHover?: (result: Labrinth.Search.v3.ResultSearchProject) => void
onProjectHoverEnd?: () => void
onContextMenu?: (
event: MouseEvent,
result: Labrinth.Search.v2.ResultSearchProject | Labrinth.Search.v3.ResultSearchProject,
) => void
onContextMenu?: (event: MouseEvent, result: Labrinth.Search.v3.ResultSearchProject) => void
offline?: Ref<boolean>
filtersMenuOpen?: Ref<boolean>
@@ -3,7 +3,7 @@ import type { Component } from 'vue'
import type { RouteLocationRaw } from 'vue-router'
export interface BrowseSearchResponse {
projectHits: (Labrinth.Search.v2.ResultSearchProject & {
projectHits: (Labrinth.Search.v3.ResultSearchProject & {
installed?: boolean
installing?: boolean
})[]
+6
View File
@@ -2825,9 +2825,15 @@
"project-card.environment.client-or-server": {
"defaultMessage": "Client or server"
},
"project-card.environment.dedicated-server": {
"defaultMessage": "Dedicated server"
},
"project-card.environment.server": {
"defaultMessage": "Server"
},
"project-card.environment.singleplayer": {
"defaultMessage": "Singleplayer"
},
"project-type.all": {
"defaultMessage": "All"
},
@@ -63,7 +63,7 @@ export const AllTypes: Story = {
:followers="25000"
date-updated="2023-06-01T00:00:00Z"
:tags="['utility']"
:environment="{ clientSide: 'unsupported', serverSide: 'required' }"
environment="server_only"
/>
<ProjectCard
link="/modpack/example-modpack"