refactor: Removed the use of d.ts files to support SDK generation for public plugins

This commit is contained in:
Simeon Radivoev 2026-05-05 01:21:22 +03:00
parent 06b7e4074d
commit 2683d46b16
Signed by: simeonradivoev
GPG key ID: 7611A451D2A5D37A
114 changed files with 408 additions and 257 deletions

View file

@ -1,6 +1,11 @@
{
"name": "com.simeonradivoev.gameflow-deck",
"displayName": "Gameflow",
"author": {
"name": "Simeon Radivoev",
"email": "work@simeonradivoev.com",
"url": "https://simeonradivoev.com"
},
"version": "1.4.0",
"description": "Game Launcher",
"icon": "./src/mainview/assets/icon.svg",
@ -43,7 +48,8 @@
"download:chromium": "bun scripts/download-chromium.ts --out=./bin/chromium",
"download:nwjs": "bun scripts/download-nw.ts",
"build:audiosprites": "bun ./scripts/generate-audio-sprites.ts",
"tsc": "tsc --noEmit"
"tsc": "tsc --noEmit",
"build:sdk": "bun ./scripts/build-sdk.ts"
},
"dependencies": {
"7zip-bin": "^5.2.0",
@ -143,4 +149,4 @@
"vite-static-assets-plugin": "^1.2.2",
"vite-tsconfig-paths": "^6.1.1"
}
}
}

35
scripts/build-sdk.ts Normal file
View file

@ -0,0 +1,35 @@
import path from 'node:path';
import appPkg from '../package.json';
import sdkTsConfig from './sdk/sdk.tsconfig.json';
import sdkPackage from './sdk/package.json';
import { emptyDir } from 'fs-extra';
import { generateDtsBundle } from 'dts-bundle-generator';
async function generateApiDeclarations ()
{
const tmpConfigPath = "./scripts/sdk/sdk.tsconfig.json";
const outDir = path.join(path.dirname(tmpConfigPath), sdkTsConfig.compilerOptions.outDir);
await emptyDir(outDir);
const results = generateDtsBundle([{
filePath: './scripts/sdk/sdk.ts',
output: {
inlineDeclareGlobals: true,
sortNodes: true,
}
},], { preferredConfigPath: './scripts/sdk/sdk.tsconfig.json' });
await Bun.write('./dist-sdk/index.d.ts', results);
const pkg = {
...sdkPackage,
license: appPkg.license,
version: appPkg.version,
repository: appPkg.repository,
author: appPkg.author,
peerDependencies: appPkg.dependencies
};
await Bun.write(path.join(outDir, '..', 'package.json'), JSON.stringify(pkg, null, 3));
}
await generateApiDeclarations();

View file

@ -22,7 +22,7 @@ function spawnServer ()
stderr: 'inherit',
stdin: 'inherit',
signal: abortController.signal,
killSignal: 'SIGUSR1',
killSignal: 'SIGKILL',
ipc (message, subprocess, handle)
{
if (message === 'focus')
@ -91,7 +91,7 @@ if (!process.env.HEADLESS)
spawnBrowser()?.then(async e =>
{
if (!server) return;
server.kill("SIGUSR1");
abortController.abort();
await server.exited;
});
}

View file

@ -1,5 +1,4 @@
import audioSprite from 'audiosprite';
import { $ } from 'bun';
import path from 'node:path';
import { soundMap } from '../src/mainview/scripts/audio/audioConstants';

View file

@ -1,6 +1,5 @@
import { $ } from "bun";
const lockfile = Bun.argv[2] ?? "bun.lockb";
const output = Bun.argv[3] ?? ".config/flatpak/sources.gen.json";
const text = await $`bun ./bun.lockb --hash: 0000000000000000-0000000000000000-0000000000000000-0000000000000000`.text();

4
scripts/sdk/package.json Normal file
View file

@ -0,0 +1,4 @@
{
"name": "gameflow-sdk",
"types": "index.d.ts"
}

18
scripts/sdk/sdk.ts Normal file
View file

@ -0,0 +1,18 @@
import { SettingsType } from '@/shared/constants';
import Conf from 'conf';
import { AppEventMap } from '../../src/bun/types/types';
import EventEmitter from "node:events";
import { TaskQueue } from '@/bun/api/task-queue';
export * from '../../src/bun/types/types.schema';
export * from '../../src/bun/types/types';
export * from '../../src/bun/api/hooks/app';
export * from '../../src/shared/constants';
export * from '../../src/shared/types';
export * from '../../src/shared/utils';
export declare const config: Conf<SettingsType>;
export declare let events: EventEmitter<AppEventMap>;
export declare let taskQueue: TaskQueue;
export { };

View file

@ -0,0 +1,47 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": [
"ES2024"
],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"emitDeclarationOnly": true,
"declaration": true,
"strict": true,
"outDir": "../../dist-sdk/sdk",
"types": [
"node"
],
"paths": {
"@/*": [
"../../src/*"
],
"~/*": [
"../../*"
],
"@shared/*": [
"../../src/shared/*"
],
"@clients/*": [
"../../src/clients/*"
],
"@schema/*": [
"../../src/bun/api/schema/*"
],
"@queries/*": [
"../../src/mainview/scripts/queries/*"
]
}
},
"include": [
"../../src/bun/api/hooks",
"../../src/bun/types",
"../../src/shared"
]
}

View file

@ -24,6 +24,7 @@ import controls from './controls/controls';
import { RunAPIServer } from "./rpc";
import { RunBunServer } from "../server";
import ReloadPluginsJob from "./jobs/reload-plugins-job";
import { AppEventMap } from "../types/types";
export let config: Conf<SettingsType>;
export let customEmulators: Conf<Record<string, string>>;

View file

@ -1,6 +1,7 @@
import si from 'systeminformation';
import fs from 'node:fs';
import os from "node:os";
import { Drive } from '@/shared/types';
async function getAccess (path: string)
{

View file

@ -5,6 +5,7 @@ import z from "zod";
import path from 'node:path';
import { config, events, plugins } from "../app";
import { getLocalGame, updateLocalLastPlayed } from "../games/services/statusService";
import { SaveFileChange } from "@/shared/types";
// TODO: use the retroarch cores based on ES-DE
export const cores: Record<string, string> = {
@ -83,7 +84,7 @@ export default new Elysia({ prefix: '/emulatorjs' })
await plugins.hooks.games.postPlay.promise({
source,
id,
saveFolderPath: path.join(config.get('downloadPath'), "saves", "EMULATORJS"),
saveFolderSlots: { 'emulatorjs': { cwd: path.join(config.get('downloadPath'), "saves", "EMULATORJS") } },
gameInfo: { platformSlug: localGame?.platform.slug },
changedSaveFiles: [],
validChangedSaveFiles: changedSaveFiles,

View file

@ -1,5 +1,6 @@
import Elysia, { status } from "elysia";
import { plugins } from "../app";
import { FrontEndCollection } from "@/shared/types";
export default new Elysia()
.get('/collections', async () =>

View file

@ -22,6 +22,7 @@ import { LaunchGameJob } from "../jobs/launch-game-job";
import { cores } from "../emulatorjs/emulatorjs";
import { findEmulatorPluginIntegration } from "../store/services/emulatorsService";
import { ImportJob } from "../jobs/import-job";
import { EmulatorSourceEntryType, EmulatorSystem, FrontEndFilterLists, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailedEmulator, FrontEndGameTypeWithIds, FrontEndId, GameLookup } from "@/shared/types";
// A custom jimp that supports webp
const Jimp = createJimp({

View file

@ -4,6 +4,7 @@ import { and, count, eq, getTableColumns, not, notExists, or } from "drizzle-orm
import { config, db, plugins } from "../app";
import * as schema from "@schema/app";
import { findPlatform } from "./services/utils";
import { FrontEndPlatformType } from "@/shared/types";
export default new Elysia()
.get('/platforms', async () =>

View file

@ -6,6 +6,7 @@ import { config, taskQueue } from '../../app';
import { LaunchGameJob } from '../../jobs/launch-game-job';
import { getStoreEmulatorPackage } from '../../store/services/gamesService';
import { getOrCachedScoopPackage } from '../../store/services/emulatorsService';
import { CommandEntry, EmulatorSourceEntryType, FrontEndId } from '@/shared/types';
export async function launchCommand (validCommand: CommandEntry, id: FrontEndId, source?: string, sourceId?: string)
{

View file

@ -10,6 +10,7 @@ import { LaunchGameJob } from "../../jobs/launch-game-job";
import * as appSchema from "@schema/app";
import { DownloadSourceSchema, RPC_URL } from "@/shared/constants";
import { host } from "@/bun/utils/host";
import { CommandEntry, FrontEndId, GameLookup, GameStatusType, LocalDownloadFileEntry } from "@/shared/types";
export class CommandSearchError extends Error
{

View file

@ -8,6 +8,7 @@ import { RPC_URL } from "@shared/constants";
import { hashFile } from "@/bun/utils";
import { host } from "@/bun/utils/host";
import * as emulatorSchema from "@schema/emulators";
import { DownloadFileEntry, FrontEndGameType, FrontEndGameTypeDetailed, GameLookup, LocalDownloadFileEntry, LocalGameMetadata } from "@/shared/types";
export async function calculateSize (installPath: string | null)
{

View file

@ -1,9 +1,9 @@
import { AuthHooks } from "./auth";
import { EmulatorHooks } from "./emulators";
import { GameHooks } from "./games";
import { StoreHooks } from "./store";
import AuthHooks from "./auth";
import EmulatorHooks from "./emulators";
import GameHooks from "./games";
import StoreHooks from "./store";
export class GameflowHooks
export default class GameflowHooks
{
games = new GameHooks();
emulators = new EmulatorHooks();

View file

@ -1,6 +1,7 @@
import { DownloadFileEntry } from "@/shared/types";
import { AsyncSeriesHook } from "tapable";
export class AuthHooks
export default class AuthHooks
{
loginComplete = new AsyncSeriesHook<[ctx: {
service: string;

View file

@ -1,16 +1,8 @@
import { EmulatorDownloadInfoType, EmulatorPackageType } from "@/shared/constants";
import { EmulatorPostInstallContext } from "@/bun/types/types";
import { DownloadFileEntry, EmulatorSourceEntryType, EmulatorSystem } from "@/shared/types";
import { AsyncSeriesBailHook, AsyncSeriesHook } from "tapable";
interface EmulatorPostInstallContext
{
emulator: string;
emulatorPackage?: EmulatorPackageType;
path: string;
update: boolean;
info: EmulatorDownloadInfoType;
}
export class EmulatorHooks
export default class EmulatorHooks
{
fetchBiosDownload = new AsyncSeriesBailHook<[ctx: {
emulator: string;

View file

@ -1,7 +1,8 @@
import { EmulatorPackageType, GameListFilterType } from '@/shared/constants';
import { SyncBailHook, AsyncSeriesHook, AsyncSeriesBailHook } from 'tapable';
import { CommandEntry, DownloadInfo, EmulatorSourceEntryType, EmulatorSupport, EmulatorSystem, FrontEndCollection, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeWithIds, FrontEndId, FrontEndPlatformType, GameLookup, SaveFileChange, SaveSlots } from '@/shared/types';
import { SyncBailHook, AsyncSeriesHook, AsyncSeriesBailHook, Hook } from 'tapable';
export class GameHooks
export default class GameHooks
{
buildLaunchCommands = new AsyncSeriesBailHook<[ctx: {
source: string | null;
@ -121,7 +122,7 @@ export class GameHooks
postPlay = new AsyncSeriesHook<[ctx: {
source: string,
id: string;
saveFolderSlots?: Record<string, { cwd: string; }>;
saveFolderSlots?: SaveSlots;
changedSaveFiles: { subPath: string, cwd: string; }[],
validChangedSaveFiles: Record<string, SaveFileChange>,
command: CommandEntry;

View file

@ -1,7 +1,8 @@
import { EmulatorDownloadInfoType } from "@/shared/constants";
import { FrontEndEmulator, FrontEndEmulatorDetailed, FrontEndGameTypeDetailed } from "@/shared/types";
import { AsyncSeriesBailHook, AsyncSeriesHook } from "tapable";
export class StoreHooks
export default class StoreHooks
{
fetchFeaturedGames = new AsyncSeriesHook<[ctx: { games: FrontEndGameTypeDetailed[]; }]>(['ctx']);
fetchEmulators = new AsyncSeriesHook<[ctx: { emulators: FrontEndEmulator[]; search?: string; }]>(['ctx']);

View file

@ -12,6 +12,7 @@ import { simulateProgress } from "@/bun/utils";
import { path7za } from "7zip-bin";
import { getEmulatorDownload, getEmulatorPath } from "../store/services/emulatorsService";
import { $ } from "bun";
import { EmulatorSourceEntryType } from "@/shared/types";
type EmulatorDownloadStates = "download" | "extract";

View file

@ -4,6 +4,7 @@ import { createLocalGame } from "../games/services/utils";
import { IJob, JobContext } from "../task-queue";
import * as schema from "@schema/app";
import z from "zod";
import { GameLookup } from "@/shared/types";
export class ImportJob implements IJob<z.infer<typeof ImportJob.dataSchema>, string>
{

View file

@ -11,6 +11,7 @@ import { ensureDir, move } from "fs-extra";
import { path7za } from "7zip-bin";
import StreamZip from 'node-stream-zip';
import { which } from "bun";
import { DownloadInfo } from "@/shared/types";
interface JobConfig
{

View file

@ -1,13 +1,13 @@
import z from "zod";
import { IJob, JobContext } from "../task-queue";
import { ActiveGameSchema, ActiveGameType } from "@/bun/types/typesc.schema";
import { ActiveGameSchema, ActiveGameType } from "@/bun/types/types.schema";
import { config, db, events, plugins } from "../app";
import * as appSchema from "@schema/app";
import { eq } from "drizzle-orm";
import { spawn } from 'node:child_process';
import fs from "node:fs/promises";
import { updateLocalLastPlayed } from "../games/services/statusService";
import { getErrorMessage } from "@/bun/utils";
import { CommandEntry, FrontEndId, SaveSlots } from "@/shared/types";
export class LaunchGameJob implements IJob<z.infer<typeof LaunchGameJob.dataSchema>, string>
{

View file

@ -1,7 +1,6 @@
import z from "zod";
import { IJob, JobContext } from "../task-queue";
import { cleanPromise, cleanup, events, plugins } from "../app";
import fs from 'fs/promises';
import { events } from "../app";
import { Downloader } from "@/bun/utils/downloader";
import path from 'node:path';
import os from "node:os";

View file

@ -1,4 +1,5 @@
import { FrontendNotification } from '@/shared/types';
import { events } from './app';
export default function buildNotificationsStream ()

View file

@ -1,4 +1,4 @@
import { PluginContextType, PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import desc from './package.json';
import path from 'node:path';
import { config } from "@/bun/api/app";

View file

@ -1,6 +1,6 @@
import { config } from "@/bun/api/app";
import { PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import path from 'node:path';
import desc from './package.json';
import { ensureDir } from "fs-extra";

View file

@ -1,11 +1,12 @@
import { config } from "@/bun/api/app";
import { PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import defaultConfig from './PCSX2.ini' with { type: 'file' };
import path from 'node:path';
import { ensureDir } from "fs-extra";
import desc from './package.json';
import ini from 'ini';
import { EmulatorCapabilities } from "@/shared/types";
export default class PCSX2Integration implements PluginType
{

View file

@ -1,4 +1,4 @@
import { PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import desc from './package.json';
import { config } from "@/bun/api/app";
import configFilePathWin32 from './win32/ppsspp.ini' with { type: 'file' };
@ -11,6 +11,7 @@ import { ensureDir } from "fs-extra";
import { homedir } from "node:os";
import ini from 'ini';
import fs from 'node:fs/promises';
import { EmulatorCapabilities } from "@/shared/types";
export default class PPSSPPIntegration implements PluginType
{

View file

@ -1,4 +1,4 @@
import { PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import desc from './package.json';
import { config } from "@/bun/api/app";
import path from "node:path";

View file

@ -1,5 +1,4 @@
import { join } from "path";
import { platform } from "os";
const SECTOR_SIZE = 0x800;
const MAGIC = "MICROSOFT*XBOX*MEDIA";

View file

@ -1,6 +1,6 @@
import { PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import desc from './package.json';
import { GameflowHooks } from "@/bun/api/hooks/app";
import GameflowHooks from "@/bun/api/hooks/app";
import { config } from "@/bun/api/app";
import path from "node:path";
import { ensureDir } from "fs-extra";

View file

@ -1,4 +1,4 @@
import { PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import desc from './package.json';
import { config, customEmulators, db, emulatorsDb } from "@/bun/api/app";
import * as emulatorSchema from '@schema/emulators';
@ -13,6 +13,7 @@ import { findStoreEmulatorExec } from "@/bun/api/games/services/launchGameServic
import { which } from "bun";
import os from 'node:os';
import { getLocalGameMatch } from "@/bun/api/games/services/utils";
import { CommandEntry, EmulatorSourceEntryType } from "@/shared/types";
export default class IgdbIntegration implements PluginType
{

View file

@ -1,13 +1,13 @@
import { PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import desc from './package.json';
import { config, db, events } from "@/bun/api/app";
import path, { dirname } from 'node:path';
import path from 'node:path';
import unzip from 'unzip-stream';
import { chmodSync, ensureDir } from "fs-extra";
import { ensureDir } from "fs-extra";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
import fs from 'node:fs/promises';
import { randomUUIDv7, sleep } from "bun";
import { randomUUIDv7 } from "bun";
import z from "zod";
import { createInterface } from "node:readline";
import { getLocalGameMatch } from "@/bun/api/games/services/utils";

View file

@ -1,9 +1,10 @@
import { PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import desc from './package.json';
import secrets from "@/bun/api/secrets";
import PQueue from 'p-queue';
import * as igdb from '@phalcode/ts-igdb-client';
import { checkLoginAndRefreshTwitch } from "@/bun/api/auth";
import { GameLookup } from "@/shared/types";
export default class IgdbIntegration implements PluginType
{
@ -80,7 +81,7 @@ export default class IgdbIntegration implements PluginType
first_release_date: g.first_release_date ? g.first_release_date * 1000 : undefined,
average_rating: g.rating ?? undefined,
keywords: g.keywords?.map(k => k.name!) ?? [],
igdb_id: g.id,
igdb_id: g.id ?? undefined,
platforms: g.platforms?.map(p => ({ id: p.id!, name: p.abbreviation, displayName: p.name!, slug: p.slug! })) ?? [],
slug: g.slug
};

View file

@ -1,6 +1,6 @@
import { PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import desc from './package.json';
import { DetailedRomSchema, getCollectionApiCollectionsIdGet, getCollectionsApiCollectionsGet, getCurrentUserApiUsersMeGet, getPlatformApiPlatformsIdGet, getPlatformFirmwareApiFirmwareGet, getPlatformsApiPlatformsGet, getRomApiRomsIdGet, getRomByMetadataProviderApiRomsByMetadataProviderGet, getRomContentApiRomsIdContentFileNameGet, getRomFiltersApiRomsFiltersGet, getRomsApiRomsGet, getSavesSummaryApiSavesSummaryGet, PlatformSchema, SimpleRomSchema, updateRomUserApiRomsIdPropsPut } from "@/clients/romm";
import { config, events } from "@/bun/api/app";
@ -14,6 +14,7 @@ import { client } from "@/clients/romm/client.gen";
import { validateGameSource } from "@/bun/api/games/services/statusService";
import z from "zod";
import { checkLoginAndRefreshRomm } from "@/bun/api/auth";
import { DownloadFileEntry, DownloadInfo, FrontEndCollection, FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeDetailedAchievement, FrontEndGameTypeWithIds, FrontEndPlatformType } from "@/shared/types";
const SettingsSchema = z.object({
savesSync: z.boolean().default(false).describe("Experimental save sync support")

View file

@ -12,6 +12,7 @@ import { shuffleInPlace } from "@/bun/utils";
import mustache from "mustache";
import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService";
import fs from "node:fs/promises";
import { CommandEntry, EmulatorSourceEntryType, EmulatorSystem, FrontEndEmulator, FrontEndFilterSets, FrontEndGameType, FrontEndGameTypeDetailed, SaveFileChange } from "@/shared/types";
export async function getStoreGames (gamesManifest: any[], filter?: { limit?: number; offset?: number; })
{

View file

@ -1,4 +1,4 @@
import { PluginLoadingContextType, PluginType } from "@/bun/types/typesc.schema";
import { PluginLoadingContextType, PluginType } from "@/bun/types/types.schema";
import desc from './package.json';
import path, { } from 'node:path';
import { buildStoreFrontendEmulatorSystems, getAllStoreEmulatorPackages, getStoreEmulatorPackage, getStoreFolder } from "@/bun/api/store/services/gamesService";
@ -12,6 +12,7 @@ import { getSourceGameDetailed } from "@/bun/api/games/services/utils";
import UpdateStoreJob from "@/bun/api/jobs/update-store";
import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService";
import { buildFilters, buildLaunchCommand, buildSaves, convertStoreEmulatorToFrontend, convertStoreToFrontend, convertStoreToFrontendDetailed, getExistingStoreEmulatorDownload, getShuffledStoreGames, getStoreGame, getValidDownloads } from "./services";
import { DownloadInfo, FrontEndEmulatorDetailed, FrontEndGameTypeWithIds } from "@/shared/types";
export default class RommIntegration implements PluginType
{

View file

@ -1,10 +1,10 @@
import { GameflowHooks } from "../hooks/app";
import { PluginDescriptionType, PluginLoadingContextType, PluginType } from "../../types/typesc.schema";
import GameflowHooks from "../hooks/app";
import { PluginDescriptionType, PluginLoadingContextType, PluginType } from "../../types/types.schema";
import { config } from "../app";
import Conf from "conf";
import projectPackage from '~/package.json';
import z from "zod";
import { EventEmitter } from "node:stream";
import { PluginSourceType } from "@/shared/types";
export const pluginZodRegistry = z.registry<{
requiresRestart?: boolean;

View file

@ -3,6 +3,7 @@ import { plugins, taskQueue } from "../app";
import z from "zod";
import { toggleElementInConfig } from "@/bun/utils";
import ReloadPluginsJob from "../jobs/reload-plugins-job";
import { FrontendPlugin } from "@/shared/types";
export default new Elysia({ prefix: '/plugins' })
.get('/', async () =>

View file

@ -11,11 +11,15 @@ import igdb from './builtin/sources/com.simeonradivoev.gameflow.igdb/package.jso
import store from './builtin/sources/com.simeonradivoev.gameflow.store/package.json';
import es from './builtin/launchers/com.simeonradivoev.gameflow.es/package.json';
import rclone from './builtin/other/com.simeonradivoev.gameflow.rclone/package.json';
import { PluginDescriptionSchema, PluginDescriptionType, PluginSchema } from "@/bun/types/typesc.schema";
import { PluginDescriptionSchema, PluginDescriptionType, PluginSchema } from "@/bun/types/types.schema";
import path from 'node:path';
import { getStoreRootFolder } from "../store/services/gamesService";
type PluginEntry = PluginDescriptionType & { load: () => Promise<any>; };
export default async function register (pluginManager: PluginManager)
{
const plugins: (PluginDescriptionType & { main: string; load: () => Promise<any>; })[] = [
const plugins: PluginEntry[] = [
{ ...pcsx2, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2') },
{ ...ppsspp, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp') },
{ ...dolphin, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin') },
@ -29,6 +33,33 @@ export default async function register (pluginManager: PluginManager)
{ ...rclone, load: () => import('./builtin/other/com.simeonradivoev.gameflow.rclone/rclone') },
];
const storePackageFile = path.join(getStoreRootFolder(), 'package.json');
const storePackage = await Bun.file(storePackageFile).json();
if (storePackage.dependencies)
{
const storePlugins = await Promise.all(Object.keys(storePackage.dependencies).map(async p =>
{
const pluginPath = path.join(getStoreRootFolder(), 'node_modules', p);
const pluginPackageFile = Bun.file(path.join(pluginPath, 'package.json'));
if (await pluginPackageFile.exists())
{
const pluginPackage = await PluginDescriptionSchema.safeParseAsync(await pluginPackageFile.json());
if (pluginPackage.success)
{
const mainPath = path.join(pluginPath, pluginPackage.data.main);
if (await Bun.file(mainPath).exists())
{
const entry: PluginEntry = { ...pluginPackage.data, load: () => import(mainPath) };
return entry;
}
}
}
}));
plugins.push(...storePlugins.filter(p => !!p));
}
await Promise.all(plugins.filter(p =>
{
if (process.env.PLUGIN_WHITELIST && !process.env.PLUGIN_WHITELIST.includes(p.name))

View file

@ -1,3 +1,4 @@
import { LocalGameMetadata } from "@/shared/types";
import { sql, relations } from "drizzle-orm";
import { integer, text, sqliteTable, blob } from "drizzle-orm/sqlite-core";

View file

@ -7,6 +7,7 @@ import { cores } from '../emulatorjs/emulatorjs';
import { SERVER_URL } from '@/shared/constants';
import { host } from '@/bun/utils/host';
import { findEmulatorPluginIntegration } from '../store/services/emulatorsService';
import { EmulatorSourceEntryType, FrontEndEmulator } from '@/shared/types';
/**
* Get emulators based on local games. Only the ones we probably need.

View file

@ -2,6 +2,7 @@ import { EmulatorDownloadInfoType, EmulatorPackageType, ScoopPackageSchema } fro
import { config, plugins } from "../../app";
import { getOrCached, getOrCachedGithubRelease } from "../../cache";
import path from "node:path";
import { EmulatorSourceEntryType, EmulatorSupport } from "@/shared/types";
export function findEmulatorPluginIntegration (name: string, validSources: (EmulatorSourceEntryType | undefined)[]): EmulatorSupport[]
{

View file

@ -1,14 +1,10 @@
import { EmulatorPackageSchema, EmulatorPackageType, GithubManifestSchema, StoreGameSchema } from "@/shared/constants";
import { CACHE_KEYS, getOrCached } from "../../cache";
import { EmulatorPackageSchema, EmulatorPackageType } from "@/shared/constants";
import { and, eq, or } from "drizzle-orm";
import { config, emulatorsDb } from '../../app';
import path from "node:path";
import fs from 'node:fs/promises';
import * as emulatorSchema from '@schema/emulators';
import { shuffleInPlace } from "@/bun/utils";
import { Glob } from "bun";
import { EmulatorSystem } from "@/shared/types";
export function getStoreRootFolder ()
{

View file

@ -13,6 +13,7 @@ import { getStoreFolder } from "./services/gamesService";
import { EmulatorDownloadJob } from "../jobs/emulator-download-job";
import { BiosDownloadJob } from "../jobs/bios-download-job";
import { findEmulatorPluginIntegration, getEmulatorPath } from "./services/emulatorsService";
import { EmulatorSourceEntryType, FrontEndEmulator, FrontEndGameTypeDetailed } from "@/shared/types";
export const store = new Elysia({ prefix: '/api/store' })
.get('/emulators', async ({ query }) =>

View file

@ -16,6 +16,7 @@ import ReloadPluginsJob from "./jobs/reload-plugins-job";
import { semver } from "bun";
import { getOrCachedGithubRelease } from "./cache";
import SelfUpdateJob from "./jobs/self-update-job";
import { DownloadsDrive } from "@/shared/types";
async function checkUpdate (force?: boolean)
{

View file

@ -1,3 +1,4 @@
import { JobStatus } from '@/shared/types';
import EventEmitter from 'node:events';
import z from 'zod';

19
src/bun/types/helpers.d.ts vendored Normal file
View file

@ -0,0 +1,19 @@
declare module '*.bat' {
const content: string;
export default content;
}
declare module '*.sh' {
const content: string;
export default content;
}
declare module '*.ini' {
const content: string;
export default content;
}
declare module '*.bin' {
const content: string;
export default content;
}

View file

@ -1,42 +0,0 @@
declare interface ObjectConstructor
{
/**
* Groups members of an iterable according to the return value of the passed callback.
* @param items An iterable.
* @param keySelector A callback which will be invoked for each item in items.
*/
groupBy<K extends PropertyKey, T> (
items: Iterable<T>,
keySelector: (item: T, index: number) => K,
): Partial<Record<K, T[]>>;
}
declare interface MapConstructor
{
/**
* Groups members of an iterable according to the return value of the passed callback.
* @param items An iterable.
* @param keySelector A callback which will be invoked for each item in items.
*/
groupBy<K, T> (
items: Iterable<T>,
keySelector: (item: T, index: number) => K,
): Map<K, T[]>;
}
declare interface AppEventMap
{
exitapp: [];
notification: [FrontendNotification];
focus: [];
}
declare module '*.bat' {
const content: string;
export default content;
}
declare module '*.sh' {
const content: string;
export default content;
}

View file

@ -1,8 +1,7 @@
import z from "zod";
import { GameflowHooks } from "../api/hooks/app";
import GameflowHooks from "../api/hooks/app";
import Conf from "conf";
import { $ZodRegistry } from "zod/v4/core";
import EventEmitter from "node:events";
export const PluginContextSchema = z.object({
hooks: z.instanceof(GameflowHooks)
@ -22,6 +21,7 @@ export const PluginDescriptionSchema = z.object({
icon: z.url().optional(),
keywords: z.array(z.string()).optional(),
category: z.string().default("other"),
main: z.string(),
canDisable: z.boolean().default(true).optional()
});

18
src/bun/types/types.ts Normal file
View file

@ -0,0 +1,18 @@
import { EmulatorDownloadInfoType, EmulatorPackageType } from "@/shared/constants";
import { FrontendNotification } from "@/shared/types";
export interface AppEventMap
{
exitapp: [];
notification: [FrontendNotification];
focus: [];
}
export interface EmulatorPostInstallContext
{
emulator: string;
emulatorPackage?: EmulatorPackageType;
path: string;
update: boolean;
info: EmulatorDownloadInfoType;
}

View file

@ -4,6 +4,7 @@ import { SettingsType } from '@/shared/constants';
import { config } from './api/app';
import fs from 'node:fs/promises';
import packageDef from '~/package.json';
import { KeysWithValueAssignableTo } from '@/shared/types';
export function checkRunning (pid: number)
{

View file

@ -5,6 +5,7 @@ import fs from 'node:fs/promises';
import { createWriteStream } from "node:fs";
import { config, jar } from "../api/app";
import { moveAllFiles } from "../utils";
import { DownloadFileEntry } from "@/shared/types";
export interface ProgressStats
{

View file

@ -35,18 +35,6 @@ interface BrowserResult
source: GetBrowserSource;
}
const PLATFORM_MAP: Record<string, string> = {
linux: "linux",
win32: "windows",
darwin: 'macos'
};
const ARCH_MAP: Record<string, Record<string, string>> = {
linux: { x64: "x86_64", arm64: "arm64" },
darwin: { x64: "x86_64", arm64: "arm64" },
win32: { x64: "x64", arm64: "arm64" },
};
/** The expected binary path per platform after extraction */
async function getBundledBinaryPath (outDir: string, version: string, platform: string, arch: string): Promise<string | undefined>
{

View file

@ -1,5 +1,5 @@
import { doesFocusableExist, FocusDetails, getCurrentFocusKey } from "@noriginmedia/norigin-spatial-navigation";
import { useEffect, useLayoutEffect } from "react";
import { useEffect } from "react";
export function AutoFocus (data: {
parentKey?: string;

View file

@ -1,4 +1,4 @@
import { FocusDetails, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import classNames from "classnames";
import { JSX } from "react";
import { twMerge } from "tailwind-merge";

View file

@ -3,7 +3,6 @@ import
FocusContext,
useFocusable,
} from "@noriginmedia/norigin-spatial-navigation";
import { GameMeta } from "../../shared/constants";
import CardElement, { GameCardParams } from "./CardElement";
import { JSX } from "react";
import { twMerge } from "tailwind-merge";

View file

@ -1,6 +1,6 @@
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { Home, TriangleAlert } from "lucide-react";
import { GamePadButtonCode, useShortcutContext, useShortcuts } from "../scripts/shortcuts";
import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts";
import { FloatingShortcuts } from "./Shortcuts";
import { Button } from "./options/Button";
import { useEffect } from "react";

View file

@ -86,7 +86,6 @@ function List (data: {
function NewFolderInput (data: { id: string, name: string | undefined, setName: (name: string) => void; className?: string; })
{
const inputRef = useRef<HTMLInputElement>(null);
const { control } = useActiveControl();
const { ref, focused, focusSelf } = useFocusable({
focusKey: data.id,
onEnterPress: () => inputRef.current?.focus(),

View file

@ -4,6 +4,7 @@ import { FileQuestion, HardDrive, Store } from "lucide-react";
import { JSX } from "react";
import { FOCUS_KEYS } from "../scripts/types";
import { useRouter } from "@tanstack/react-router";
import { FrontEndGameType, FrontEndId } from "@/shared/types";
export default function FrontEndGameCard (data: { index: number, game: FrontEndGameType; showSource?: boolean; } & FocusParams & InteractParams)
{

View file

@ -1,12 +1,13 @@
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
import { useSuspenseQuery } from "@tanstack/react-query";
import { GameMetaExtra, CardList } from "./CardList";
import { DefaultRommStaleTime, GameListFilterType, RPC_URL } from "@shared/constants";
import { useNavigate } from "@tanstack/react-router";
import { HardDrive } from "lucide-react";
import { JSX, Ref, useContext, useEffect } from "react";
import { JSX, useContext } from "react";
import { useLocalSetting } from "../scripts/utils";
import { AnimatedBackgroundContext } from "../scripts/contexts";
import { allGamesQuery } from "@queries/romm";
import { FrontEndGameType, FrontEndId } from "@/shared/types";
export interface GameListParams extends FocusParams
{
@ -95,7 +96,7 @@ export function GameList (data: GameListParams)
const previewUrls = g.path_covers.map(c =>
{
const url = new URL(`${RPC_URL(__HOST__)}${c}`);
const url = c.startsWith("http") ? new URL(c) : new URL(`${RPC_URL(__HOST__)}${c}`);
url.searchParams.delete('ts');
return url;
});
@ -103,7 +104,7 @@ export function GameList (data: GameListParams)
let platformUrl: URL | undefined = undefined;
if (g.path_platform_cover)
{
platformUrl = new URL(`${RPC_URL(__HOST__)}${g.path_platform_cover}`);
platformUrl = g.path_platform_cover.startsWith("http") ? new URL(g.path_platform_cover) : new URL(`${RPC_URL(__HOST__)}${g.path_platform_cover}`);
platformUrl.searchParams.set('width', "64");
}

View file

@ -40,7 +40,7 @@ const KeyElements: Record<string, JSX.Element> = {
'←': <ArrowLeft />,
'→': <ArrowRight />,
};
const DZ = 0.22, TH = 0.85, NS = 'http://www.w3.org/2000/svg';
const DZ = 0.22;
function ang (x: number, y: number)
{

View file

@ -19,7 +19,6 @@ function SearchInput (data: {
onSubmit: (search: string | undefined) => void;
} & FocusParams)
{
const { control } = useActiveControl();
const { ref, focusKey } = useFocusable({
onBlur: () => inputRef.current?.blur(),
onFocus: (l, p, d) =>

View file

@ -1,6 +1,6 @@
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { Home, TriangleAlert } from "lucide-react";
import { GamePadButtonCode, useShortcutContext, useShortcuts } from "../scripts/shortcuts";
import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts";
import { Button } from "./options/Button";
import { useEffect } from "react";
import { useRouter } from "@tanstack/react-router";

View file

@ -1,4 +1,5 @@
import { RPC_URL } from "@/shared/constants";
import { FrontendNotification } from "@/shared/types";
import { Clock, CloudUpload, Save } from "lucide-react";
import { useEffect } from "react";
import toast, { ToastOptions } from "react-hot-toast";

View file

@ -3,8 +3,8 @@ import { useNavigate } from "@tanstack/react-router";
import { DefaultRommStaleTime, RPC_URL } from "@shared/constants";
import { CardList, GameMetaExtra } from "./CardList";
import { rommApi } from "../scripts/clientApi";
import { JSX, useMemo, useState } from "react";
import { Gamepad2, HardDrive } from "lucide-react";
import { JSX, useMemo } from "react";
import { HardDrive } from "lucide-react";
import { mobileCheck } from "../scripts/utils";
import { twMerge } from "tailwind-merge";
import placeholder from '../assets/256x256.png?url';

View file

@ -6,6 +6,7 @@ import { useFocusable, FocusContext } from "@noriginmedia/norigin-spatial-naviga
import { ArrowDownAz, ClockArrowDown, CalendarArrowDown, Rocket, HardDrive, SortDesc, User, Drama, FunnelX, Store } from "lucide-react";
import { sourceIconMap } from "./Constants";
import { useContextDialog, ContextList, DialogEntry } from "./ContextDialog";
import { FrontEndFilterLists } from "@/shared/types";
function FilterButton (data: {
id: string,

View file

@ -1,4 +1,4 @@
import { Ref, RefObject } from 'react';
import { Ref } from 'react';
import './dots.css';
export default function DotsLoading (data: { ref?: Ref<any>; })

View file

@ -1,4 +1,5 @@
import { FrontEndGameTypeDetailed, FrontEndGameTypeDetailedAchievement } from "@/shared/types";
import { useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { Medal } from "lucide-react";

View file

@ -10,6 +10,7 @@ import ActionButton from "./ActionButton";
import { useLocalStorage } from "usehooks-ts";
import FocusTooltip from "../FocusTooltip";
import { useBlocker, useNavigate, useRouter } from "@tanstack/react-router";
import { FrontEndGameTypeDetailed } from "@/shared/types";
function AchievementsInfo (data: { game: FrontEndGameTypeDetailed; } & InteractParams)
{

View file

@ -2,7 +2,7 @@ import { scrollIntoViewHandler } from "@/mainview/scripts/utils";
import { RPC_URL } from "@/shared/constants";
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import classNames from "classnames";
import { Clock, CloudBackup, CloudDownload, CloudUpload, Gamepad2, HardDrive, Store, TriangleAlert } from "lucide-react";
import { Clock, CloudDownload, CloudUpload, HardDrive, TriangleAlert } from "lucide-react";
import prettyBytes from "pretty-bytes";
import { JSX } from "react";
import ActionButtons from "./ActionButtons";
@ -10,6 +10,7 @@ import prettyMilliseconds from 'pretty-ms';
import { useQuery } from "@tanstack/react-query";
import { validateSourceQuery } from "@/mainview/scripts/queries/romm";
import { sourceIconMap } from "../Constants";
import { FrontEndGameTypeDetailed } from "@/shared/types";
export function DetailElement (data: { icon: JSX.Element; tooltip?: string | null, children?: any | any[]; })
{

View file

@ -1,4 +1,4 @@
import { gameLookup } from "@/mainview/scripts/queries/romm";
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { useQuery } from "@tanstack/react-query";
import { Check, Search } from "lucide-react";
@ -6,6 +6,8 @@ import HeaderSearchField from "../HeaderSearchField";
import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts";
import { scrollIntoViewHandler } from "@/mainview/scripts/utils";
import { FOCUS_KEYS } from "@/mainview/scripts/types";
import { FrontEndId, GameLookup } from "@/shared/types";
import { gameLookupQuery } from "@/mainview/scripts/queries/romm";
function Result (data: {
match: GameLookup;
@ -54,7 +56,7 @@ function SearchField (data: { setSearch: (search: string | undefined) => void; s
</div>;
}
export default function GameLookup (data: {
export default function GameLookupElement (data: {
search: string | undefined,
setSearch: (search: string | undefined) => void,
onSelect: (match: GameLookup) => void;
@ -62,7 +64,7 @@ export default function GameLookup (data: {
selected?: FrontEndId;
})
{
const { data: lookups, isFetching } = useQuery({ ...gameLookup(data.search), staleTime: 1000 * 60 * 60 });
const { data: lookups, isFetching } = useQuery({ ...gameLookupQuery(data.search), staleTime: 1000 * 60 * 60 });
return <div>
<SearchField setSearch={data.setSearch} search={data.search} />

View file

@ -11,6 +11,7 @@ import ActionButton from "./ActionButton";
import { useRouter } from "@tanstack/react-router";
import { DownloadSourceType } from "@/shared/constants";
import { GamePadButtonCode, Shortcut, useShortcuts } from "@/mainview/scripts/shortcuts";
import { CommandEntry, FrontEndGameTypeDetailed } from "@/shared/types";
export default function MainActions (data: { game?: FrontEndGameTypeDetailed, source: string, id: string; })
{

View file

@ -3,6 +3,7 @@ import { PathSettingsOptionBase, PathSettingsOptionParams } from "./PathSettings
import { useMutation, useQuery } from "@tanstack/react-query";
import { changeDownloadsMutation, getSettingQuery } from "@queries/settings";
import { SettingsType } from "@/shared/constants";
import { KeysWithValueAssignableTo } from "@/shared/types";
export default function DownloadDirectoryOption (data: PathSettingsOptionParams & { id: KeysWithValueAssignableTo<SettingsType, string>; })
{

View file

@ -5,7 +5,6 @@ import { useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { CheckIcon, X } from "lucide-react";
import { oneShot } from "@/mainview/scripts/audio/audio";
import { GamePadButtonCode, Shortcut, useShortcuts } from "@/mainview/scripts/shortcuts";
import useActiveControl from "@/mainview/scripts/gamepads";
export function OptionInput (data: {
name: string;
@ -35,7 +34,6 @@ export function OptionInput (data: {
}
oneShot('click');
};
const { control } = useActiveControl();
const [inputFocused, setInputFocused] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const { ref, focusKey } = useFocusable({

View file

@ -9,6 +9,7 @@ import { ContextDialog } from "../ContextDialog";
import FilePicker from "../FilePicker";
import { setFocus } from "@noriginmedia/norigin-spatial-navigation";
import { getSettingQuery, setSettingMutation } from "@queries/settings";
import { KeysWithValueAssignableTo } from "@/shared/types";
export interface PathSettingsOptionParams
{

View file

@ -4,6 +4,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import { OptionSpace } from "./OptionSpace";
import { getSettingQuery, setSettingMutation } from "@queries/settings";
import { OptionDropdown } from "./OptionDropdown";
import { KeysWithValueAssignableTo } from "@/shared/types";
export function SettingsDropdown (data: {
label: string;

View file

@ -4,6 +4,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import { OptionSpace } from "./OptionSpace";
import { OptionInput } from "./OptionInput";
import { getSettingQuery, setSettingMutation } from "@queries/settings";
import { KeysWithValueAssignableTo } from "@/shared/types";
export function SettingsOption (data: {
label: string;

View file

@ -12,6 +12,7 @@ import { StoreEmulatorCard } from "./StoreEmulatorCard";
import { FOCUS_KEYS } from "@/mainview/scripts/types";
import Carousel from "../Carousel";
import { useRouter } from "@tanstack/react-router";
import { FrontEndEmulator } from "@/shared/types";
function SeeAllCard (data: { id: string; onAction: () => void; onFocus?: (details: { node: HTMLElement, instant?: boolean; }) => void; })
{

View file

@ -10,6 +10,7 @@ import FrontEndGameCard from "../FrontEndGameCard";
import { FOCUS_KEYS } from "@/mainview/scripts/types";
import Carousel from "../Carousel";
import { twMerge } from "tailwind-merge";
import { FrontEndGameType, FrontEndId } from "@/shared/types";
export function GamesSection (data: {
games?: FrontEndGameType[];

View file

@ -8,6 +8,7 @@ import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts";
import { RPC_URL } from "@/shared/constants";
import { FOCUS_KEYS } from "@/mainview/scripts/types";
import { oneShot } from "@/mainview/scripts/audio/audio";
import { FrontEndEmulator } from "@/shared/types";
// ── Single missing-emulator card ───────────────────────────────────────────
interface MissingCardProps

View file

@ -10,6 +10,7 @@ import { JSX } from "react";
import { oneShot } from "@/mainview/scripts/audio/audio";
import { useQuery } from "@tanstack/react-query";
import { getUpdateInfoForEmulator } from "@/mainview/scripts/queries/store";
import { FrontEndEmulator } from "@/shared/types";
export const emulatorStatusIcons: Record<string, JSX.Element> = {
store: <Store />,

View file

@ -5,7 +5,7 @@ import z from 'zod';
import { RefObject, useEffect, useRef, useState } from 'react';
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
import { ButtonStyle } from '../components/options/Button';
import { CloudDownload, DoorOpen, RefreshCw, Save, Undo } from 'lucide-react';
import { CloudDownload, DoorOpen, RefreshCw, Undo } from 'lucide-react';
import { GamePadButtonCode, useShortcuts } from '../scripts/shortcuts';
import { FloatingShortcuts } from '../components/Shortcuts';
import { useEventListener } from 'usehooks-ts';

View file

@ -6,8 +6,8 @@ import { Calendar, Folder, Gamepad2, Image, Info, TriangleAlert, Trophy } from "
import { HeaderUI, StickyHeaderUI } from "../../components/Header";
import { AnimatedBackground } from "../../components/AnimatedBackground";
import { useQuery } from "@tanstack/react-query";
import Shortcuts, { FloatingShortcuts } from "../../components/Shortcuts";
import { GamePadButtonCode, useShortcutContext, useShortcuts } from "@/mainview/scripts/shortcuts";
import { FloatingShortcuts } from "../../components/Shortcuts";
import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts";
import Screenshots from "@/mainview/components/Screenshots";
import { HandleGoBack, scrollIntoViewHandler, useOnNavigateBack } from "@/mainview/scripts/utils";
import { FilterUI } from "@/mainview/components/Filters";
@ -23,8 +23,8 @@ import { GamesSection } from "@/mainview/components/store/GamesSection";
import Details from "@/mainview/components/game/Details";
import { AutoFocus } from "@/mainview/components/AutoFocus";
import SelectMenu from "@/mainview/components/SelectMenu";
import { en } from "zod/v4/locales";
import { IGDBIcon } from "@/mainview/scripts/brandIcons";
import { FrontEndGameTypeDetailed } from "@/shared/types";
export const Route = createFileRoute("/game/$source/$id")({
loader: async ({ params, context }) =>

View file

@ -1,6 +1,6 @@
import { AutoFocus } from '@/mainview/components/AutoFocus';
import { OptionElement } from '@/mainview/components/ContextDialog';
import GameLookup from '@/mainview/components/game/GameLookup';
import GameLookupElement from '@/mainview/components/game/GameLookup';
import { StickyHeaderUI } from '@/mainview/components/Header';
import LoadingScreen from '@/mainview/components/LoadingScreen';
import { Button } from '@/mainview/components/options/Button';
@ -208,7 +208,7 @@ function Lookup ()
navigate({ to: '/game/add', search: { ...state, selectedGame: { source, id }, platformId: undefined, search, step: 2 }, replace: true });
oneShot('openGeneric');
};
return <GameLookup
return <GameLookupElement
showPlatforms
selected={state.selectedGame}
search={search}

View file

@ -1,6 +1,6 @@
import { AnimatedBackground } from '@/mainview/components/AnimatedBackground';
import { AutoFocus } from '@/mainview/components/AutoFocus';
import GameLookup from '@/mainview/components/game/GameLookup';
import GameLookupElement from '@/mainview/components/game/GameLookup';
import { StickyHeaderUI } from '@/mainview/components/Header';
import { FloatingShortcuts } from '@/mainview/components/Shortcuts';
import { customUpdateMutation, gameInvalidationQuery, gameQuery } from '@/mainview/scripts/queries/romm';
@ -20,7 +20,6 @@ function RouteComponent ()
{
const { source, id } = Route.useParams();
const [search, setSearch] = useState<string | undefined>(undefined);
const navigate = useNavigate();
const router = useRouter();
const { data: game } = useQuery(gameQuery(source, id));
@ -47,7 +46,7 @@ function RouteComponent ()
<FocusContext value={focusKey}>
<div className='flex flex-col z-10 overflow-y-scroll'>
<StickyHeaderUI ref={ref} />
<GameLookup
<GameLookupElement
search={search}
setSearch={setSearch}
onSelect={l =>

View file

@ -3,23 +3,17 @@ import
{
Gamepad2,
Settings,
MessageSquare,
Image,
Search,
Power,
OctagonAlert,
Maximize,
Store,
LayoutGrid,
PlusCircle,
Plus,
LucideIcon,
} from "lucide-react";
import
{
createFileRoute,
PathParamOptions,
ToPathOption,
useRouter,
} from "@tanstack/react-router";
import { useMutation, useQueryClient } from "@tanstack/react-query";
@ -41,11 +35,11 @@ import SaveScroll from "../components/SaveScroll";
import { ErrorBoundary, useErrorBoundary } from "react-error-boundary";
import { twMerge } from "tailwind-merge";
import { PlatformsList } from "../components/PlatformsList";
import { GamePadButtonCode, useShortcutContext, useShortcuts } from "../scripts/shortcuts";
import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts";
import z from "zod";
import CollectionList from "../components/CollectionList";
import { zodValidator } from '@tanstack/zod-adapter';
import { mobileCheck, scrollIntoNearestParent, scrollIntoViewHandler, useDragScroll } from "../scripts/utils";
import { mobileCheck, scrollIntoViewHandler, useDragScroll } from "../scripts/utils";
import { AnimatedBackgroundContext } from "../scripts/contexts";
import Carousel from "../components/Carousel";
import { closeMutation } from "@queries/system";
@ -56,6 +50,7 @@ import SelectMenu from "../components/SelectMenu";
import HeaderSearchField from "../components/HeaderSearchField";
import CardElement from "../components/CardElement";
import { Router } from "..";
import { FrontEndId } from "@/shared/types";
export const Route = createFileRoute("/")({
component: ConsoleHomeUI,

View file

@ -1,12 +1,11 @@
import { AnimatedBackground } from '@/mainview/components/AnimatedBackground';
import { createFileRoute, useBlocker, useRouter } from '@tanstack/react-router';
import DotsLoading from '../components/backgrounds/dots';
import { GamePadButtonCode, useShortcutContext, useShortcuts } from '../scripts/shortcuts';
import { GamePadButtonCode, useShortcuts } from '../scripts/shortcuts';
import { useFocusable } from '@noriginmedia/norigin-spatial-navigation';
import Shortcuts, { FloatingShortcuts } from '../components/Shortcuts';
import { FloatingShortcuts } from '../components/Shortcuts';
import { useJobStatus } from '../scripts/utils';
import { useEffect, useRef } from 'react';
import { rommApi } from '../scripts/clientApi';
import { useRef } from 'react';
export const Route = createFileRoute('/launcher/$source/$id')({
component: RouteComponent,

View file

@ -1,11 +1,9 @@
import { Button } from '@/mainview/components/options/Button';
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
import { checkUpdateMutation, hasUpdateQuery, systemInfoQuery, updateMutation } from '@queries/system';
import { useMutation, useQuery } from '@tanstack/react-query';
import { systemInfoQuery } from '@queries/system';
import { useQuery } from '@tanstack/react-query';
import { createFileRoute } from '@tanstack/react-router';
import { ArrowUpCircle, CircleFadingArrowUp, RefreshCcw } from 'lucide-react';
import prettyBytes from 'pretty-bytes';
export const Route = createFileRoute('/settings/about')({

View file

@ -13,6 +13,7 @@ import { systemApi } from '@/mainview/scripts/clientApi';
import useActiveControl from '@/mainview/scripts/gamepads';
import { changeDownloadsMutation } from '@queries/settings';
import { downloadDrivesQuery } from '@/mainview/scripts/queries/system';
import { DownloadsDrive } from '@/shared/types';
export const Route = createFileRoute('/settings/directories')({
component: RouteComponent,

View file

@ -20,6 +20,7 @@ import { FOCUS_KEYS } from '@/mainview/scripts/types';
import { scrollIntoNearestParent, scrollIntoViewHandler, useDragScroll } from '@/mainview/scripts/utils';
import { SettingsOption } from '@/mainview/components/options/SettingsOption';
import { SettingsDropdown } from '@/mainview/components/options/SettingsDropdown';
import { FrontEndEmulator } from '@/shared/types';
export const Route = createFileRoute('/settings/emulators')({
component: RouteComponent,

View file

@ -5,15 +5,15 @@ import { OptionDropdown } from '@/mainview/components/options/OptionDropdown';
import { OptionInput } from '@/mainview/components/options/OptionInput';
import { OptionSpace } from '@/mainview/components/options/OptionSpace';
import { RoundButton } from '@/mainview/components/RoundButton';
import { getAllPluginsQuery, getPluginDetailsQuery } from '@/mainview/scripts/queries/plugins';
import { getPluginDetailsQuery } from '@/mainview/scripts/queries/plugins';
import { getPluginActionsQuery, getPluginSettingQuery, getPluginSettingsDefinitionQuery, pluginActionMutation, setPluginSettingMutation } from '@/mainview/scripts/queries/settings';
import { GamePadButtonCode, useShortcuts } from '@/mainview/scripts/shortcuts';
import { scrollIntoViewHandler } from '@/mainview/scripts/utils';
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
import { useMutation, useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { JSONSchema7 } from 'json-schema';
import { ArrowLeft, CirclePlay, Play, Settings2, SettingsIcon } from 'lucide-react';
import { ArrowLeft, CirclePlay, Settings2 } from 'lucide-react';
import toast from 'react-hot-toast';
export const Route = createFileRoute('/settings/plugin/$source')({
component: RouteComponent,

View file

@ -1,11 +1,11 @@
import { AutoFocus } from '@/mainview/components/AutoFocus';
import { pluginCategoryIcons, pluginCategoryPriorities } from '@/mainview/components/Constants';
import { Button } from '@/mainview/components/options/Button';
import { OptionInput } from '@/mainview/components/options/OptionInput';
import { OptionSpace } from '@/mainview/components/options/OptionSpace';
import { RoundButton } from '@/mainview/components/RoundButton';
import { enablePluginMutation, getAllPluginsQuery } from '@/mainview/scripts/queries/plugins';
import { GamePadButtonCode, Shortcut } from '@/mainview/scripts/shortcuts';
import { FrontendPlugin } from '@/shared/types';
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
import { useMutation, useQuery } from '@tanstack/react-query';
import { createFileRoute, useNavigate } from '@tanstack/react-router';

View file

@ -7,7 +7,6 @@ import
{
Outlet,
createFileRoute,
useMatch,
useMatchRoute,
useRouter,
useRouterState,
@ -29,8 +28,8 @@ import { JSX, useMemo } from "react";
import { twMerge } from "tailwind-merge";
import z from "zod";
import { SettingsSchema } from "../../../shared/constants";
import { GamePadButtonCode, useShortcutContext, useShortcuts } from "@/mainview/scripts/shortcuts";
import Shortcuts, { FloatingShortcuts } from "@/mainview/components/Shortcuts";
import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts";
import Shortcuts from "@/mainview/components/Shortcuts";
import { HandleGoBack } from "@/mainview/scripts/utils";
import { AutoFocus } from "@/mainview/components/AutoFocus";
import { oneShot } from "@/mainview/scripts/audio/audio";

View file

@ -5,12 +5,12 @@ import
FocusContext,
} from "@noriginmedia/norigin-spatial-navigation";
import { createFileRoute, useNavigate, useRouter } from "@tanstack/react-router";
import { GamePadButtonCode, useShortcutContext, useShortcuts } from "@/mainview/scripts/shortcuts";
import Shortcuts, { FloatingShortcuts } from "@/mainview/components/Shortcuts";
import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts";
import { FloatingShortcuts } from "@/mainview/components/Shortcuts";
import { AnimatedBackground } from "@/mainview/components/AnimatedBackground";
import { rommApi, systemApi } from "@/mainview/scripts/clientApi";
import { Button } from "@/mainview/components/options/Button";
import { ChevronDown, CircleFadingArrowUp, CloudUpload, Cpu, Download, Fullscreen, Gamepad2, Info, Monitor, Puzzle, Save, Settings, Settings2, Terminal, Trash2, TriangleAlert, WandSparkles } from "lucide-react";
import { ChevronDown, CircleFadingArrowUp, CloudUpload, Cpu, Download, Fullscreen, Gamepad2, Info, Monitor, Puzzle, Settings, Settings2, Terminal, Trash2, TriangleAlert, WandSparkles } from "lucide-react";
import { ContextList, DialogEntry, useContextDialog } from "@/mainview/components/ContextDialog";
import { RPC_URL } from "@/shared/constants";
import Screenshots from "@/mainview/components/Screenshots";
@ -29,6 +29,7 @@ import FocusTooltip from "@/mainview/components/FocusTooltip";
import { AutoFocus } from "@/mainview/components/AutoFocus";
import { FilterUI } from "@/mainview/components/Filters";
import Markdown from "react-markdown";
import { FrontEndEmulatorDetailed } from "@/shared/types";
export const Route = createFileRoute('/store/details/emulator/$id')({
component: RouteComponent,

View file

@ -1,6 +1,6 @@
import { createFileRoute, useSearch } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
import { Joystick } from 'lucide-react';
import { useContext, useEffect } from 'react';
import { FocusContext, getCurrentFocusKey, useFocusable } from '@noriginmedia/norigin-spatial-navigation';

Some files were not shown because too many files have changed in this diff Show more