feat: Made design more responsive
fix: Made blurring server side to help with performance fix: Fixed shortcut useEffect loop
This commit is contained in:
parent
b4a89385d0
commit
9e4b2a02c1
38 changed files with 583 additions and 329 deletions
|
|
@ -1,11 +1,11 @@
|
|||
import Elysia, { status } from "elysia";
|
||||
import { activeGame, config, db, events, setActiveGame, taskQueue } from "../app";
|
||||
import { and, eq, getTableColumns } from "drizzle-orm";
|
||||
import { config, db, taskQueue } from "../app";
|
||||
import { and, eq, getTableColumns, sql } from "drizzle-orm";
|
||||
import z from "zod";
|
||||
import * as schema from "../schema/app";
|
||||
import fs from "node:fs/promises";
|
||||
import { FrontEndGameType, FrontEndGameTypeDetailed, GameListFilterSchema } from "@shared/constants";
|
||||
import { getRomApiRomsIdGet, getRomsApiRomsGet, updateRomUserApiRomsIdPropsPut } from "@clients/romm";
|
||||
import { getRomApiRomsIdGet, getRomsApiRomsGet } from "@clients/romm";
|
||||
import { InstallJob } from "../jobs/install-job";
|
||||
import path from "node:path";
|
||||
import { calculateSize, checkInstalled, convertRomToFrontend, convertRomToFrontendDetailed, getLocalGameMatch } from "./services/utils";
|
||||
|
|
@ -13,9 +13,10 @@ import buildStatusResponse, { getValidLaunchCommandsForGame } from "./services/s
|
|||
import { errorToResponse } from "elysia/adapter/bun/handler";
|
||||
import { launchCommand } from "./services/launchGameService";
|
||||
import { getErrorMessage } from "@/bun/utils";
|
||||
import sharp from 'sharp';
|
||||
|
||||
export default new Elysia()
|
||||
.get('/game/local/:id/cover', async ({ params: { id }, set }) =>
|
||||
.get('/game/local/:id/cover', async ({ params: { id }, query: { blur, width, height }, set }) =>
|
||||
{
|
||||
const coverBlob = await db.query.games.findFirst({ columns: { cover: true, cover_type: true }, where: eq(schema.games.id, id) });
|
||||
if (!coverBlob || !coverBlob.cover)
|
||||
|
|
@ -26,9 +27,23 @@ export default new Elysia()
|
|||
{
|
||||
set.headers["content-type"] = coverBlob.cover_type;
|
||||
}
|
||||
return status(200, coverBlob.cover);
|
||||
}, { response: { 200: z.instanceof(Buffer<ArrayBufferLike>), 404: z.any() }, params: z.object({ id: z.coerce.number() }) })
|
||||
.get('/screenshot/:id', async ({ params: { id }, set }) =>
|
||||
|
||||
return sharp(coverBlob.cover).resize({ width, height, withoutEnlargement: true }).blur(blur);
|
||||
}, {
|
||||
params: z.object({ id: z.coerce.number() }),
|
||||
query: z.object({ blur: z.coerce.number().optional(), width: z.coerce.number().optional(), height: z.coerce.number().optional() })
|
||||
})
|
||||
.get('/image/:source/*', async ({ params: { source, "*": path }, query: { blur, width, height } }) =>
|
||||
{
|
||||
if (source === 'romm')
|
||||
{
|
||||
const rommAdress = config.get('rommAddress');
|
||||
const rommFetch = await fetch(`${rommAdress}/${path}`);
|
||||
return sharp(await rommFetch.arrayBuffer()).resize({ width, height, withoutEnlargement: true }).sharpen().blur(blur);
|
||||
}
|
||||
return status('Not Found');
|
||||
}, { query: z.object({ blur: z.coerce.number().optional(), width: z.coerce.number().optional(), height: z.coerce.number().optional() }) })
|
||||
.get('/screenshot/:id', async ({ params: { id }, query: { blur, width, height }, set }) =>
|
||||
{
|
||||
const screenshot = await db.query.screenshots.findFirst({ where: eq(schema.screenshots.id, id), columns: { content: true, type: true } });
|
||||
if (screenshot)
|
||||
|
|
@ -37,12 +52,15 @@ export default new Elysia()
|
|||
{
|
||||
set.headers["content-type"] = screenshot.type;
|
||||
}
|
||||
return screenshot.content;
|
||||
return sharp(screenshot.content).resize({ width, height, withoutEnlargement: true }).blur(blur);
|
||||
|
||||
}
|
||||
|
||||
return status(404);
|
||||
}, { params: z.object({ id: z.coerce.number() }) })
|
||||
}, {
|
||||
params: z.object({ id: z.coerce.number() }),
|
||||
query: z.object({ blur: z.coerce.number().optional(), width: z.coerce.number().optional(), height: z.coerce.number().optional() })
|
||||
})
|
||||
.get("/game/local/:id/installed", async ({ params: { id } }) =>
|
||||
{
|
||||
const data = await db.query.games.findFirst({ where: eq(schema.games.id, id) });
|
||||
|
|
@ -69,32 +87,34 @@ export default new Elysia()
|
|||
if (!collection_id)
|
||||
{
|
||||
const localGames = await db.select({
|
||||
platform_display_name: schema.platforms.name,
|
||||
id: schema.games.id,
|
||||
last_played: schema.games.last_played,
|
||||
created_at: schema.games.created_at,
|
||||
platform_id: schema.games.platform_id,
|
||||
slug: schema.games.slug,
|
||||
name: schema.games.name,
|
||||
path_fs: schema.games.path_fs,
|
||||
source_id: schema.games.source_id,
|
||||
source: schema.games.source
|
||||
}).from(schema.games)
|
||||
.leftJoin(schema.platforms, eq(schema.games.platform_id, schema.platforms.id))
|
||||
...getTableColumns(schema.games),
|
||||
platform: schema.platforms,
|
||||
screenshotIds: sql<number[]>`coalesce(json_group_array(${schema.screenshots.id}),json('[]'))`.mapWith(d => JSON.parse(d) as number[]),
|
||||
})
|
||||
.from(schema.games)
|
||||
.leftJoin(schema.platforms, eq(schema.platforms.id, schema.games.platform_id))
|
||||
.leftJoin(schema.screenshots, eq(schema.screenshots.game_id, schema.games.id))
|
||||
.groupBy(schema.games.id)
|
||||
|
||||
.where(and(...where));
|
||||
|
||||
localGamesSet = new Set(localGames.filter(g => !!g.source_id).map(g => g.source_id!));
|
||||
games.push(...localGames.map(g =>
|
||||
{
|
||||
const game: FrontEndGameType = {
|
||||
...g,
|
||||
platform_display_name: g.platform_display_name ?? "Local",
|
||||
platform_display_name: g.platform?.name ?? "Local",
|
||||
id: { id: g.id, source: 'local' },
|
||||
updated_at: g.created_at,
|
||||
path_cover: `/api/romm/game/local/${g.id}/cover`,
|
||||
source_id: g.source_id,
|
||||
source: g.source,
|
||||
path_platform_cover: `/api/romm/platform/local/${g.platform_id}/cover`
|
||||
path_platform_cover: `/api/romm/platform/local/${g.platform_id}/cover`,
|
||||
paths_screenshots: g.screenshotIds?.map(s => `/api/romm/screenshot/${s}`) ?? [],
|
||||
path_fs: g.path_fs,
|
||||
last_played: g.last_played,
|
||||
slug: g.slug,
|
||||
name: g.name,
|
||||
platform_id: g.platform_id
|
||||
};
|
||||
return game;
|
||||
}));
|
||||
|
|
@ -118,25 +138,35 @@ export default new Elysia()
|
|||
{
|
||||
async function getLocalGameDetailed (match: any)
|
||||
{
|
||||
const localGames = await db.select({
|
||||
platform_display_name: schema.platforms.name,
|
||||
...getTableColumns(schema.games)
|
||||
}).from(schema.games).where(match).leftJoin(schema.platforms, eq(schema.games.platform_id, schema.platforms.id));
|
||||
if (localGames.length > 0)
|
||||
const localGame = await db.query.games.findFirst({
|
||||
where: match,
|
||||
with: {
|
||||
screenshots: { columns: { id: true } },
|
||||
platform: { columns: { name: true } }
|
||||
}
|
||||
});
|
||||
if (localGame)
|
||||
{
|
||||
const screenshots = await db.query.screenshots.findMany({ where: eq(schema.screenshots.game_id, localGames[0].id), columns: { id: true } });
|
||||
const exists = await checkInstalled(localGames[0].path_fs);
|
||||
const fileSize = await calculateSize(localGames[0].path_fs);
|
||||
const exists = await checkInstalled(localGame.path_fs);
|
||||
const fileSize = await calculateSize(localGame.path_fs);
|
||||
const game: FrontEndGameTypeDetailed = {
|
||||
...localGames[0],
|
||||
path_cover: `/api/romm/game/local/${localGames[0].id}/cover`,
|
||||
updated_at: localGames[0].created_at,
|
||||
id: { id: localGames[0].id, source: 'local' },
|
||||
path_platform_cover: `/api/romm/platform/local/${localGames[0].platform_id}/cover`,
|
||||
path_cover: `/api/romm/game/local/${localGame.id}/cover`,
|
||||
updated_at: localGame.created_at,
|
||||
id: { id: localGame.id, source: 'local' },
|
||||
path_platform_cover: `/api/romm/platform/local/${localGame.platform_id}/cover`,
|
||||
fs_size_bytes: fileSize ?? null,
|
||||
paths_screenshots: screenshots.map(s => `/api/romm/screenshot/${s.id}`),
|
||||
paths_screenshots: localGame.screenshots.map(s => `/api/romm/screenshot/${s.id}`),
|
||||
local: true,
|
||||
missing: !exists
|
||||
missing: !exists,
|
||||
platform_display_name: localGame.platform.name,
|
||||
summary: localGame.summary,
|
||||
source: localGame.source,
|
||||
source_id: localGame.source_id,
|
||||
path_fs: localGame.path_fs,
|
||||
last_played: localGame.last_played,
|
||||
slug: localGame.slug,
|
||||
name: localGame.name,
|
||||
platform_id: localGame.platform_id
|
||||
};
|
||||
return game;
|
||||
}
|
||||
|
|
@ -146,14 +176,12 @@ export default new Elysia()
|
|||
|
||||
if (source === 'local')
|
||||
{
|
||||
|
||||
const localGame = await getLocalGameDetailed(eq(schema.games.id, id));
|
||||
if (localGame) return localGame;
|
||||
return status('Not Found');
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
const localGame = await getLocalGameDetailed(getLocalGameMatch(id, source));
|
||||
if (localGame) return localGame;
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default new Elysia()
|
|||
slug: p.slug,
|
||||
name: p.display_name,
|
||||
family_name: p.family_name,
|
||||
path_cover: `/api/romm/assets/platforms/${p.slug}.svg`,
|
||||
path_cover: `/api/romm/image/romm/assets/platforms/${p.slug}.svg`,
|
||||
game_count: p.rom_count,
|
||||
updated_at: new Date(p.updated_at),
|
||||
id: { source: 'romm', id: p.id },
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export function convertRomToFrontend (rom: SimpleRomSchema): FrontEndGameType
|
|||
{
|
||||
const game: FrontEndGameType = {
|
||||
id: { id: rom.id, source: 'romm' },
|
||||
path_cover: `/api/romm${rom.path_cover_large}`,
|
||||
path_cover: `/api/romm/image/romm${rom.path_cover_large}`,
|
||||
last_played: rom.rom_user.last_played ? new Date(rom.rom_user.last_played) : null,
|
||||
updated_at: new Date(rom.updated_at),
|
||||
slug: rom.slug,
|
||||
|
|
@ -36,9 +36,10 @@ export function convertRomToFrontend (rom: SimpleRomSchema): FrontEndGameType
|
|||
platform_display_name: rom.platform_display_name,
|
||||
name: rom.name,
|
||||
path_fs: null,
|
||||
path_platform_cover: `/api/romm/assets/platforms/${rom.platform_slug}.svg`,
|
||||
path_platform_cover: `/api/romm/image/romm/assets/platforms/${rom.platform_slug}.svg`,
|
||||
source: null,
|
||||
source_id: null
|
||||
source_id: null,
|
||||
paths_screenshots: rom.merged_screenshots.map(s => `/api/romm/image/romm/${s}`),
|
||||
};
|
||||
|
||||
return game;
|
||||
|
|
@ -50,7 +51,6 @@ export function convertRomToFrontendDetailed (rom: DetailedRomSchema)
|
|||
...convertRomToFrontend(rom),
|
||||
summary: rom.summary,
|
||||
fs_size_bytes: rom.fs_size_bytes,
|
||||
paths_screenshots: rom.merged_screenshots.map(s => `/api/romm${s}`),
|
||||
local: false,
|
||||
missing: rom.missing_from_fs
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { sql } from "drizzle-orm";
|
||||
import { sql, relations } from "drizzle-orm";
|
||||
import { integer, text, sqliteTable, blob } from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const games = sqliteTable('games', {
|
||||
|
|
@ -19,6 +19,14 @@ export const games = sqliteTable('games', {
|
|||
summary: text("summary")
|
||||
});
|
||||
|
||||
export const gamesRelations = relations(games, ({ many, one }) => ({
|
||||
screenshots: many(screenshots),
|
||||
platform: one(platforms, {
|
||||
fields: [games.id],
|
||||
references: [platforms.id]
|
||||
})
|
||||
}));
|
||||
|
||||
export const platforms = sqliteTable('platforms', {
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
igdb_id: integer("igdb_id").unique(),
|
||||
|
|
@ -35,6 +43,8 @@ export const platforms = sqliteTable('platforms', {
|
|||
family_name: text("family_name")
|
||||
});
|
||||
|
||||
export const platformsRelations = relations(platforms, ({ many }) => ({ games: many(games) }));
|
||||
|
||||
export const collections_games = sqliteTable('collections_games', {
|
||||
collection_id: integer('collection_id').notNull().references(() => collections.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
game_id: integer('game_id').notNull().references(() => games.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
|
|
@ -51,4 +61,11 @@ export const screenshots = sqliteTable('screenshots', {
|
|||
game_id: integer('game_id').references(() => games.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
content: blob('content', { mode: 'buffer' }).notNull(),
|
||||
type: text('type')
|
||||
});
|
||||
});
|
||||
|
||||
export const screenshotsRelations = relations(screenshots, ({ one }) => ({
|
||||
game: one(games, {
|
||||
fields: [screenshots.game_id],
|
||||
references: [games.id]
|
||||
})
|
||||
}));
|
||||
|
|
@ -10,7 +10,7 @@ import { host } from "./host";
|
|||
export async function BuildParams (data: { configPath: string; })
|
||||
{
|
||||
const validBrowser = await getBrowserPath({
|
||||
browserOrder: ['chrome', 'chromium']
|
||||
browserOrder: Bun.env.BROWSER_PRIORITY ? Bun.env.BROWSER_PRIORITY.split(',') as any : ['chrome', 'chromium']
|
||||
});
|
||||
|
||||
if (!validBrowser)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue