feat: add Raw Performer badge and real data to search results
This commit is contained in:
+23
-5
@@ -39,6 +39,9 @@ interface UnifiedResult {
|
||||
price?: { currency: string; initial: number; final: number } | null
|
||||
platforms?: { windows: boolean; mac: boolean; linux: boolean } | null
|
||||
controllerSupport?: string | null
|
||||
isRawPerformer?: boolean
|
||||
bestFps?: number | null
|
||||
latestVersion?: string | null
|
||||
}
|
||||
|
||||
function SearchContent() {
|
||||
@@ -86,8 +89,8 @@ function SearchContent() {
|
||||
|
||||
function handleClick(result: UnifiedResult) {
|
||||
const path = result.appId
|
||||
? `/game/${result.appId}`
|
||||
: `/game/${result.id}`
|
||||
? `/game/${result.appId}?sync=1`
|
||||
: `/game/${result.id}?sync=1`
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
@@ -244,9 +247,16 @@ function SearchResultCard({
|
||||
{/* Row 1: Title + metascore/price row */}
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="min-w-0 flex items-center gap-2 flex-wrap">
|
||||
<h3 className="text-sm sm:text-base font-semibold text-text group-hover:text-primary transition-colors duration-200 truncate">
|
||||
{result.title}
|
||||
</h3>
|
||||
{result.isRawPerformer && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-green-500/10 border border-green-500/20 text-green-400 text-[10px] font-semibold shrink-0">
|
||||
⚡ RAW PERFORMER
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{(result.developer || result.publisher) && (
|
||||
<p className="text-[11px] text-text/45 mt-0.5 truncate">
|
||||
{result.developer}
|
||||
@@ -422,11 +432,19 @@ function SearchResultCard({
|
||||
color={result.platformSupport ? protonColor(result.platformSupport.protonStatus) : undefined}
|
||||
/>
|
||||
|
||||
{/* Avg FPS */}
|
||||
<DataField label="Avg. FPS" value="—" bar />
|
||||
{/* Best FPS */}
|
||||
<DataField
|
||||
label="Best FPS"
|
||||
value={result.bestFps != null ? String(Math.round(result.bestFps)) : "—"}
|
||||
bar={result.bestFps != null}
|
||||
color={result.bestFps != null && result.bestFps >= 60 ? "text-green-400" : undefined}
|
||||
/>
|
||||
|
||||
{/* Version */}
|
||||
<DataField label="Version" value="—" />
|
||||
<DataField
|
||||
label="Version"
|
||||
value={result.latestVersion ?? "—"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.article>
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
communityPresets,
|
||||
gameComments,
|
||||
} from "@/lib/db/schema"
|
||||
import { ilike, or, sql, eq, inArray } from "drizzle-orm"
|
||||
import { ilike, or, sql, eq, inArray, and } from "drizzle-orm"
|
||||
|
||||
interface SteamSearchItem {
|
||||
id: number
|
||||
@@ -146,6 +146,64 @@ export const searchUnifiedRoutes = new Elysia({ prefix: "/search" }).get(
|
||||
countMap.get(c.gameId)!.comments = c.count
|
||||
}
|
||||
|
||||
// ── 2b. Raw Performer + best FPS ────────────────────────────────
|
||||
let rawPerformerMap = new Map<string, boolean>()
|
||||
let bestFpsMap = new Map<string, number>()
|
||||
|
||||
if (localGameIds.length > 0) {
|
||||
const perfStats = await db
|
||||
.select({
|
||||
gameId: gameVersions.gameId,
|
||||
bestFps: sql<number>`MAX(${performanceEntries.fpsAvg})::real`,
|
||||
isRawPerformer: sql<boolean>`BOOL_OR(
|
||||
${performanceEntries.fpsAvg} >= 60
|
||||
AND ${performanceEntries.fsrVersion} = 'none'
|
||||
AND ${performanceEntries.frameGenMethod} = 'none'
|
||||
)`,
|
||||
})
|
||||
.from(performanceEntries)
|
||||
.innerJoin(
|
||||
gameVersions,
|
||||
eq(performanceEntries.versionId, gameVersions.id),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
inArray(gameVersions.gameId, localGameIds),
|
||||
eq(performanceEntries.isRemoved, false),
|
||||
),
|
||||
)
|
||||
.groupBy(gameVersions.gameId)
|
||||
|
||||
for (const row of perfStats) {
|
||||
bestFpsMap.set(row.gameId, row.bestFps)
|
||||
rawPerformerMap.set(row.gameId, row.isRawPerformer)
|
||||
}
|
||||
}
|
||||
|
||||
// ── 2c. Latest version ──────────────────────────────────────────
|
||||
let latestVersionMap = new Map<string, string>()
|
||||
|
||||
if (localGameIds.length > 0) {
|
||||
const versionRows = await db
|
||||
.select({
|
||||
gameId: gameVersions.gameId,
|
||||
versionString: gameVersions.versionString,
|
||||
})
|
||||
.from(gameVersions)
|
||||
.where(
|
||||
and(
|
||||
inArray(gameVersions.gameId, localGameIds),
|
||||
eq(gameVersions.isLatest, true),
|
||||
),
|
||||
)
|
||||
|
||||
for (const row of versionRows) {
|
||||
if (row.versionString) {
|
||||
latestVersionMap.set(row.gameId, row.versionString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Search Steam ─────────────────────────────────────────────
|
||||
let steamItems: SteamSearchItem[] = []
|
||||
try {
|
||||
@@ -214,6 +272,9 @@ export const searchUnifiedRoutes = new Elysia({ prefix: "/search" }).get(
|
||||
antiCheatStatus: platform.antiCheatStatus,
|
||||
}
|
||||
: null,
|
||||
isRawPerformer: rawPerformerMap.get(g.id) ?? false,
|
||||
bestFps: bestFpsMap.get(g.id) ?? null,
|
||||
latestVersion: latestVersionMap.get(g.id) ?? null,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -242,6 +303,9 @@ export const searchUnifiedRoutes = new Elysia({ prefix: "/search" }).get(
|
||||
: null,
|
||||
platforms: item.platforms,
|
||||
controllerSupport: item.controller_support || null,
|
||||
isRawPerformer: false,
|
||||
bestFps: null,
|
||||
latestVersion: null,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user