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", "name": "com.simeonradivoev.gameflow-deck",
"displayName": "Gameflow", "displayName": "Gameflow",
"author": {
"name": "Simeon Radivoev",
"email": "work@simeonradivoev.com",
"url": "https://simeonradivoev.com"
},
"version": "1.4.0", "version": "1.4.0",
"description": "Game Launcher", "description": "Game Launcher",
"icon": "./src/mainview/assets/icon.svg", "icon": "./src/mainview/assets/icon.svg",
@ -43,7 +48,8 @@
"download:chromium": "bun scripts/download-chromium.ts --out=./bin/chromium", "download:chromium": "bun scripts/download-chromium.ts --out=./bin/chromium",
"download:nwjs": "bun scripts/download-nw.ts", "download:nwjs": "bun scripts/download-nw.ts",
"build:audiosprites": "bun ./scripts/generate-audio-sprites.ts", "build:audiosprites": "bun ./scripts/generate-audio-sprites.ts",
"tsc": "tsc --noEmit" "tsc": "tsc --noEmit",
"build:sdk": "bun ./scripts/build-sdk.ts"
}, },
"dependencies": { "dependencies": {
"7zip-bin": "^5.2.0", "7zip-bin": "^5.2.0",
@ -143,4 +149,4 @@
"vite-static-assets-plugin": "^1.2.2", "vite-static-assets-plugin": "^1.2.2",
"vite-tsconfig-paths": "^6.1.1" "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', stderr: 'inherit',
stdin: 'inherit', stdin: 'inherit',
signal: abortController.signal, signal: abortController.signal,
killSignal: 'SIGUSR1', killSignal: 'SIGKILL',
ipc (message, subprocess, handle) ipc (message, subprocess, handle)
{ {
if (message === 'focus') if (message === 'focus')
@ -91,7 +91,7 @@ if (!process.env.HEADLESS)
spawnBrowser()?.then(async e => spawnBrowser()?.then(async e =>
{ {
if (!server) return; if (!server) return;
server.kill("SIGUSR1"); abortController.abort();
await server.exited; await server.exited;
}); });
} }

View file

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

View file

@ -1,6 +1,5 @@
import { $ } from "bun"; import { $ } from "bun";
const lockfile = Bun.argv[2] ?? "bun.lockb";
const output = Bun.argv[3] ?? ".config/flatpak/sources.gen.json"; const output = Bun.argv[3] ?? ".config/flatpak/sources.gen.json";
const text = await $`bun ./bun.lockb --hash: 0000000000000000-0000000000000000-0000000000000000-0000000000000000`.text(); 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 { RunAPIServer } from "./rpc";
import { RunBunServer } from "../server"; import { RunBunServer } from "../server";
import ReloadPluginsJob from "./jobs/reload-plugins-job"; import ReloadPluginsJob from "./jobs/reload-plugins-job";
import { AppEventMap } from "../types/types";
export let config: Conf<SettingsType>; export let config: Conf<SettingsType>;
export let customEmulators: Conf<Record<string, string>>; export let customEmulators: Conf<Record<string, string>>;

View file

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

View file

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

View file

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

View file

@ -22,6 +22,7 @@ import { LaunchGameJob } from "../jobs/launch-game-job";
import { cores } from "../emulatorjs/emulatorjs"; import { cores } from "../emulatorjs/emulatorjs";
import { findEmulatorPluginIntegration } from "../store/services/emulatorsService"; import { findEmulatorPluginIntegration } from "../store/services/emulatorsService";
import { ImportJob } from "../jobs/import-job"; 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 // A custom jimp that supports webp
const Jimp = createJimp({ 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 { config, db, plugins } from "../app";
import * as schema from "@schema/app"; import * as schema from "@schema/app";
import { findPlatform } from "./services/utils"; import { findPlatform } from "./services/utils";
import { FrontEndPlatformType } from "@/shared/types";
export default new Elysia() export default new Elysia()
.get('/platforms', async () => .get('/platforms', async () =>

View file

@ -6,6 +6,7 @@ import { config, taskQueue } from '../../app';
import { LaunchGameJob } from '../../jobs/launch-game-job'; import { LaunchGameJob } from '../../jobs/launch-game-job';
import { getStoreEmulatorPackage } from '../../store/services/gamesService'; import { getStoreEmulatorPackage } from '../../store/services/gamesService';
import { getOrCachedScoopPackage } from '../../store/services/emulatorsService'; 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) 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 * as appSchema from "@schema/app";
import { DownloadSourceSchema, RPC_URL } from "@/shared/constants"; import { DownloadSourceSchema, RPC_URL } from "@/shared/constants";
import { host } from "@/bun/utils/host"; import { host } from "@/bun/utils/host";
import { CommandEntry, FrontEndId, GameLookup, GameStatusType, LocalDownloadFileEntry } from "@/shared/types";
export class CommandSearchError extends Error export class CommandSearchError extends Error
{ {

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import { DownloadFileEntry } from "@/shared/types";
import { AsyncSeriesHook } from "tapable"; import { AsyncSeriesHook } from "tapable";
export class AuthHooks export default class AuthHooks
{ {
loginComplete = new AsyncSeriesHook<[ctx: { loginComplete = new AsyncSeriesHook<[ctx: {
service: string; 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"; import { AsyncSeriesBailHook, AsyncSeriesHook } from "tapable";
interface EmulatorPostInstallContext export default class EmulatorHooks
{
emulator: string;
emulatorPackage?: EmulatorPackageType;
path: string;
update: boolean;
info: EmulatorDownloadInfoType;
}
export class EmulatorHooks
{ {
fetchBiosDownload = new AsyncSeriesBailHook<[ctx: { fetchBiosDownload = new AsyncSeriesBailHook<[ctx: {
emulator: string; emulator: string;

View file

@ -1,7 +1,8 @@
import { EmulatorPackageType, GameListFilterType } from '@/shared/constants'; 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: { buildLaunchCommands = new AsyncSeriesBailHook<[ctx: {
source: string | null; source: string | null;
@ -121,7 +122,7 @@ export class GameHooks
postPlay = new AsyncSeriesHook<[ctx: { postPlay = new AsyncSeriesHook<[ctx: {
source: string, source: string,
id: string; id: string;
saveFolderSlots?: Record<string, { cwd: string; }>; saveFolderSlots?: SaveSlots;
changedSaveFiles: { subPath: string, cwd: string; }[], changedSaveFiles: { subPath: string, cwd: string; }[],
validChangedSaveFiles: Record<string, SaveFileChange>, validChangedSaveFiles: Record<string, SaveFileChange>,
command: CommandEntry; command: CommandEntry;

View file

@ -1,7 +1,8 @@
import { EmulatorDownloadInfoType } from "@/shared/constants"; import { EmulatorDownloadInfoType } from "@/shared/constants";
import { FrontEndEmulator, FrontEndEmulatorDetailed, FrontEndGameTypeDetailed } from "@/shared/types";
import { AsyncSeriesBailHook, AsyncSeriesHook } from "tapable"; import { AsyncSeriesBailHook, AsyncSeriesHook } from "tapable";
export class StoreHooks export default class StoreHooks
{ {
fetchFeaturedGames = new AsyncSeriesHook<[ctx: { games: FrontEndGameTypeDetailed[]; }]>(['ctx']); fetchFeaturedGames = new AsyncSeriesHook<[ctx: { games: FrontEndGameTypeDetailed[]; }]>(['ctx']);
fetchEmulators = new AsyncSeriesHook<[ctx: { emulators: FrontEndEmulator[]; search?: string; }]>(['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 { path7za } from "7zip-bin";
import { getEmulatorDownload, getEmulatorPath } from "../store/services/emulatorsService"; import { getEmulatorDownload, getEmulatorPath } from "../store/services/emulatorsService";
import { $ } from "bun"; import { $ } from "bun";
import { EmulatorSourceEntryType } from "@/shared/types";
type EmulatorDownloadStates = "download" | "extract"; type EmulatorDownloadStates = "download" | "extract";

View file

@ -4,6 +4,7 @@ import { createLocalGame } from "../games/services/utils";
import { IJob, JobContext } from "../task-queue"; import { IJob, JobContext } from "../task-queue";
import * as schema from "@schema/app"; import * as schema from "@schema/app";
import z from "zod"; import z from "zod";
import { GameLookup } from "@/shared/types";
export class ImportJob implements IJob<z.infer<typeof ImportJob.dataSchema>, string> 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 { path7za } from "7zip-bin";
import StreamZip from 'node-stream-zip'; import StreamZip from 'node-stream-zip';
import { which } from "bun"; import { which } from "bun";
import { DownloadInfo } from "@/shared/types";
interface JobConfig interface JobConfig
{ {

View file

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

View file

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

View file

@ -1,4 +1,5 @@
import { FrontendNotification } from '@/shared/types';
import { events } from './app'; import { events } from './app';
export default function buildNotificationsStream () 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 desc from './package.json';
import path from 'node:path'; import path from 'node:path';
import { config } from "@/bun/api/app"; import { config } from "@/bun/api/app";

View file

@ -1,6 +1,6 @@
import { config } from "@/bun/api/app"; 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 path from 'node:path';
import desc from './package.json'; import desc from './package.json';
import { ensureDir } from "fs-extra"; import { ensureDir } from "fs-extra";

View file

@ -1,11 +1,12 @@
import { config } from "@/bun/api/app"; 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 defaultConfig from './PCSX2.ini' with { type: 'file' };
import path from 'node:path'; import path from 'node:path';
import { ensureDir } from "fs-extra"; import { ensureDir } from "fs-extra";
import desc from './package.json'; import desc from './package.json';
import ini from 'ini'; import ini from 'ini';
import { EmulatorCapabilities } from "@/shared/types";
export default class PCSX2Integration implements PluginType 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 desc from './package.json';
import { config } from "@/bun/api/app"; import { config } from "@/bun/api/app";
import configFilePathWin32 from './win32/ppsspp.ini' with { type: 'file' }; import configFilePathWin32 from './win32/ppsspp.ini' with { type: 'file' };
@ -11,6 +11,7 @@ import { ensureDir } from "fs-extra";
import { homedir } from "node:os"; import { homedir } from "node:os";
import ini from 'ini'; import ini from 'ini';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import { EmulatorCapabilities } from "@/shared/types";
export default class PPSSPPIntegration implements PluginType 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 desc from './package.json';
import { config } from "@/bun/api/app"; import { config } from "@/bun/api/app";
import path from "node:path"; import path from "node:path";

View file

@ -1,5 +1,4 @@
import { join } from "path"; import { join } from "path";
import { platform } from "os";
const SECTOR_SIZE = 0x800; const SECTOR_SIZE = 0x800;
const MAGIC = "MICROSOFT*XBOX*MEDIA"; 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 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 { config } from "@/bun/api/app";
import path from "node:path"; import path from "node:path";
import { ensureDir } from "fs-extra"; 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 desc from './package.json';
import { config, customEmulators, db, emulatorsDb } from "@/bun/api/app"; import { config, customEmulators, db, emulatorsDb } from "@/bun/api/app";
import * as emulatorSchema from '@schema/emulators'; import * as emulatorSchema from '@schema/emulators';
@ -13,6 +13,7 @@ import { findStoreEmulatorExec } from "@/bun/api/games/services/launchGameServic
import { which } from "bun"; import { which } from "bun";
import os from 'node:os'; import os from 'node:os';
import { getLocalGameMatch } from "@/bun/api/games/services/utils"; import { getLocalGameMatch } from "@/bun/api/games/services/utils";
import { CommandEntry, EmulatorSourceEntryType } from "@/shared/types";
export default class IgdbIntegration implements PluginType 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 desc from './package.json';
import { config, db, events } from "@/bun/api/app"; 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 unzip from 'unzip-stream';
import { chmodSync, ensureDir } from "fs-extra"; import { ensureDir } from "fs-extra";
import { Readable } from "node:stream"; import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises"; import { pipeline } from "node:stream/promises";
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import { randomUUIDv7, sleep } from "bun"; import { randomUUIDv7 } from "bun";
import z from "zod"; import z from "zod";
import { createInterface } from "node:readline"; import { createInterface } from "node:readline";
import { getLocalGameMatch } from "@/bun/api/games/services/utils"; 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 desc from './package.json';
import secrets from "@/bun/api/secrets"; import secrets from "@/bun/api/secrets";
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import * as igdb from '@phalcode/ts-igdb-client'; import * as igdb from '@phalcode/ts-igdb-client';
import { checkLoginAndRefreshTwitch } from "@/bun/api/auth"; import { checkLoginAndRefreshTwitch } from "@/bun/api/auth";
import { GameLookup } from "@/shared/types";
export default class IgdbIntegration implements PluginType 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, first_release_date: g.first_release_date ? g.first_release_date * 1000 : undefined,
average_rating: g.rating ?? undefined, average_rating: g.rating ?? undefined,
keywords: g.keywords?.map(k => k.name!) ?? [], 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! })) ?? [], platforms: g.platforms?.map(p => ({ id: p.id!, name: p.abbreviation, displayName: p.name!, slug: p.slug! })) ?? [],
slug: g.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 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 { 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"; 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 { validateGameSource } from "@/bun/api/games/services/statusService";
import z from "zod"; import z from "zod";
import { checkLoginAndRefreshRomm } from "@/bun/api/auth"; import { checkLoginAndRefreshRomm } from "@/bun/api/auth";
import { DownloadFileEntry, DownloadInfo, FrontEndCollection, FrontEndGameType, FrontEndGameTypeDetailed, FrontEndGameTypeDetailedAchievement, FrontEndGameTypeWithIds, FrontEndPlatformType } from "@/shared/types";
const SettingsSchema = z.object({ const SettingsSchema = z.object({
savesSync: z.boolean().default(false).describe("Experimental save sync support") 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 mustache from "mustache";
import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService"; import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService";
import fs from "node:fs/promises"; 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; }) 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 desc from './package.json';
import path, { } from 'node:path'; import path, { } from 'node:path';
import { buildStoreFrontendEmulatorSystems, getAllStoreEmulatorPackages, getStoreEmulatorPackage, getStoreFolder } from "@/bun/api/store/services/gamesService"; 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 UpdateStoreJob from "@/bun/api/jobs/update-store";
import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService"; import { getEmulatorDownload, getEmulatorPath } from "@/bun/api/store/services/emulatorsService";
import { buildFilters, buildLaunchCommand, buildSaves, convertStoreEmulatorToFrontend, convertStoreToFrontend, convertStoreToFrontendDetailed, getExistingStoreEmulatorDownload, getShuffledStoreGames, getStoreGame, getValidDownloads } from "./services"; 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 export default class RommIntegration implements PluginType
{ {

View file

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

View file

@ -3,6 +3,7 @@ import { plugins, taskQueue } from "../app";
import z from "zod"; import z from "zod";
import { toggleElementInConfig } from "@/bun/utils"; import { toggleElementInConfig } from "@/bun/utils";
import ReloadPluginsJob from "../jobs/reload-plugins-job"; import ReloadPluginsJob from "../jobs/reload-plugins-job";
import { FrontendPlugin } from "@/shared/types";
export default new Elysia({ prefix: '/plugins' }) export default new Elysia({ prefix: '/plugins' })
.get('/', async () => .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 store from './builtin/sources/com.simeonradivoev.gameflow.store/package.json';
import es from './builtin/launchers/com.simeonradivoev.gameflow.es/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 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) 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') }, { ...pcsx2, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2') },
{ ...ppsspp, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp') }, { ...ppsspp, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp') },
{ ...dolphin, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin') }, { ...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') }, { ...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 => await Promise.all(plugins.filter(p =>
{ {
if (process.env.PLUGIN_WHITELIST && !process.env.PLUGIN_WHITELIST.includes(p.name)) 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 { sql, relations } from "drizzle-orm";
import { integer, text, sqliteTable, blob } from "drizzle-orm/sqlite-core"; 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 { SERVER_URL } from '@/shared/constants';
import { host } from '@/bun/utils/host'; import { host } from '@/bun/utils/host';
import { findEmulatorPluginIntegration } from '../store/services/emulatorsService'; import { findEmulatorPluginIntegration } from '../store/services/emulatorsService';
import { EmulatorSourceEntryType, FrontEndEmulator } from '@/shared/types';
/** /**
* Get emulators based on local games. Only the ones we probably need. * 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 { config, plugins } from "../../app";
import { getOrCached, getOrCachedGithubRelease } from "../../cache"; import { getOrCached, getOrCachedGithubRelease } from "../../cache";
import path from "node:path"; import path from "node:path";
import { EmulatorSourceEntryType, EmulatorSupport } from "@/shared/types";
export function findEmulatorPluginIntegration (name: string, validSources: (EmulatorSourceEntryType | undefined)[]): EmulatorSupport[] export function findEmulatorPluginIntegration (name: string, validSources: (EmulatorSourceEntryType | undefined)[]): EmulatorSupport[]
{ {

View file

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

View file

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

View file

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

View file

@ -1,3 +1,4 @@
import { JobStatus } from '@/shared/types';
import EventEmitter from 'node:events'; import EventEmitter from 'node:events';
import z from 'zod'; 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 z from "zod";
import { GameflowHooks } from "../api/hooks/app"; import GameflowHooks from "../api/hooks/app";
import Conf from "conf"; import Conf from "conf";
import { $ZodRegistry } from "zod/v4/core"; import { $ZodRegistry } from "zod/v4/core";
import EventEmitter from "node:events";
export const PluginContextSchema = z.object({ export const PluginContextSchema = z.object({
hooks: z.instanceof(GameflowHooks) hooks: z.instanceof(GameflowHooks)
@ -22,6 +21,7 @@ export const PluginDescriptionSchema = z.object({
icon: z.url().optional(), icon: z.url().optional(),
keywords: z.array(z.string()).optional(), keywords: z.array(z.string()).optional(),
category: z.string().default("other"), category: z.string().default("other"),
main: z.string(),
canDisable: z.boolean().default(true).optional() 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 { config } from './api/app';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import packageDef from '~/package.json'; import packageDef from '~/package.json';
import { KeysWithValueAssignableTo } from '@/shared/types';
export function checkRunning (pid: number) export function checkRunning (pid: number)
{ {

View file

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

View file

@ -35,18 +35,6 @@ interface BrowserResult
source: GetBrowserSource; 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 */ /** The expected binary path per platform after extraction */
async function getBundledBinaryPath (outDir: string, version: string, platform: string, arch: string): Promise<string | undefined> 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 { doesFocusableExist, FocusDetails, getCurrentFocusKey } from "@noriginmedia/norigin-spatial-navigation";
import { useEffect, useLayoutEffect } from "react"; import { useEffect } from "react";
export function AutoFocus (data: { export function AutoFocus (data: {
parentKey?: string; 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 classNames from "classnames";
import { JSX } from "react"; import { JSX } from "react";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";

View file

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

View file

@ -1,6 +1,6 @@
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation"; import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { Home, TriangleAlert } from "lucide-react"; import { Home, TriangleAlert } from "lucide-react";
import { GamePadButtonCode, useShortcutContext, useShortcuts } from "../scripts/shortcuts"; import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts";
import { FloatingShortcuts } from "./Shortcuts"; import { FloatingShortcuts } from "./Shortcuts";
import { Button } from "./options/Button"; import { Button } from "./options/Button";
import { useEffect } from "react"; 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; }) function NewFolderInput (data: { id: string, name: string | undefined, setName: (name: string) => void; className?: string; })
{ {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const { control } = useActiveControl();
const { ref, focused, focusSelf } = useFocusable({ const { ref, focused, focusSelf } = useFocusable({
focusKey: data.id, focusKey: data.id,
onEnterPress: () => inputRef.current?.focus(), onEnterPress: () => inputRef.current?.focus(),

View file

@ -4,6 +4,7 @@ import { FileQuestion, HardDrive, Store } from "lucide-react";
import { JSX } from "react"; import { JSX } from "react";
import { FOCUS_KEYS } from "../scripts/types"; import { FOCUS_KEYS } from "../scripts/types";
import { useRouter } from "@tanstack/react-router"; 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) 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 { GameMetaExtra, CardList } from "./CardList";
import { DefaultRommStaleTime, GameListFilterType, RPC_URL } from "@shared/constants"; import { DefaultRommStaleTime, GameListFilterType, RPC_URL } from "@shared/constants";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
import { HardDrive } from "lucide-react"; import { HardDrive } from "lucide-react";
import { JSX, Ref, useContext, useEffect } from "react"; import { JSX, useContext } from "react";
import { useLocalSetting } from "../scripts/utils"; import { useLocalSetting } from "../scripts/utils";
import { AnimatedBackgroundContext } from "../scripts/contexts"; import { AnimatedBackgroundContext } from "../scripts/contexts";
import { allGamesQuery } from "@queries/romm"; import { allGamesQuery } from "@queries/romm";
import { FrontEndGameType, FrontEndId } from "@/shared/types";
export interface GameListParams extends FocusParams export interface GameListParams extends FocusParams
{ {
@ -95,7 +96,7 @@ export function GameList (data: GameListParams)
const previewUrls = g.path_covers.map(c => 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'); url.searchParams.delete('ts');
return url; return url;
}); });
@ -103,7 +104,7 @@ export function GameList (data: GameListParams)
let platformUrl: URL | undefined = undefined; let platformUrl: URL | undefined = undefined;
if (g.path_platform_cover) 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"); platformUrl.searchParams.set('width', "64");
} }

View file

@ -40,7 +40,7 @@ const KeyElements: Record<string, JSX.Element> = {
'←': <ArrowLeft />, '←': <ArrowLeft />,
'→': <ArrowRight />, '→': <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) function ang (x: number, y: number)
{ {

View file

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

View file

@ -1,6 +1,6 @@
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation"; import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { Home, TriangleAlert } from "lucide-react"; 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 { Button } from "./options/Button";
import { useEffect } from "react"; import { useEffect } from "react";
import { useRouter } from "@tanstack/react-router"; import { useRouter } from "@tanstack/react-router";

View file

@ -1,4 +1,5 @@
import { RPC_URL } from "@/shared/constants"; import { RPC_URL } from "@/shared/constants";
import { FrontendNotification } from "@/shared/types";
import { Clock, CloudUpload, Save } from "lucide-react"; import { Clock, CloudUpload, Save } from "lucide-react";
import { useEffect } from "react"; import { useEffect } from "react";
import toast, { ToastOptions } from "react-hot-toast"; 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 { DefaultRommStaleTime, RPC_URL } from "@shared/constants";
import { CardList, GameMetaExtra } from "./CardList"; import { CardList, GameMetaExtra } from "./CardList";
import { rommApi } from "../scripts/clientApi"; import { rommApi } from "../scripts/clientApi";
import { JSX, useMemo, useState } from "react"; import { JSX, useMemo } from "react";
import { Gamepad2, HardDrive } from "lucide-react"; import { HardDrive } from "lucide-react";
import { mobileCheck } from "../scripts/utils"; import { mobileCheck } from "../scripts/utils";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import placeholder from '../assets/256x256.png?url'; 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 { ArrowDownAz, ClockArrowDown, CalendarArrowDown, Rocket, HardDrive, SortDesc, User, Drama, FunnelX, Store } from "lucide-react";
import { sourceIconMap } from "./Constants"; import { sourceIconMap } from "./Constants";
import { useContextDialog, ContextList, DialogEntry } from "./ContextDialog"; import { useContextDialog, ContextList, DialogEntry } from "./ContextDialog";
import { FrontEndFilterLists } from "@/shared/types";
function FilterButton (data: { function FilterButton (data: {
id: string, id: string,

View file

@ -1,4 +1,4 @@
import { Ref, RefObject } from 'react'; import { Ref } from 'react';
import './dots.css'; import './dots.css';
export default function DotsLoading (data: { ref?: Ref<any>; }) 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 { useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { Medal } from "lucide-react"; import { Medal } from "lucide-react";

View file

@ -10,6 +10,7 @@ import ActionButton from "./ActionButton";
import { useLocalStorage } from "usehooks-ts"; import { useLocalStorage } from "usehooks-ts";
import FocusTooltip from "../FocusTooltip"; import FocusTooltip from "../FocusTooltip";
import { useBlocker, useNavigate, useRouter } from "@tanstack/react-router"; import { useBlocker, useNavigate, useRouter } from "@tanstack/react-router";
import { FrontEndGameTypeDetailed } from "@/shared/types";
function AchievementsInfo (data: { game: FrontEndGameTypeDetailed; } & InteractParams) 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 { RPC_URL } from "@/shared/constants";
import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation"; import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import classNames from "classnames"; 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 prettyBytes from "pretty-bytes";
import { JSX } from "react"; import { JSX } from "react";
import ActionButtons from "./ActionButtons"; import ActionButtons from "./ActionButtons";
@ -10,6 +10,7 @@ import prettyMilliseconds from 'pretty-ms';
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { validateSourceQuery } from "@/mainview/scripts/queries/romm"; import { validateSourceQuery } from "@/mainview/scripts/queries/romm";
import { sourceIconMap } from "../Constants"; import { sourceIconMap } from "../Constants";
import { FrontEndGameTypeDetailed } from "@/shared/types";
export function DetailElement (data: { icon: JSX.Element; tooltip?: string | null, children?: any | any[]; }) 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 { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { Check, Search } from "lucide-react"; import { Check, Search } from "lucide-react";
@ -6,6 +6,8 @@ import HeaderSearchField from "../HeaderSearchField";
import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts"; import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts";
import { scrollIntoViewHandler } from "@/mainview/scripts/utils"; import { scrollIntoViewHandler } from "@/mainview/scripts/utils";
import { FOCUS_KEYS } from "@/mainview/scripts/types"; import { FOCUS_KEYS } from "@/mainview/scripts/types";
import { FrontEndId, GameLookup } from "@/shared/types";
import { gameLookupQuery } from "@/mainview/scripts/queries/romm";
function Result (data: { function Result (data: {
match: GameLookup; match: GameLookup;
@ -54,7 +56,7 @@ function SearchField (data: { setSearch: (search: string | undefined) => void; s
</div>; </div>;
} }
export default function GameLookup (data: { export default function GameLookupElement (data: {
search: string | undefined, search: string | undefined,
setSearch: (search: string | undefined) => void, setSearch: (search: string | undefined) => void,
onSelect: (match: GameLookup) => void; onSelect: (match: GameLookup) => void;
@ -62,7 +64,7 @@ export default function GameLookup (data: {
selected?: FrontEndId; 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> return <div>
<SearchField setSearch={data.setSearch} search={data.search} /> <SearchField setSearch={data.setSearch} search={data.search} />

View file

@ -11,6 +11,7 @@ import ActionButton from "./ActionButton";
import { useRouter } from "@tanstack/react-router"; import { useRouter } from "@tanstack/react-router";
import { DownloadSourceType } from "@/shared/constants"; import { DownloadSourceType } from "@/shared/constants";
import { GamePadButtonCode, Shortcut, useShortcuts } from "@/mainview/scripts/shortcuts"; 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; }) 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 { useMutation, useQuery } from "@tanstack/react-query";
import { changeDownloadsMutation, getSettingQuery } from "@queries/settings"; import { changeDownloadsMutation, getSettingQuery } from "@queries/settings";
import { SettingsType } from "@/shared/constants"; import { SettingsType } from "@/shared/constants";
import { KeysWithValueAssignableTo } from "@/shared/types";
export default function DownloadDirectoryOption (data: PathSettingsOptionParams & { id: KeysWithValueAssignableTo<SettingsType, string>; }) 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 { CheckIcon, X } from "lucide-react";
import { oneShot } from "@/mainview/scripts/audio/audio"; import { oneShot } from "@/mainview/scripts/audio/audio";
import { GamePadButtonCode, Shortcut, useShortcuts } from "@/mainview/scripts/shortcuts"; import { GamePadButtonCode, Shortcut, useShortcuts } from "@/mainview/scripts/shortcuts";
import useActiveControl from "@/mainview/scripts/gamepads";
export function OptionInput (data: { export function OptionInput (data: {
name: string; name: string;
@ -35,7 +34,6 @@ export function OptionInput (data: {
} }
oneShot('click'); oneShot('click');
}; };
const { control } = useActiveControl();
const [inputFocused, setInputFocused] = useState(false); const [inputFocused, setInputFocused] = useState(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const { ref, focusKey } = useFocusable({ const { ref, focusKey } = useFocusable({

View file

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

View file

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

View file

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

View file

@ -12,6 +12,7 @@ import { StoreEmulatorCard } from "./StoreEmulatorCard";
import { FOCUS_KEYS } from "@/mainview/scripts/types"; import { FOCUS_KEYS } from "@/mainview/scripts/types";
import Carousel from "../Carousel"; import Carousel from "../Carousel";
import { useRouter } from "@tanstack/react-router"; 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; }) 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 { FOCUS_KEYS } from "@/mainview/scripts/types";
import Carousel from "../Carousel"; import Carousel from "../Carousel";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { FrontEndGameType, FrontEndId } from "@/shared/types";
export function GamesSection (data: { export function GamesSection (data: {
games?: FrontEndGameType[]; games?: FrontEndGameType[];

View file

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

View file

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

View file

@ -5,7 +5,7 @@ import z from 'zod';
import { RefObject, useEffect, useRef, useState } from 'react'; import { RefObject, useEffect, useRef, useState } from 'react';
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
import { ButtonStyle } from '../components/options/Button'; 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 { GamePadButtonCode, useShortcuts } from '../scripts/shortcuts';
import { FloatingShortcuts } from '../components/Shortcuts'; import { FloatingShortcuts } from '../components/Shortcuts';
import { useEventListener } from 'usehooks-ts'; 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 { HeaderUI, StickyHeaderUI } from "../../components/Header";
import { AnimatedBackground } from "../../components/AnimatedBackground"; import { AnimatedBackground } from "../../components/AnimatedBackground";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import Shortcuts, { FloatingShortcuts } from "../../components/Shortcuts"; import { FloatingShortcuts } from "../../components/Shortcuts";
import { GamePadButtonCode, useShortcutContext, useShortcuts } from "@/mainview/scripts/shortcuts"; import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts";
import Screenshots from "@/mainview/components/Screenshots"; import Screenshots from "@/mainview/components/Screenshots";
import { HandleGoBack, scrollIntoViewHandler, useOnNavigateBack } from "@/mainview/scripts/utils"; import { HandleGoBack, scrollIntoViewHandler, useOnNavigateBack } from "@/mainview/scripts/utils";
import { FilterUI } from "@/mainview/components/Filters"; 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 Details from "@/mainview/components/game/Details";
import { AutoFocus } from "@/mainview/components/AutoFocus"; import { AutoFocus } from "@/mainview/components/AutoFocus";
import SelectMenu from "@/mainview/components/SelectMenu"; import SelectMenu from "@/mainview/components/SelectMenu";
import { en } from "zod/v4/locales";
import { IGDBIcon } from "@/mainview/scripts/brandIcons"; import { IGDBIcon } from "@/mainview/scripts/brandIcons";
import { FrontEndGameTypeDetailed } from "@/shared/types";
export const Route = createFileRoute("/game/$source/$id")({ export const Route = createFileRoute("/game/$source/$id")({
loader: async ({ params, context }) => loader: async ({ params, context }) =>

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,9 @@
import { Button } from '@/mainview/components/options/Button';
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
import { checkUpdateMutation, hasUpdateQuery, systemInfoQuery, updateMutation } from '@queries/system'; import { systemInfoQuery } from '@queries/system';
import { useMutation, useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { createFileRoute } from '@tanstack/react-router'; import { createFileRoute } from '@tanstack/react-router';
import { ArrowUpCircle, CircleFadingArrowUp, RefreshCcw } from 'lucide-react';
import prettyBytes from 'pretty-bytes'; import prettyBytes from 'pretty-bytes';
export const Route = createFileRoute('/settings/about')({ 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 useActiveControl from '@/mainview/scripts/gamepads';
import { changeDownloadsMutation } from '@queries/settings'; import { changeDownloadsMutation } from '@queries/settings';
import { downloadDrivesQuery } from '@/mainview/scripts/queries/system'; import { downloadDrivesQuery } from '@/mainview/scripts/queries/system';
import { DownloadsDrive } from '@/shared/types';
export const Route = createFileRoute('/settings/directories')({ export const Route = createFileRoute('/settings/directories')({
component: RouteComponent, component: RouteComponent,

View file

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

View file

@ -5,15 +5,15 @@ import { OptionDropdown } from '@/mainview/components/options/OptionDropdown';
import { OptionInput } from '@/mainview/components/options/OptionInput'; import { OptionInput } from '@/mainview/components/options/OptionInput';
import { OptionSpace } from '@/mainview/components/options/OptionSpace'; import { OptionSpace } from '@/mainview/components/options/OptionSpace';
import { RoundButton } from '@/mainview/components/RoundButton'; 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 { getPluginActionsQuery, getPluginSettingQuery, getPluginSettingsDefinitionQuery, pluginActionMutation, setPluginSettingMutation } from '@/mainview/scripts/queries/settings';
import { GamePadButtonCode, useShortcuts } from '@/mainview/scripts/shortcuts'; import { GamePadButtonCode, useShortcuts } from '@/mainview/scripts/shortcuts';
import { scrollIntoViewHandler } from '@/mainview/scripts/utils'; import { scrollIntoViewHandler } from '@/mainview/scripts/utils';
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; 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 { createFileRoute, useNavigate } from '@tanstack/react-router';
import { JSONSchema7 } from 'json-schema'; 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'; import toast from 'react-hot-toast';
export const Route = createFileRoute('/settings/plugin/$source')({ export const Route = createFileRoute('/settings/plugin/$source')({
component: RouteComponent, component: RouteComponent,

View file

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

View file

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

View file

@ -5,12 +5,12 @@ import
FocusContext, FocusContext,
} from "@noriginmedia/norigin-spatial-navigation"; } from "@noriginmedia/norigin-spatial-navigation";
import { createFileRoute, useNavigate, useRouter } from "@tanstack/react-router"; import { createFileRoute, useNavigate, useRouter } from "@tanstack/react-router";
import { GamePadButtonCode, useShortcutContext, useShortcuts } from "@/mainview/scripts/shortcuts"; import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts";
import Shortcuts, { FloatingShortcuts } from "@/mainview/components/Shortcuts"; import { FloatingShortcuts } from "@/mainview/components/Shortcuts";
import { AnimatedBackground } from "@/mainview/components/AnimatedBackground"; import { AnimatedBackground } from "@/mainview/components/AnimatedBackground";
import { rommApi, systemApi } from "@/mainview/scripts/clientApi"; import { rommApi, systemApi } from "@/mainview/scripts/clientApi";
import { Button } from "@/mainview/components/options/Button"; 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 { ContextList, DialogEntry, useContextDialog } from "@/mainview/components/ContextDialog";
import { RPC_URL } from "@/shared/constants"; import { RPC_URL } from "@/shared/constants";
import Screenshots from "@/mainview/components/Screenshots"; import Screenshots from "@/mainview/components/Screenshots";
@ -29,6 +29,7 @@ import FocusTooltip from "@/mainview/components/FocusTooltip";
import { AutoFocus } from "@/mainview/components/AutoFocus"; import { AutoFocus } from "@/mainview/components/AutoFocus";
import { FilterUI } from "@/mainview/components/Filters"; import { FilterUI } from "@/mainview/components/Filters";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
import { FrontEndEmulatorDetailed } from "@/shared/types";
export const Route = createFileRoute('/store/details/emulator/$id')({ export const Route = createFileRoute('/store/details/emulator/$id')({
component: RouteComponent, 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 { Joystick } from 'lucide-react';
import { useContext, useEffect } from 'react'; import { useContext, useEffect } from 'react';
import { FocusContext, getCurrentFocusKey, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; 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