diff --git a/package.json b/package.json index 8abf0dd..43264be 100644 --- a/package.json +++ b/package.json @@ -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" } -} +} \ No newline at end of file diff --git a/scripts/build-sdk.ts b/scripts/build-sdk.ts new file mode 100644 index 0000000..173b2fc --- /dev/null +++ b/scripts/build-sdk.ts @@ -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(); \ No newline at end of file diff --git a/scripts/dev.ts b/scripts/dev.ts index c619171..ffc843c 100644 --- a/scripts/dev.ts +++ b/scripts/dev.ts @@ -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; }); } \ No newline at end of file diff --git a/scripts/generate-audio-sprites.ts b/scripts/generate-audio-sprites.ts index 1625362..24b65df 100644 --- a/scripts/generate-audio-sprites.ts +++ b/scripts/generate-audio-sprites.ts @@ -1,5 +1,4 @@ import audioSprite from 'audiosprite'; -import { $ } from 'bun'; import path from 'node:path'; import { soundMap } from '../src/mainview/scripts/audio/audioConstants'; diff --git a/scripts/generate-flatpak-sources.ts b/scripts/generate-flatpak-sources.ts index 6217e30..75c6084 100644 --- a/scripts/generate-flatpak-sources.ts +++ b/scripts/generate-flatpak-sources.ts @@ -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(); diff --git a/scripts/sdk/package.json b/scripts/sdk/package.json new file mode 100644 index 0000000..8d1b9eb --- /dev/null +++ b/scripts/sdk/package.json @@ -0,0 +1,4 @@ +{ + "name": "gameflow-sdk", + "types": "index.d.ts" +} \ No newline at end of file diff --git a/scripts/sdk/sdk.ts b/scripts/sdk/sdk.ts new file mode 100644 index 0000000..6568653 --- /dev/null +++ b/scripts/sdk/sdk.ts @@ -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; +export declare let events: EventEmitter; +export declare let taskQueue: TaskQueue; + +export { }; \ No newline at end of file diff --git a/scripts/sdk/sdk.tsconfig.json b/scripts/sdk/sdk.tsconfig.json new file mode 100644 index 0000000..20df404 --- /dev/null +++ b/scripts/sdk/sdk.tsconfig.json @@ -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" + ] +} \ No newline at end of file diff --git a/src/bun/api/app.ts b/src/bun/api/app.ts index 57aa61c..db06c81 100644 --- a/src/bun/api/app.ts +++ b/src/bun/api/app.ts @@ -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; export let customEmulators: Conf>; diff --git a/src/bun/api/drives.ts b/src/bun/api/drives.ts index 2df0dd8..a7f0565 100644 --- a/src/bun/api/drives.ts +++ b/src/bun/api/drives.ts @@ -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) { diff --git a/src/bun/api/emulatorjs/emulatorjs.ts b/src/bun/api/emulatorjs/emulatorjs.ts index 9e81b46..d770a43 100644 --- a/src/bun/api/emulatorjs/emulatorjs.ts +++ b/src/bun/api/emulatorjs/emulatorjs.ts @@ -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 = { @@ -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, diff --git a/src/bun/api/games/collections.ts b/src/bun/api/games/collections.ts index 6728845..1a49a12 100644 --- a/src/bun/api/games/collections.ts +++ b/src/bun/api/games/collections.ts @@ -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 () => diff --git a/src/bun/api/games/games.ts b/src/bun/api/games/games.ts index 604932c..88e9d50 100644 --- a/src/bun/api/games/games.ts +++ b/src/bun/api/games/games.ts @@ -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({ diff --git a/src/bun/api/games/platforms.ts b/src/bun/api/games/platforms.ts index d1509a0..de888ba 100644 --- a/src/bun/api/games/platforms.ts +++ b/src/bun/api/games/platforms.ts @@ -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 () => diff --git a/src/bun/api/games/services/launchGameService.ts b/src/bun/api/games/services/launchGameService.ts index 1a0dddc..248d6f2 100644 --- a/src/bun/api/games/services/launchGameService.ts +++ b/src/bun/api/games/services/launchGameService.ts @@ -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) { diff --git a/src/bun/api/games/services/statusService.ts b/src/bun/api/games/services/statusService.ts index b3a1691..291bf56 100644 --- a/src/bun/api/games/services/statusService.ts +++ b/src/bun/api/games/services/statusService.ts @@ -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 { diff --git a/src/bun/api/games/services/utils.ts b/src/bun/api/games/services/utils.ts index 833fbdd..0e1aba9 100644 --- a/src/bun/api/games/services/utils.ts +++ b/src/bun/api/games/services/utils.ts @@ -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) { diff --git a/src/bun/api/hooks/app.ts b/src/bun/api/hooks/app.ts index a1f0eec..bd549ca 100644 --- a/src/bun/api/hooks/app.ts +++ b/src/bun/api/hooks/app.ts @@ -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(); diff --git a/src/bun/api/hooks/auth.ts b/src/bun/api/hooks/auth.ts index 7234992..992d91e 100644 --- a/src/bun/api/hooks/auth.ts +++ b/src/bun/api/hooks/auth.ts @@ -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; diff --git a/src/bun/api/hooks/emulators.ts b/src/bun/api/hooks/emulators.ts index 4ac51e7..402bb21 100644 --- a/src/bun/api/hooks/emulators.ts +++ b/src/bun/api/hooks/emulators.ts @@ -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; diff --git a/src/bun/api/hooks/games.ts b/src/bun/api/hooks/games.ts index b08607c..3645d93 100644 --- a/src/bun/api/hooks/games.ts +++ b/src/bun/api/hooks/games.ts @@ -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; + saveFolderSlots?: SaveSlots; changedSaveFiles: { subPath: string, cwd: string; }[], validChangedSaveFiles: Record, command: CommandEntry; diff --git a/src/bun/api/hooks/store.ts b/src/bun/api/hooks/store.ts index de889d1..b08cee5 100644 --- a/src/bun/api/hooks/store.ts +++ b/src/bun/api/hooks/store.ts @@ -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']); diff --git a/src/bun/api/jobs/emulator-download-job.ts b/src/bun/api/jobs/emulator-download-job.ts index 0da918e..7dbaf6e 100644 --- a/src/bun/api/jobs/emulator-download-job.ts +++ b/src/bun/api/jobs/emulator-download-job.ts @@ -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"; diff --git a/src/bun/api/jobs/import-job.ts b/src/bun/api/jobs/import-job.ts index 91e118b..a0adee6 100644 --- a/src/bun/api/jobs/import-job.ts +++ b/src/bun/api/jobs/import-job.ts @@ -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, string> { diff --git a/src/bun/api/jobs/install-job.ts b/src/bun/api/jobs/install-job.ts index 1b48394..b6809e2 100644 --- a/src/bun/api/jobs/install-job.ts +++ b/src/bun/api/jobs/install-job.ts @@ -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 { diff --git a/src/bun/api/jobs/launch-game-job.ts b/src/bun/api/jobs/launch-game-job.ts index 54085a1..d81c25c 100644 --- a/src/bun/api/jobs/launch-game-job.ts +++ b/src/bun/api/jobs/launch-game-job.ts @@ -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, string> { diff --git a/src/bun/api/jobs/self-update-job.ts b/src/bun/api/jobs/self-update-job.ts index f965311..05ac4e6 100644 --- a/src/bun/api/jobs/self-update-job.ts +++ b/src/bun/api/jobs/self-update-job.ts @@ -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"; diff --git a/src/bun/api/notifications.ts b/src/bun/api/notifications.ts index 1a49080..514ee58 100644 --- a/src/bun/api/notifications.ts +++ b/src/bun/api/notifications.ts @@ -1,4 +1,5 @@ +import { FrontendNotification } from '@/shared/types'; import { events } from './app'; export default function buildNotificationsStream () diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.cemu/cemu.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.cemu/cemu.ts index 080af5f..9fe34a4 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.cemu/cemu.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.cemu/cemu.ts @@ -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"; diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin.ts index 79a615b..111b9c5 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin.ts @@ -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"; diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2.ts index db39248..23c2736 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2.ts @@ -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 { diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp.ts index a0c4a2d..87886dd 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp.ts @@ -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 { diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xemu/xemu.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xemu/xemu.ts index fe0f65e..ff8c3f9 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xemu/xemu.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xemu/xemu.ts @@ -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"; diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/utils.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/utils.ts index ceef07e..b3c26f9 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/utils.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/utils.ts @@ -1,5 +1,4 @@ import { join } from "path"; -import { platform } from "os"; const SECTOR_SIZE = 0x800; const MAGIC = "MICROSOFT*XBOX*MEDIA"; diff --git a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/xenia.ts b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/xenia.ts index 9d8670f..eb0715d 100644 --- a/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/xenia.ts +++ b/src/bun/api/plugins/builtin/emulators/com.simeonradivoev.gameflow.xenia/xenia.ts @@ -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"; diff --git a/src/bun/api/plugins/builtin/launchers/com.simeonradivoev.gameflow.es/es-de.ts b/src/bun/api/plugins/builtin/launchers/com.simeonradivoev.gameflow.es/es-de.ts index 7108dcf..cf57e13 100644 --- a/src/bun/api/plugins/builtin/launchers/com.simeonradivoev.gameflow.es/es-de.ts +++ b/src/bun/api/plugins/builtin/launchers/com.simeonradivoev.gameflow.es/es-de.ts @@ -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 { diff --git a/src/bun/api/plugins/builtin/other/com.simeonradivoev.gameflow.rclone/rclone.ts b/src/bun/api/plugins/builtin/other/com.simeonradivoev.gameflow.rclone/rclone.ts index f5fdf9f..a31c94d 100644 --- a/src/bun/api/plugins/builtin/other/com.simeonradivoev.gameflow.rclone/rclone.ts +++ b/src/bun/api/plugins/builtin/other/com.simeonradivoev.gameflow.rclone/rclone.ts @@ -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"; diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.igdb/igdb.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.igdb/igdb.ts index 71fa313..ce9cd6b 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.igdb/igdb.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.igdb/igdb.ts @@ -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 }; diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts index 0515ef0..a627199 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.romm/romm.ts @@ -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") diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts index 1d92b59..de08c46 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/services.ts @@ -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; }) { diff --git a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts index 2e0d507..3215548 100644 --- a/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts +++ b/src/bun/api/plugins/builtin/sources/com.simeonradivoev.gameflow.store/store.ts @@ -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 { diff --git a/src/bun/api/plugins/plugin-manager.ts b/src/bun/api/plugins/plugin-manager.ts index 86944f3..e3511b5 100644 --- a/src/bun/api/plugins/plugin-manager.ts +++ b/src/bun/api/plugins/plugin-manager.ts @@ -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; diff --git a/src/bun/api/plugins/plugins.ts b/src/bun/api/plugins/plugins.ts index a898036..eed9466 100644 --- a/src/bun/api/plugins/plugins.ts +++ b/src/bun/api/plugins/plugins.ts @@ -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 () => diff --git a/src/bun/api/plugins/register-plugins.ts b/src/bun/api/plugins/register-plugins.ts index 5f542ae..ead6f54 100644 --- a/src/bun/api/plugins/register-plugins.ts +++ b/src/bun/api/plugins/register-plugins.ts @@ -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; }; export default async function register (pluginManager: PluginManager) { - const plugins: (PluginDescriptionType & { main: string; load: () => Promise; })[] = [ + 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)) diff --git a/src/bun/api/schema/app.ts b/src/bun/api/schema/app.ts index 6008689..a30c4fb 100644 --- a/src/bun/api/schema/app.ts +++ b/src/bun/api/schema/app.ts @@ -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"; diff --git a/src/bun/api/settings/services.ts b/src/bun/api/settings/services.ts index f5b2d71..a560de6 100644 --- a/src/bun/api/settings/services.ts +++ b/src/bun/api/settings/services.ts @@ -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. diff --git a/src/bun/api/store/services/emulatorsService.ts b/src/bun/api/store/services/emulatorsService.ts index d29a1be..c61ed00 100644 --- a/src/bun/api/store/services/emulatorsService.ts +++ b/src/bun/api/store/services/emulatorsService.ts @@ -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[] { diff --git a/src/bun/api/store/services/gamesService.ts b/src/bun/api/store/services/gamesService.ts index 2e0d675..f2149ff 100644 --- a/src/bun/api/store/services/gamesService.ts +++ b/src/bun/api/store/services/gamesService.ts @@ -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 () { diff --git a/src/bun/api/store/store.ts b/src/bun/api/store/store.ts index 1c4323a..85463b3 100644 --- a/src/bun/api/store/store.ts +++ b/src/bun/api/store/store.ts @@ -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 }) => diff --git a/src/bun/api/system.ts b/src/bun/api/system.ts index 504cc25..66e7742 100644 --- a/src/bun/api/system.ts +++ b/src/bun/api/system.ts @@ -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) { diff --git a/src/bun/api/task-queue.ts b/src/bun/api/task-queue.ts index a54026a..97e783d 100644 --- a/src/bun/api/task-queue.ts +++ b/src/bun/api/task-queue.ts @@ -1,3 +1,4 @@ +import { JobStatus } from '@/shared/types'; import EventEmitter from 'node:events'; import z from 'zod'; diff --git a/src/bun/types/helpers.d.ts b/src/bun/types/helpers.d.ts new file mode 100644 index 0000000..afd8ea5 --- /dev/null +++ b/src/bun/types/helpers.d.ts @@ -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; +} \ No newline at end of file diff --git a/src/bun/types/types.d.ts b/src/bun/types/types.d.ts deleted file mode 100644 index ee43a63..0000000 --- a/src/bun/types/types.d.ts +++ /dev/null @@ -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 ( - items: Iterable, - keySelector: (item: T, index: number) => K, - ): Partial>; -} - -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 ( - items: Iterable, - keySelector: (item: T, index: number) => K, - ): Map; -} - -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; -} \ No newline at end of file diff --git a/src/bun/types/typesc.schema.ts b/src/bun/types/types.schema.ts similarity index 96% rename from src/bun/types/typesc.schema.ts rename to src/bun/types/types.schema.ts index 56bf90c..f56dd15 100644 --- a/src/bun/types/typesc.schema.ts +++ b/src/bun/types/types.schema.ts @@ -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() }); diff --git a/src/bun/types/types.ts b/src/bun/types/types.ts new file mode 100644 index 0000000..6802ff9 --- /dev/null +++ b/src/bun/types/types.ts @@ -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; +} \ No newline at end of file diff --git a/src/bun/utils.ts b/src/bun/utils.ts index 23d17c0..f03a42c 100644 --- a/src/bun/utils.ts +++ b/src/bun/utils.ts @@ -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) { diff --git a/src/bun/utils/downloader.ts b/src/bun/utils/downloader.ts index f4f7d95..000542a 100644 --- a/src/bun/utils/downloader.ts +++ b/src/bun/utils/downloader.ts @@ -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 { diff --git a/src/bun/utils/get-browser.ts b/src/bun/utils/get-browser.ts index c489dcc..7ba788e 100644 --- a/src/bun/utils/get-browser.ts +++ b/src/bun/utils/get-browser.ts @@ -35,18 +35,6 @@ interface BrowserResult source: GetBrowserSource; } -const PLATFORM_MAP: Record = { - linux: "linux", - win32: "windows", - darwin: 'macos' -}; - -const ARCH_MAP: Record> = { - 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 { diff --git a/src/mainview/components/AutoFocus.tsx b/src/mainview/components/AutoFocus.tsx index ae0bb33..7bed71b 100644 --- a/src/mainview/components/AutoFocus.tsx +++ b/src/mainview/components/AutoFocus.tsx @@ -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; diff --git a/src/mainview/components/CardElement.tsx b/src/mainview/components/CardElement.tsx index 6960315..6e27664 100644 --- a/src/mainview/components/CardElement.tsx +++ b/src/mainview/components/CardElement.tsx @@ -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"; diff --git a/src/mainview/components/CardList.tsx b/src/mainview/components/CardList.tsx index d311167..d05ce7b 100644 --- a/src/mainview/components/CardList.tsx +++ b/src/mainview/components/CardList.tsx @@ -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"; diff --git a/src/mainview/components/Error.tsx b/src/mainview/components/Error.tsx index 81bcb63..c77c28c 100644 --- a/src/mainview/components/Error.tsx +++ b/src/mainview/components/Error.tsx @@ -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"; diff --git a/src/mainview/components/FilePicker.tsx b/src/mainview/components/FilePicker.tsx index 09bc8f5..67c1a8b 100644 --- a/src/mainview/components/FilePicker.tsx +++ b/src/mainview/components/FilePicker.tsx @@ -86,7 +86,6 @@ function List (data: { function NewFolderInput (data: { id: string, name: string | undefined, setName: (name: string) => void; className?: string; }) { const inputRef = useRef(null); - const { control } = useActiveControl(); const { ref, focused, focusSelf } = useFocusable({ focusKey: data.id, onEnterPress: () => inputRef.current?.focus(), diff --git a/src/mainview/components/FrontEndGameCard.tsx b/src/mainview/components/FrontEndGameCard.tsx index 3d8ea25..c6b8e12 100644 --- a/src/mainview/components/FrontEndGameCard.tsx +++ b/src/mainview/components/FrontEndGameCard.tsx @@ -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) { diff --git a/src/mainview/components/GameList.tsx b/src/mainview/components/GameList.tsx index 5b999b1..67e689e 100644 --- a/src/mainview/components/GameList.tsx +++ b/src/mainview/components/GameList.tsx @@ -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"); } diff --git a/src/mainview/components/GamepadKeyboard.tsx b/src/mainview/components/GamepadKeyboard.tsx index ad2710e..4125db3 100644 --- a/src/mainview/components/GamepadKeyboard.tsx +++ b/src/mainview/components/GamepadKeyboard.tsx @@ -40,7 +40,7 @@ const KeyElements: Record = { '←': , '→': , }; -const DZ = 0.22, TH = 0.85, NS = 'http://www.w3.org/2000/svg'; +const DZ = 0.22; function ang (x: number, y: number) { diff --git a/src/mainview/components/HeaderSearchField.tsx b/src/mainview/components/HeaderSearchField.tsx index db83aad..36d0eb0 100644 --- a/src/mainview/components/HeaderSearchField.tsx +++ b/src/mainview/components/HeaderSearchField.tsx @@ -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) => diff --git a/src/mainview/components/NotFound.tsx b/src/mainview/components/NotFound.tsx index 6985609..2774240 100644 --- a/src/mainview/components/NotFound.tsx +++ b/src/mainview/components/NotFound.tsx @@ -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"; diff --git a/src/mainview/components/Notifications.tsx b/src/mainview/components/Notifications.tsx index a069069..4fbe03d 100644 --- a/src/mainview/components/Notifications.tsx +++ b/src/mainview/components/Notifications.tsx @@ -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"; diff --git a/src/mainview/components/PlatformsList.tsx b/src/mainview/components/PlatformsList.tsx index 2efcfbd..790a33d 100644 --- a/src/mainview/components/PlatformsList.tsx +++ b/src/mainview/components/PlatformsList.tsx @@ -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'; diff --git a/src/mainview/components/SideFilters.tsx b/src/mainview/components/SideFilters.tsx index 117577c..180030d 100644 --- a/src/mainview/components/SideFilters.tsx +++ b/src/mainview/components/SideFilters.tsx @@ -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, diff --git a/src/mainview/components/backgrounds/dots.tsx b/src/mainview/components/backgrounds/dots.tsx index 5971082..11e11c3 100644 --- a/src/mainview/components/backgrounds/dots.tsx +++ b/src/mainview/components/backgrounds/dots.tsx @@ -1,4 +1,4 @@ -import { Ref, RefObject } from 'react'; +import { Ref } from 'react'; import './dots.css'; export default function DotsLoading (data: { ref?: Ref; }) diff --git a/src/mainview/components/game/Achievements.tsx b/src/mainview/components/game/Achievements.tsx index 9296403..e9445cb 100644 --- a/src/mainview/components/game/Achievements.tsx +++ b/src/mainview/components/game/Achievements.tsx @@ -1,4 +1,5 @@ +import { FrontEndGameTypeDetailed, FrontEndGameTypeDetailedAchievement } from "@/shared/types"; import { useFocusable } from "@noriginmedia/norigin-spatial-navigation"; import { Medal } from "lucide-react"; diff --git a/src/mainview/components/game/ActionButtons.tsx b/src/mainview/components/game/ActionButtons.tsx index 7752644..02db473 100644 --- a/src/mainview/components/game/ActionButtons.tsx +++ b/src/mainview/components/game/ActionButtons.tsx @@ -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) { diff --git a/src/mainview/components/game/Details.tsx b/src/mainview/components/game/Details.tsx index ce7add0..c0ac4ea 100644 --- a/src/mainview/components/game/Details.tsx +++ b/src/mainview/components/game/Details.tsx @@ -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[]; }) { diff --git a/src/mainview/components/game/GameLookup.tsx b/src/mainview/components/game/GameLookup.tsx index da38987..15abb1c 100644 --- a/src/mainview/components/game/GameLookup.tsx +++ b/src/mainview/components/game/GameLookup.tsx @@ -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 ; } -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
diff --git a/src/mainview/components/game/MainActions.tsx b/src/mainview/components/game/MainActions.tsx index 51de536..a2caabc 100644 --- a/src/mainview/components/game/MainActions.tsx +++ b/src/mainview/components/game/MainActions.tsx @@ -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; }) { diff --git a/src/mainview/components/options/DownloadDirectoryOption.tsx b/src/mainview/components/options/DownloadDirectoryOption.tsx index 339bcc9..9cbe29f 100644 --- a/src/mainview/components/options/DownloadDirectoryOption.tsx +++ b/src/mainview/components/options/DownloadDirectoryOption.tsx @@ -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; }) { diff --git a/src/mainview/components/options/OptionInput.tsx b/src/mainview/components/options/OptionInput.tsx index 2d0fe6b..54caee8 100644 --- a/src/mainview/components/options/OptionInput.tsx +++ b/src/mainview/components/options/OptionInput.tsx @@ -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(null); const { ref, focusKey } = useFocusable({ diff --git a/src/mainview/components/options/PathSettingsOption.tsx b/src/mainview/components/options/PathSettingsOption.tsx index d81d32f..2c25fb2 100644 --- a/src/mainview/components/options/PathSettingsOption.tsx +++ b/src/mainview/components/options/PathSettingsOption.tsx @@ -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 { diff --git a/src/mainview/components/options/SettingsDropdown.tsx b/src/mainview/components/options/SettingsDropdown.tsx index 563b859..18eabd5 100644 --- a/src/mainview/components/options/SettingsDropdown.tsx +++ b/src/mainview/components/options/SettingsDropdown.tsx @@ -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; diff --git a/src/mainview/components/options/SettingsOption.tsx b/src/mainview/components/options/SettingsOption.tsx index 6823218..55357d7 100644 --- a/src/mainview/components/options/SettingsOption.tsx +++ b/src/mainview/components/options/SettingsOption.tsx @@ -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; diff --git a/src/mainview/components/store/EmulatorsSection.tsx b/src/mainview/components/store/EmulatorsSection.tsx index fd9ceff..eec1325 100644 --- a/src/mainview/components/store/EmulatorsSection.tsx +++ b/src/mainview/components/store/EmulatorsSection.tsx @@ -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; }) { diff --git a/src/mainview/components/store/GamesSection.tsx b/src/mainview/components/store/GamesSection.tsx index 0a1f4cb..ff3cbbb 100644 --- a/src/mainview/components/store/GamesSection.tsx +++ b/src/mainview/components/store/GamesSection.tsx @@ -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[]; diff --git a/src/mainview/components/store/MissingEmulatorsSection.tsx b/src/mainview/components/store/MissingEmulatorsSection.tsx index 4f07dbd..84f150b 100644 --- a/src/mainview/components/store/MissingEmulatorsSection.tsx +++ b/src/mainview/components/store/MissingEmulatorsSection.tsx @@ -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 diff --git a/src/mainview/components/store/StoreEmulatorCard.tsx b/src/mainview/components/store/StoreEmulatorCard.tsx index 47c07c0..09b3ca3 100644 --- a/src/mainview/components/store/StoreEmulatorCard.tsx +++ b/src/mainview/components/store/StoreEmulatorCard.tsx @@ -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 = { store: , diff --git a/src/mainview/routes/embedded.$source.$id.tsx b/src/mainview/routes/embedded.$source.$id.tsx index 3222280..b9a39b6 100644 --- a/src/mainview/routes/embedded.$source.$id.tsx +++ b/src/mainview/routes/embedded.$source.$id.tsx @@ -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'; diff --git a/src/mainview/routes/game/$source.$id.tsx b/src/mainview/routes/game/$source.$id.tsx index 86c551b..064deee 100644 --- a/src/mainview/routes/game/$source.$id.tsx +++ b/src/mainview/routes/game/$source.$id.tsx @@ -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 }) => diff --git a/src/mainview/routes/game/add.tsx b/src/mainview/routes/game/add.tsx index d741765..bf5f481 100644 --- a/src/mainview/routes/game/add.tsx +++ b/src/mainview/routes/game/add.tsx @@ -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 (undefined); - const navigate = useNavigate(); const router = useRouter(); const { data: game } = useQuery(gameQuery(source, id)); @@ -47,7 +46,7 @@ function RouteComponent ()
- diff --git a/src/mainview/routes/index.tsx b/src/mainview/routes/index.tsx index 527fea9..9505d30 100644 --- a/src/mainview/routes/index.tsx +++ b/src/mainview/routes/index.tsx @@ -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, diff --git a/src/mainview/routes/launcher.$source.$id.tsx b/src/mainview/routes/launcher.$source.$id.tsx index e66de07..78df354 100644 --- a/src/mainview/routes/launcher.$source.$id.tsx +++ b/src/mainview/routes/launcher.$source.$id.tsx @@ -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, diff --git a/src/mainview/routes/settings/about.tsx b/src/mainview/routes/settings/about.tsx index b6db34f..da450d4 100644 --- a/src/mainview/routes/settings/about.tsx +++ b/src/mainview/routes/settings/about.tsx @@ -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')({ diff --git a/src/mainview/routes/settings/directories.tsx b/src/mainview/routes/settings/directories.tsx index 5a32eec..4af256b 100644 --- a/src/mainview/routes/settings/directories.tsx +++ b/src/mainview/routes/settings/directories.tsx @@ -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, diff --git a/src/mainview/routes/settings/emulators.tsx b/src/mainview/routes/settings/emulators.tsx index f0e9360..7e3f381 100644 --- a/src/mainview/routes/settings/emulators.tsx +++ b/src/mainview/routes/settings/emulators.tsx @@ -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, diff --git a/src/mainview/routes/settings/plugin.$source.tsx b/src/mainview/routes/settings/plugin.$source.tsx index eedeada..7263bea 100644 --- a/src/mainview/routes/settings/plugin.$source.tsx +++ b/src/mainview/routes/settings/plugin.$source.tsx @@ -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, diff --git a/src/mainview/routes/settings/plugins.tsx b/src/mainview/routes/settings/plugins.tsx index 37240e9..c9476ba 100644 --- a/src/mainview/routes/settings/plugins.tsx +++ b/src/mainview/routes/settings/plugins.tsx @@ -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'; diff --git a/src/mainview/routes/settings/route.tsx b/src/mainview/routes/settings/route.tsx index 5df28ac..0283ac3 100644 --- a/src/mainview/routes/settings/route.tsx +++ b/src/mainview/routes/settings/route.tsx @@ -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"; diff --git a/src/mainview/routes/store/details.emulator.$id.tsx b/src/mainview/routes/store/details.emulator.$id.tsx index 0d6f6af..60b3917 100644 --- a/src/mainview/routes/store/details.emulator.$id.tsx +++ b/src/mainview/routes/store/details.emulator.$id.tsx @@ -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, diff --git a/src/mainview/routes/store/tab/emulators.tsx b/src/mainview/routes/store/tab/emulators.tsx index 67a5724..0d4ba0e 100644 --- a/src/mainview/routes/store/tab/emulators.tsx +++ b/src/mainview/routes/store/tab/emulators.tsx @@ -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'; diff --git a/src/mainview/routes/store/tab/games.tsx b/src/mainview/routes/store/tab/games.tsx index edf0864..c36b824 100644 --- a/src/mainview/routes/store/tab/games.tsx +++ b/src/mainview/routes/store/tab/games.tsx @@ -1,13 +1,11 @@ import { FocusContext, getCurrentFocusKey, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; -import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { Gamepad2, HardDrive } from 'lucide-react'; -import { JSX, useContext, useEffect, useState } from 'react'; -import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'; -import FrontEndGameCard from '@/mainview/components/FrontEndGameCard'; +import { JSX, useEffect } from 'react'; +import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { GetFocusedElement } from '@/mainview/scripts/spatialNavigation'; import LoadMoreButton from '@/mainview/components/LoadMoreButton'; import { storeGamesInfiniteQuery } from '@queries/store'; -import { StoreContext } from '@/mainview/scripts/contexts'; import InvalidStoreError from '@/mainview/components/store/InvalidStoreError'; import { CardList, GameMetaExtra } from '@/mainview/components/CardList'; import { GameListFilterType, RPC_URL } from '@/shared/constants'; @@ -16,6 +14,7 @@ import { zodValidator } from '@tanstack/zod-adapter'; import z from 'zod'; import SideFilters from '@/mainview/components/SideFilters'; import { gameFiltersQuery } from '@/mainview/scripts/queries/romm'; +import { FrontEndGameType } from '@/shared/types'; export const Route = createFileRoute('/store/tab/games')({ component: RouteComponent, @@ -91,7 +90,7 @@ function RouteComponent () 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; }); diff --git a/src/mainview/routes/store/tab/index.tsx b/src/mainview/routes/store/tab/index.tsx index 226f688..08ca653 100644 --- a/src/mainview/routes/store/tab/index.tsx +++ b/src/mainview/routes/store/tab/index.tsx @@ -16,6 +16,7 @@ import { useQuery } from '@tanstack/react-query'; import { autoEmulatorsQuery } from '@queries/settings'; import { storeEmulatorsRecommendedQuery, storeFeaturedGamesQuery } from '@queries/store'; import ImageWithFallbacks from '@/mainview/components/ImageWithFallbacks'; +import { FrontEndGameTypeDetailed } from '@/shared/types'; export const Route = createFileRoute('/store/tab/')({ component: RouteComponent diff --git a/src/mainview/scripts/audio/audio.ts b/src/mainview/scripts/audio/audio.ts index 04b5c8d..430e4de 100644 --- a/src/mainview/scripts/audio/audio.ts +++ b/src/mainview/scripts/audio/audio.ts @@ -37,10 +37,6 @@ function sinRandom () return Math.sin(new Date().getMilliseconds() / 1000 * Math.PI); } -function cosRandom () -{ - return Math.sin(new Date().getMilliseconds() / 1000 * Math.PI); -} function random () { diff --git a/src/mainview/scripts/contexts.ts b/src/mainview/scripts/contexts.ts index d987199..f54f0e2 100644 --- a/src/mainview/scripts/contexts.ts +++ b/src/mainview/scripts/contexts.ts @@ -2,6 +2,7 @@ import { SystemInfoType } from "@/shared/constants"; import { Direction, FocusDetails } from "@noriginmedia/norigin-spatial-navigation"; import { createContext } from "react"; import { Shortcut } from "./shortcuts"; +import { Drive } from "@/shared/types"; export const StoreContext = createContext({} as { showDetails: (type: 'emulator' | 'game', source: string, id: string, focusSource: string) => void; diff --git a/src/mainview/scripts/queries/romm.ts b/src/mainview/scripts/queries/romm.ts index c81cb3b..4a7a4f6 100644 --- a/src/mainview/scripts/queries/romm.ts +++ b/src/mainview/scripts/queries/romm.ts @@ -3,6 +3,7 @@ import { rommApi, settingsApi } from "../clientApi"; import { InvalidateQueryFilters, mutationOptions, QueryClient, QueryFilters, queryOptions } from "@tanstack/react-query"; import z from "zod"; import { statsApiStatsGetOptions } from "@/clients/romm/@tanstack/react-query.gen"; +import { FrontEndId } from "@/shared/types"; export const allGamesQuery = (filter?: GameListFilterType) => queryOptions({ queryKey: ['games', filter ?? 'all'], @@ -180,7 +181,7 @@ export const gameInvalidationQuery = (source: string, id: string): QueryFilters export const validateSourceQuery = (source: string, id: string) => queryOptions({ queryKey: ["game", source, id, "validate"], queryFn: async () => { - const { data, error } = await rommApi.api.romm.game({ source })({ id }).validate.get(); + const { data } = await rommApi.api.romm.game({ source })({ id }).validate.get(); return data; } }); @@ -237,7 +238,7 @@ export const gameFiltersQuery = (filters: { source?: string; }) => queryOptions( } }); -export const gameLookup = (search: string | undefined) => queryOptions({ +export const gameLookupQuery = (search: string | undefined) => queryOptions({ queryKey: ['game', 'lookup', search], queryFn: async () => { diff --git a/src/mainview/scripts/queries/store.ts b/src/mainview/scripts/queries/store.ts index 9e506a9..6347944 100644 --- a/src/mainview/scripts/queries/store.ts +++ b/src/mainview/scripts/queries/store.ts @@ -1,6 +1,7 @@ import { infiniteQueryOptions, mutationOptions, queryOptions } from "@tanstack/react-query"; import { rommApi, storeApi } from "../clientApi"; import { GameListFilterType } from "@/shared/constants"; +import { FrontEndGameType } from "@/shared/types"; export const storeEmulatorsQuery = (filters: { search?: string; }) => queryOptions({ diff --git a/src/mainview/scripts/types.ts b/src/mainview/scripts/types.ts index 1f45367..a192266 100644 --- a/src/mainview/scripts/types.ts +++ b/src/mainview/scripts/types.ts @@ -1,3 +1,5 @@ +import { FrontEndId } from "@/shared/types"; + export const FOCUS_KEYS = { NAV_CATEGORIES: "NAV_CATEGORIES", NAV_CATEGORY: (cat: string) => `NAV_CAT_${cat}`, diff --git a/src/mainview/scripts/utils.ts b/src/mainview/scripts/utils.ts index d14fcc0..7f4bf68 100644 --- a/src/mainview/scripts/utils.ts +++ b/src/mainview/scripts/utils.ts @@ -1,5 +1,5 @@ import { LocalSettingsSchema, LocalSettingsType } from "@/shared/constants"; -import { DependencyList, FocusEventHandler, RefObject, useEffect, useRef, useState } from "react"; +import { DependencyList, RefObject, useEffect, useRef, useState } from "react"; import { useLocalStorage } from "usehooks-ts"; import { jobsApi, systemApi } from "./clientApi"; import { JobsAPIType } from "@/bun/api/rpc"; diff --git a/src/mainview/scripts/windowEvents.ts b/src/mainview/scripts/windowEvents.ts index c6f7ab8..88dd926 100644 --- a/src/mainview/scripts/windowEvents.ts +++ b/src/mainview/scripts/windowEvents.ts @@ -2,7 +2,7 @@ import { settingsApi } from "./clientApi"; const handleResize = () => { - settingsApi.api.settings({ id: 'windowSize' }).post({ value: { width: window.innerWidth, height: window.innerHeight } }); + settingsApi.api.settings.local({ id: 'windowSize' }).post({ value: { width: window.innerWidth, height: window.innerHeight } }); }; window.addEventListener("resize", handleResize); import.meta.hot?.dispose(() => window.removeEventListener('resize', handleResize)); @@ -13,7 +13,7 @@ var screenPositionInternal: NodeJS.Timeout = setInterval(() => { if (lastWindowPosX != window.screenX || lastWindowPosY != window.screenY) { - settingsApi.api.settings({ id: 'windowPosition' }).post({ value: { x: window.screenX, y: window.screenY } }); + settingsApi.api.settings.local({ id: 'windowPosition' }).post({ value: { x: window.screenX, y: window.screenY } }); } lastWindowPosX = window.screenX; diff --git a/src/mainview/types.d.ts b/src/mainview/types.d.ts index 0ac95d6..1a1246e 100644 --- a/src/mainview/types.d.ts +++ b/src/mainview/types.d.ts @@ -46,6 +46,16 @@ declare interface FocusEventDetails focusKeyChanged: boolean; } +declare interface GameMeta extends FocusParams +{ + id: string, + onSelect?: () => void, + title: string, + subtitle?: any, + previewUrls?: string | URL[]; + previewSrcset?: string; +}; + declare interface FocusParams { onFocus?: (focusKey: string, node: HTMLElement, details: Record) => void; diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 639202e..5eb5fdf 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -1,4 +1,3 @@ -import { JSX } from 'react'; import * as z from 'zod'; export const LOGIN_PORT = 5196; @@ -16,15 +15,6 @@ export const settingRegistry = z.registry<{ }>(); export const DefaultRommStaleTime = 60 * 1000; // A minute -export interface GameMeta extends FocusParams -{ - id: string, - onSelect?: () => void, - title: string, - subtitle?: string | JSX.Element, - previewUrls?: string | URL[]; - previewSrcset?: string; -}; export const SettingsSchema = z.object({ rommAddress: z.url().optional(), diff --git a/src/shared/types..d.ts b/src/shared/types.ts similarity index 77% rename from src/shared/types..d.ts rename to src/shared/types.ts index a0b3d19..dc72a7a 100644 --- a/src/shared/types..d.ts +++ b/src/shared/types.ts @@ -1,6 +1,15 @@ -declare type EmulatorSourceType = 'custom' | 'store' | 'registry' | 'system' | 'static' | 'embedded'; +export interface SaveFileChange +{ + subPath: string | string[]; + isGlob?: true; + cwd: string; + shared: boolean; + fixedSize?: boolean; +} -declare interface EmulatorSourceEntryType +export type EmulatorSourceType = 'custom' | 'store' | 'registry' | 'system' | 'static' | 'embedded'; + +export interface EmulatorSourceEntryType { binPath: string; rootPath?: string; @@ -8,7 +17,7 @@ declare interface EmulatorSourceEntryType exists: boolean; } -declare interface FrontEndEmulator +export interface FrontEndEmulator { name: string; source: string; @@ -20,16 +29,16 @@ declare interface FrontEndEmulator integrations: EmulatorSupport[]; } -declare interface EmulatorSystem { id: string, romm_slug?: string, name: string, iconUrl: string; } +export interface EmulatorSystem { id: string, romm_slug?: string, name: string, iconUrl: string; } -declare interface FrontEndEmulatorDetailedDownload +export interface FrontEndEmulatorDetailedDownload { name: string; type: string | undefined; version?: string; } -declare interface FrontEndEmulatorDetailed extends FrontEndEmulator +export interface FrontEndEmulatorDetailed extends FrontEndEmulator { homepage: string; description: string; @@ -41,7 +50,7 @@ declare interface FrontEndEmulatorDetailed extends FrontEndEmulator storeDownloadInfo?: { hasUpdate: boolean; version?: string, type: string; description?: string; }; } -declare interface FrontEndGameTypeDetailedAchievement +export interface FrontEndGameTypeDetailedAchievement { id: string; title: string; @@ -53,12 +62,12 @@ declare interface FrontEndGameTypeDetailedAchievement type?: string; } -declare interface FrontEndGameTypeDetailedEmulator extends FrontEndEmulator +export interface FrontEndGameTypeDetailedEmulator extends FrontEndEmulator { } -declare interface FrontEndGameTypeDetailed extends Exclude +export interface FrontEndGameTypeDetailed extends Exclude { summary: string | null; fs_size_bytes: number | null; @@ -76,7 +85,7 @@ declare interface FrontEndGameTypeDetailed extends Exclude, player_counts: Set, @@ -187,7 +196,7 @@ declare interface FrontEndFilterSets genres: Set; } -declare interface FrontEndFilterLists +export interface FrontEndFilterLists { age_ratings: string[], player_counts: string[], @@ -196,12 +205,12 @@ declare interface FrontEndFilterLists genres: string[]; } -declare interface FrontEndGameMetadata +export interface FrontEndGameMetadata { first_release_date: Date | null; } -declare interface FrontEndGameMetadataDetailed extends FrontEndGameMetadata +export interface FrontEndGameMetadataDetailed extends FrontEndGameMetadata { genres: string[], companies: string[], @@ -211,7 +220,7 @@ declare interface FrontEndGameMetadataDetailed extends FrontEndGameMetadata average_rating: number | null; } -declare interface FrontEndGameType +export interface FrontEndGameType { platform_display_name: string | null, path_platform_cover: string | null; @@ -230,9 +239,9 @@ declare interface FrontEndGameType paths_screenshots: string[]; }; -declare type GameStatusType = 'installed' | 'missing-emulator' | 'error' | 'install' | 'download' | 'extract' | 'playing' | 'queued'; +export type GameStatusType = 'installed' | 'missing-emulator' | 'error' | 'install' | 'download' | 'extract' | 'playing' | 'queued'; -declare interface GameInstallProgress +export interface GameInstallProgress { progress?: number; status?: GameStatusType; @@ -241,10 +250,10 @@ declare interface GameInstallProgress error?: any; } -declare type JobStatus = 'completed' | 'error' | 'running' | 'queued' | 'aborted'; -declare type GameInstallProgressEvent = 'refresh'; +export type JobStatus = 'completed' | 'error' | 'running' | 'queued' | 'aborted'; +export type GameInstallProgressEvent = 'refresh'; -declare interface FrontendPlugin +export interface FrontendPlugin { name: string; displayName: string; @@ -258,13 +267,13 @@ declare interface FrontendPlugin icon?: string; } -declare type PluginSourceType = "builtin"; +export type PluginSourceType = "builtin"; -declare type KeysWithValueAssignableTo = { +export type KeysWithValueAssignableTo = { [K in keyof T]: Exclude extends Value ? K : never; }[keyof T]; -declare interface DownloadInfo +export interface DownloadInfo { id: string; screenshotUrls: string[]; @@ -289,7 +298,7 @@ declare interface DownloadInfo version_system?: string; } -declare interface DownloadPlatform +export interface DownloadPlatform { id: string; source: string; @@ -303,7 +312,7 @@ declare interface DownloadPlatform family_name?: string; } -declare interface DownloadFileEntry +export interface DownloadFileEntry { url: URL; /** The path of the file, excluding the name */ @@ -316,7 +325,7 @@ declare interface DownloadFileEntry size?: number; } -declare interface LocalDownloadFileEntry extends DownloadFileEntry +export interface LocalDownloadFileEntry extends DownloadFileEntry { /** Exists on the file system */ exists: boolean; @@ -324,7 +333,7 @@ declare interface LocalDownloadFileEntry extends DownloadFileEntry matches: boolean; } -declare interface FrontEndCollection +export interface FrontEndCollection { id: FrontEndId; name: string; @@ -333,9 +342,9 @@ declare interface FrontEndCollection game_count: number; } -declare type EmulatorCapabilities = "saves" | "fullscreen" | "resolution" | "batch" | "states" | "config"; +export type EmulatorCapabilities = "saves" | "fullscreen" | "resolution" | "batch" | "states" | "config"; -declare interface EmulatorSupport +export interface EmulatorSupport { id: string; source?: EmulatorSourceEntryType; @@ -343,7 +352,7 @@ declare interface EmulatorSupport capabilities?: EmulatorCapabilities[]; } -declare interface GameLookup +export interface GameLookup { source: string; id: string; @@ -369,19 +378,10 @@ declare interface GameLookup }[]; } -declare interface AutoSaveChange +export interface AutoSaveChange { subPath: string; cwd: string; } -declare interface SaveFileChange -{ - subPath: string | string[]; - isGlob?: true; - cwd: string; - shared: boolean; - fixedSize?: boolean; -} - -declare type SaveSlots = Record; \ No newline at end of file +export type SaveSlots = Record; \ No newline at end of file diff --git a/src/tests/downloads.test.ts b/src/tests/downloads.test.ts index eea234f..6a58e55 100644 --- a/src/tests/downloads.test.ts +++ b/src/tests/downloads.test.ts @@ -4,6 +4,7 @@ import * as app from '@/bun/api/app'; import fs from 'node:fs/promises'; import path from "node:path"; import AdmZip from "adm-zip"; +import { DownloadInfo } from '@/shared/types'; describe("Download Tests", () => { @@ -50,17 +51,18 @@ describe("Download Tests", () => { const mock = jest.fn(); app.plugins.hooks.games.fetchDownloads.tap('test2', mock); - app.plugins.hooks.games.fetchDownloads.tapPromise('test', async ({ source, id }) => + app.plugins.hooks.games.fetchDownloads.tapPromise('test', async ({ source }) => { if (source !== 'test') return; - return { + return [{ files: [{ file_name: "Test File.txt", file_path: 'test/files', url: new URL(`${server.url.href}download/single_file.txt`) }], coverUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/SIPI_Jelly_Beans_4.1.07.tiff/lossy-page1-256px-SIPI_Jelly_Beans_4.1.07.tiff.jpg", name: "Test Game", screenshotUrls: [], system_slug: 'ps2', - source_id: "0" - }; + source_id: "0", + id: 'test' + } satisfies DownloadInfo]; }); const res = await client.rommApi.api.romm.game({ source: 'test' })({ id: '0' }).install.post(); @@ -77,7 +79,7 @@ describe("Download Tests", () => app.plugins.hooks.games.fetchDownloads.tapPromise('test', async ({ source, id }) => { if (source !== 'test') return; - return { + return [{ files: [ { file_name: "Test File.txt", file_path: 'test/files', url: new URL(`${server.url.href}download/single_file.txt`) }, { file_name: "Test File 2.txt", file_path: 'test/files', url: new URL(`${server.url.href}download/single_file_2.txt`) }], @@ -85,8 +87,9 @@ describe("Download Tests", () => name: "Test Game", screenshotUrls: [], system_slug: 'ps2', - source_id: "0" - }; + source_id: "0", + id: 'test' + } satisfies DownloadInfo]; }); const res = await client.rommApi.api.romm.game({ source: 'test' })({ id: '0' }).install.post(); @@ -104,7 +107,7 @@ describe("Download Tests", () => app.plugins.hooks.games.fetchDownloads.tapPromise('test', async ({ source, id }) => { if (source !== 'test') return; - return { + return [{ files: [ { file_name: "zip_file_with_single_file.zip", file_path: 'test', url: new URL(`${server.url.href}download/zip_file_with_single_file.zip`) }], coverUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/SIPI_Jelly_Beans_4.1.07.tiff/lossy-page1-256px-SIPI_Jelly_Beans_4.1.07.tiff.jpg", @@ -112,8 +115,9 @@ describe("Download Tests", () => screenshotUrls: [], system_slug: 'ps2', source_id: "0", - extract_path: 'test/files' - }; + extract_path: 'test/files', + id: 'test' + } satisfies DownloadInfo]; }); const res = await client.rommApi.api.romm.game({ source: 'test' })({ id: '0' }).install.post(); diff --git a/src/tests/game-launching.test.ts b/src/tests/game-launching.test.ts index 9a0f960..a84e6d5 100644 --- a/src/tests/game-launching.test.ts +++ b/src/tests/game-launching.test.ts @@ -1,4 +1,4 @@ -import { expect, test, beforeEach, describe } from 'bun:test'; +import { expect, test } from 'bun:test'; import path, { resolve } from 'node:path'; import * as app from '@/bun/api/app'; import * as appSchema from '@/bun/api/schema/app';