fix: Moved to manual plugin version checking and fixed some steam deck issues.
This commit is contained in:
parent
5593985884
commit
641eb2fcd5
13 changed files with 219 additions and 28 deletions
3
bun.lock
3
bun.lock
|
|
@ -25,7 +25,6 @@
|
||||||
"node-downloader-helper": "^2.1.11",
|
"node-downloader-helper": "^2.1.11",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
"node-unrar-js": "^2.0.2",
|
"node-unrar-js": "^2.0.2",
|
||||||
"npm-check-updates": "^22.2.0",
|
|
||||||
"open": "^11.0.0",
|
"open": "^11.0.0",
|
||||||
"p-queue": "^9.2.0",
|
"p-queue": "^9.2.0",
|
||||||
"pathe": "^2.0.3",
|
"pathe": "^2.0.3",
|
||||||
|
|
@ -1436,8 +1435,6 @@
|
||||||
|
|
||||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||||
|
|
||||||
"npm-check-updates": ["npm-check-updates@22.2.0", "", { "bin": { "npm-check-updates": "build/cli.js", "ncu": "build/cli.js" } }, "sha512-kaxgbkGkCOtoSrsUXShgcEiEfrRPqmOGk6Yeya+5hoNptblu9vuE8/PLABUSJz+IeNgKJBFxcC3UrBYmKsB8iA=="],
|
|
||||||
|
|
||||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||||
|
|
||||||
"nypm": ["nypm@0.6.4", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw=="],
|
"nypm": ["nypm@0.6.4", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw=="],
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,6 @@
|
||||||
"node-downloader-helper": "^2.1.11",
|
"node-downloader-helper": "^2.1.11",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
"node-unrar-js": "^2.0.2",
|
"node-unrar-js": "^2.0.2",
|
||||||
"npm-check-updates": "^22.2.0",
|
|
||||||
"open": "^11.0.0",
|
"open": "^11.0.0",
|
||||||
"p-queue": "^9.2.0",
|
"p-queue": "^9.2.0",
|
||||||
"pathe": "^2.0.3",
|
"pathe": "^2.0.3",
|
||||||
|
|
@ -90,6 +89,11 @@
|
||||||
"webview-bun": "^2.4.0",
|
"webview-bun": "^2.4.0",
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"@tanstack/router-generator": {
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ap0nia/eden": "^1.6.1",
|
"@ap0nia/eden": "^1.6.1",
|
||||||
"@ap0nia/eden-tanstack-query": "^1.0.0-next.22",
|
"@ap0nia/eden-tanstack-query": "^1.0.0-next.22",
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,12 @@ export async function checkLoginAndRefreshTwitch ()
|
||||||
|
|
||||||
export async function checkLoginAndRefreshRomm ()
|
export async function checkLoginAndRefreshRomm ()
|
||||||
{
|
{
|
||||||
|
//TODO: move to plugin logic
|
||||||
|
if (plugins.plugins['com.simeonradivoev.gameflow.romm'].config?.get('clientApiToken'))
|
||||||
|
{
|
||||||
|
return { hasLogin: true };
|
||||||
|
}
|
||||||
|
|
||||||
const access_token = await secrets.get({ service: 'gameflow', name: 'romm_access_token' });
|
const access_token = await secrets.get({ service: 'gameflow', name: 'romm_access_token' });
|
||||||
if (!access_token)
|
if (!access_token)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export default class RommIntegration implements PluginType<SettingsType>
|
||||||
async getAccessToken (config: Conf<SettingsType>)
|
async getAccessToken (config: Conf<SettingsType>)
|
||||||
{
|
{
|
||||||
if (process.env.ROMM_CLIENT_TOKEN) return process.env.ROMM_CLIENT_TOKEN;
|
if (process.env.ROMM_CLIENT_TOKEN) return process.env.ROMM_CLIENT_TOKEN;
|
||||||
const client_token = await config.get('clientApiToken');
|
const client_token = config.get('clientApiToken');
|
||||||
if (client_token) return client_token;
|
if (client_token) return client_token;
|
||||||
return (await secrets.get({ service: 'gameflow', name: 'romm_access_token' })) ?? undefined;
|
return (await secrets.get({ service: 'gameflow', name: 'romm_access_token' })) ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-s
|
||||||
import desc from './package.json';
|
import desc from './package.json';
|
||||||
import path, { } from 'node:path';
|
import path, { } from 'node:path';
|
||||||
import { buildStoreFrontendEmulatorSystems, getAllStoreEmulatorPackages, getStoreEmulatorPackage, getStoreFolder } from "@/bun/api/store/services/gamesService";
|
import { buildStoreFrontendEmulatorSystems, getAllStoreEmulatorPackages, getStoreEmulatorPackage, getStoreFolder } from "@/bun/api/store/services/gamesService";
|
||||||
import { Glob, pathToFileURL, sleep, which } from "bun";
|
import { Glob, pathToFileURL, which } from "bun";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import * as emulatorSchema from '@schema/emulators';
|
import * as emulatorSchema from '@schema/emulators';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ export class PluginManager
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async reload (name: string, reloadCtx: { setProgress: (progress: number, state: string) => void; }, update: string | undefined)
|
private async reload (name: string, reloadCtx: { setProgress: (progress: number, state: string) => void; }, update: string | undefined | null)
|
||||||
{
|
{
|
||||||
const plugin = this.plugins[name];
|
const plugin = this.plugins[name];
|
||||||
if (plugin)
|
if (plugin)
|
||||||
|
|
@ -149,7 +149,7 @@ export class PluginManager
|
||||||
for await (const id of Object.keys(this.plugins))
|
for await (const id of Object.keys(this.plugins))
|
||||||
{
|
{
|
||||||
ctx.setProgress(0, `Loading ${id}`);
|
ctx.setProgress(0, `Loading ${id}`);
|
||||||
await this.reload(id, ctx, outdated?.[id]);
|
await this.reload(id, ctx, outdated.find(i => i.package === id)?.update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,19 +122,24 @@ export default async function register (pluginManager: PluginManager)
|
||||||
|
|
||||||
if (outdated)
|
if (outdated)
|
||||||
{
|
{
|
||||||
for await (const plugin of validPlugins)
|
for (let i = 0; i < validPlugins.length; i++)
|
||||||
{
|
{
|
||||||
const newVersion = outdated[plugin.name];
|
const plugin = validPlugins[i];
|
||||||
|
const newVersion = outdated.find(i => i.package === plugin.name);
|
||||||
if (newVersion)
|
if (newVersion)
|
||||||
{
|
{
|
||||||
console.log("Plugin", plugin.name, "has update", plugin.version, "=>", newVersion);
|
console.log("Plugin", plugin.name, "has update", plugin.version, "=>", newVersion.update);
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin.autoUpdate)
|
if (plugin.autoUpdate || plugin.name === '@simeonradivoev/gameflow-store')
|
||||||
{
|
{
|
||||||
console.log("Auto Updating Plugin", plugin.name);
|
console.log("Auto Updating Plugin", plugin.name);
|
||||||
let response = await runBunPackageCommand(["add", `${plugin.name}@${newVersion}`, "--registry", PluginRegistry, '--omit', 'peer']);
|
let response = await runBunPackageCommand(["add", `${plugin.name}@${newVersion?.update}`, "--registry", PluginRegistry, '--omit', 'peer']);
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
// Update plugin package
|
||||||
|
const newPlugin = await getPlugin(plugin.name, pluginManager);
|
||||||
|
if (newPlugin)
|
||||||
|
validPlugins[i] = newPlugin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import path from 'node:path';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import { getStoreRootFolder } from '../store/services/gamesService';
|
import { getStoreRootFolder } from '../store/services/gamesService';
|
||||||
import { PluginDescriptionType } from '@simeonradivoev/gameflow-sdk';
|
import { PluginDescriptionType } from '@simeonradivoev/gameflow-sdk';
|
||||||
import { run } from 'npm-check-updates';
|
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
|
import { checkOutdated } from './update-check';
|
||||||
|
|
||||||
export function canDisable (description: PluginDescriptionType)
|
export function canDisable (description: PluginDescriptionType)
|
||||||
{
|
{
|
||||||
|
|
@ -16,9 +16,9 @@ export function canDisable (description: PluginDescriptionType)
|
||||||
|
|
||||||
export async function getUpdates ()
|
export async function getUpdates ()
|
||||||
{
|
{
|
||||||
if (!existsSync(getStoreRootFolder())) return {};
|
if (!existsSync(getStoreRootFolder())) return [];
|
||||||
const updated = await run({ packageManager: 'bun', peer: true, cwd: getStoreRootFolder(), jsonUpgraded: true, reject: ['@simeonradivoev/gameflow-sdk'] });
|
const results = await checkOutdated(getStoreRootFolder());
|
||||||
return updated as Record<string, string>;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canUninstall (description: PluginDescriptionType, source: string)
|
export function canUninstall (description: PluginDescriptionType, source: string)
|
||||||
|
|
|
||||||
169
src/bun/api/plugins/update-check.ts
Normal file
169
src/bun/api/plugins/update-check.ts
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
import { semver } from "bun";
|
||||||
|
import { readFile } from "fs/promises";
|
||||||
|
import { join } from "path";
|
||||||
|
import { getOrCached } from "../cache";
|
||||||
|
import { PluginRegistry } from "@/shared/constants";
|
||||||
|
import sdkPkg from '@/packages/gameflow-sdk/package.json';
|
||||||
|
|
||||||
|
interface UpdateInfo
|
||||||
|
{
|
||||||
|
package: string,
|
||||||
|
current: string,
|
||||||
|
update: string | null,
|
||||||
|
latest: string,
|
||||||
|
sdkConstrained: boolean,
|
||||||
|
sdkRange: string,
|
||||||
|
note: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBunOutdated (cwd: string)
|
||||||
|
{
|
||||||
|
const proc = Bun.spawnSync([process.execPath, "outdated"], {
|
||||||
|
stderr: "inherit", env: {
|
||||||
|
BUN_BE_BUN: "1",
|
||||||
|
NO_COLOR: "1",
|
||||||
|
}, cwd: cwd
|
||||||
|
});
|
||||||
|
const output = proc.stdout.toString();
|
||||||
|
const lines = output.split("\n").filter(Boolean);
|
||||||
|
|
||||||
|
const headerIndex = lines.findIndex(
|
||||||
|
(l) => l.includes("Package") && l.includes("Current")
|
||||||
|
);
|
||||||
|
if (headerIndex === -1) return [];
|
||||||
|
|
||||||
|
return lines
|
||||||
|
.slice(headerIndex + 1)
|
||||||
|
.filter((line) => !/^[-─╌| ]+$/.test(line))
|
||||||
|
.map((line) =>
|
||||||
|
{
|
||||||
|
const [, pkg, current, , latest] = line.split("|").map((c) => c.trim());
|
||||||
|
return pkg ? { package: pkg, current, latest } : null;
|
||||||
|
})
|
||||||
|
.filter(p => p !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getInstalledVersion (cwd: string, pkg: string)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const raw = await readFile(join(cwd, "node_modules", pkg, "package.json"), "utf8");
|
||||||
|
return JSON.parse(raw).version ?? null;
|
||||||
|
} catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAllVersions (pkg: string)
|
||||||
|
{
|
||||||
|
const res = await fetch(`${PluginRegistry}/${pkg}`);
|
||||||
|
if (!res.ok) return [];
|
||||||
|
const data = await res.json();
|
||||||
|
return Object.keys(data.versions ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPeerDeps (pkg: string, version: string)
|
||||||
|
{
|
||||||
|
const peerDependencies = await getOrCached(`npm-${pkg}-${version}`, async () =>
|
||||||
|
{
|
||||||
|
const res = await fetch(`${PluginRegistry}/${pkg}/${version}`);
|
||||||
|
if (!res.ok)
|
||||||
|
{
|
||||||
|
throw new Error(`Error while fetching peer deps for ${pkg} ${version} ${res.status} ${res.statusText}`);
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
return data.peerDependencies ?? {};
|
||||||
|
}, {
|
||||||
|
//5 days
|
||||||
|
expireMs: 1000 * 60 * 60 * 24 * 5
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return peerDependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findBestVersion (pkg: string, allVersions: string[], sdkVersion: string)
|
||||||
|
{
|
||||||
|
// Sort descending so we find the highest compatible version first
|
||||||
|
const sorted = [...allVersions].sort((a, b) => semver.order(b, a));
|
||||||
|
|
||||||
|
for (const version of sorted)
|
||||||
|
{
|
||||||
|
const peers = await fetchPeerDeps(pkg, version);
|
||||||
|
const sdkRange = peers[sdkPkg.name];
|
||||||
|
|
||||||
|
if (!sdkRange)
|
||||||
|
{
|
||||||
|
// No peer dep on SDK — compatible by default
|
||||||
|
return { version, sdkRange: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (semver.satisfies(sdkVersion, sdkRange))
|
||||||
|
{
|
||||||
|
return { version, sdkRange };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkOutdated (cwd: string)
|
||||||
|
{
|
||||||
|
const outdated = parseBunOutdated(cwd);
|
||||||
|
|
||||||
|
if (outdated.length === 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sdkVersion = await getInstalledVersion(cwd, sdkPkg.name);
|
||||||
|
if (!sdkVersion)
|
||||||
|
{
|
||||||
|
console.error(`Could not find installed version of ${sdkPkg.name} in node_modules.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await Promise.all(
|
||||||
|
outdated.map(async ({ package: pkg, current, latest }) =>
|
||||||
|
{
|
||||||
|
const allVersions = await fetchAllVersions(pkg);
|
||||||
|
|
||||||
|
// Check if the outright latest is already SDK compatible
|
||||||
|
const latestPeers = await fetchPeerDeps(pkg, latest);
|
||||||
|
const latestSdkRange = latestPeers[sdkPkg.name];
|
||||||
|
|
||||||
|
const latestCompatible =
|
||||||
|
!latestSdkRange || semver.satisfies(sdkVersion, latestSdkRange);
|
||||||
|
|
||||||
|
if (latestCompatible)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
package: pkg,
|
||||||
|
current,
|
||||||
|
update: latest,
|
||||||
|
latest,
|
||||||
|
sdkConstrained: false,
|
||||||
|
sdkRange: latestSdkRange ?? null,
|
||||||
|
note: null
|
||||||
|
} satisfies UpdateInfo as UpdateInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
const best = await findBestVersion(pkg, allVersions, sdkVersion);
|
||||||
|
|
||||||
|
return {
|
||||||
|
package: pkg,
|
||||||
|
current,
|
||||||
|
update: best?.version ?? null,
|
||||||
|
latest,
|
||||||
|
sdkConstrained: true,
|
||||||
|
sdkRange: best?.sdkRange ?? null,
|
||||||
|
note: best
|
||||||
|
? `Latest (${latest}) requires incompatible SDK range; best compatible: ${best.version}`
|
||||||
|
: `No version of ${pkg} is compatible with ${sdkPkg.name}@${sdkVersion}`,
|
||||||
|
} satisfies UpdateInfo as UpdateInfo;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
@ -112,10 +112,10 @@ function Details ()
|
||||||
{!!data.update && <Button onAction={e => update.mutate()} className='gap-2' style='warning' id='install-btn' >
|
{!!data.update && <Button onAction={e => update.mutate()} className='gap-2' style='warning' id='install-btn' >
|
||||||
{update.isPending ? <span className="loading loading-spinner loading-lg"></span> : <CircleFadingArrowUp />} Update
|
{update.isPending ? <span className="loading loading-spinner loading-lg"></span> : <CircleFadingArrowUp />} Update
|
||||||
</Button>}
|
</Button>}
|
||||||
<Button onAction={e => uninstall.mutate()} className='gap-2' style='accent' id='install-btn' >
|
<Button onAction={e => uninstall.mutate()} className='gap-2' style='accent' id='uninstall-btn' >
|
||||||
{uninstall.isPending ? <span className="loading loading-spinner loading-lg"></span> : <Trash />} Uninstall
|
{uninstall.isPending ? <span className="loading loading-spinner loading-lg"></span> : <Trash />} Uninstall
|
||||||
</Button>
|
</Button>
|
||||||
<Button external onAction={e => { navigate({ to: '/settings/plugin/$source', params: { source: encodeURIComponent(plugin) } }); }} className='gap-2' style='info' id='install-btn' >
|
<Button external onAction={e => { navigate({ to: '/settings/plugin/$source', params: { source: encodeURIComponent(plugin) } }); }} className='gap-2' style='info' id='plugin-settings-btn' >
|
||||||
<Settings /> Settings
|
<Settings /> Settings
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import DotsLoading from '@/mainview/components/backgrounds/dots';
|
import DotsLoading from '@/mainview/components/backgrounds/dots';
|
||||||
import LoadMoreButton from '@/mainview/components/LoadMoreButton';
|
import LoadMoreButton from '@/mainview/components/LoadMoreButton';
|
||||||
|
import { Button } from '@/mainview/components/options/Button';
|
||||||
import { SideDownloadFilters } from '@/mainview/components/SideFilters';
|
import { SideDownloadFilters } from '@/mainview/components/SideFilters';
|
||||||
import { downloadLookupFiltersQuery, downloadsLookupQuery } from '@/mainview/scripts/queries/romm';
|
import { downloadLookupFiltersQuery, downloadsLookupQuery } from '@/mainview/scripts/queries/romm';
|
||||||
import { scrollIntoViewHandler } from '@/mainview/scripts/utils';
|
import { scrollIntoViewHandler } from '@/mainview/scripts/utils';
|
||||||
|
|
@ -7,7 +8,7 @@ import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-naviga
|
||||||
import { DownloadLookupEntry, DownloadsLookupFilter } from '@simeonradivoev/gameflow-sdk/shared';
|
import { DownloadLookupEntry, DownloadsLookupFilter } from '@simeonradivoev/gameflow-sdk/shared';
|
||||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||||
import { DownloadIcon, Eye, MessageCircle, Save, Star } from 'lucide-react';
|
import { ArrowRight, DownloadIcon, Eye, MessageCircle, Puzzle, Save, Star } from 'lucide-react';
|
||||||
import prettyBytes from 'pretty-bytes';
|
import prettyBytes from 'pretty-bytes';
|
||||||
import { useSessionStorage } from 'usehooks-ts';
|
import { useSessionStorage } from 'usehooks-ts';
|
||||||
|
|
||||||
|
|
@ -49,6 +50,7 @@ function Downloads (data: {
|
||||||
pages: {
|
pages: {
|
||||||
data: DownloadLookupEntry[];
|
data: DownloadLookupEntry[];
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
|
hadMatchers: boolean;
|
||||||
nextPage: number;
|
nextPage: number;
|
||||||
}[];
|
}[];
|
||||||
hasNextPage: boolean,
|
hasNextPage: boolean,
|
||||||
|
|
@ -58,12 +60,15 @@ function Downloads (data: {
|
||||||
error: string | undefined;
|
error: string | undefined;
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
const navigate = useNavigate();
|
||||||
const { ref, focusKey } = useFocusable({ focusKey: 'downloads-list' });
|
const { ref, focusKey } = useFocusable({ focusKey: 'downloads-list' });
|
||||||
return <ul ref={ref} className='grid ml-12 h-fit sm:gap-2 md:gap-5 auto-rows-[10rem] grid-cols-1 md:grid-cols-2 lg:grid-cols-3'>
|
return <ul ref={ref} className='grid ml-12 h-fit sm:gap-2 md:gap-5 auto-rows-[10rem] grid-cols-1 md:grid-cols-2 lg:grid-cols-3'>
|
||||||
<FocusContext value={focusKey}>
|
<FocusContext value={focusKey}>
|
||||||
{data.pages.flatMap((page, p) => page.data.map((match, i) => <Download focusKey={`dl-${p}-${i}`} key={match.id} match={match} />))}
|
{data.pages.flatMap((page, p) => page.data.map((match, i) => <Download focusKey={`dl-${p}-${i}`} key={match.id} match={match} />))}
|
||||||
{data.hasNextPage && <LoadMoreButton
|
{!data.pages[0].hadMatchers && <div className='flex justify-center items-center gap-2 font-semibold text-2xl col-span-3'><Button id='install-plugins-btn' className='gap-2 text-2xl!' onAction={e => navigate({ to: '/store/tab/plugins' })}><Puzzle />Get Donwloads Plugin <ArrowRight /></Button></div>}
|
||||||
|
{data.hasNextPage && data.pages[0].hadMatchers && <LoadMoreButton
|
||||||
isFetching={data.isFetchingNextPage || data.isFetching}
|
isFetching={data.isFetchingNextPage || data.isFetching}
|
||||||
|
hidden
|
||||||
onAction={() =>
|
onAction={() =>
|
||||||
{
|
{
|
||||||
if (data.isFetchingNextPage || data.isFetching)
|
if (data.isFetchingNextPage || data.isFetching)
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,12 @@ export const addManualGameMutation = mutationOptions({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const downloadsLookupQuery = (filter: DownloadsLookupFilter) => infiniteQueryOptions<{ data: DownloadLookupEntry[], totalCount: number, nextPage: number; }>({
|
export const downloadsLookupQuery = (filter: DownloadsLookupFilter) => infiniteQueryOptions<{
|
||||||
|
data: DownloadLookupEntry[],
|
||||||
|
totalCount: number,
|
||||||
|
nextPage: number;
|
||||||
|
hadMatchers: boolean;
|
||||||
|
}>({
|
||||||
initialPageParam: 1,
|
initialPageParam: 1,
|
||||||
queryKey: ["downloads", filter],
|
queryKey: ["downloads", filter],
|
||||||
getNextPageParam: (lastPage, pages) => lastPage.nextPage,
|
getNextPageParam: (lastPage, pages) => lastPage.nextPage,
|
||||||
|
|
@ -304,7 +309,7 @@ export const downloadsLookupQuery = (filter: DownloadsLookupFilter) => infiniteQ
|
||||||
const pageParam = params.pageParam as number;
|
const pageParam = params.pageParam as number;
|
||||||
const { data, error } = await rommApi.api.romm.downloads.lookup.get({ query: { ...filter, page: pageParam } });
|
const { data, error } = await rommApi.api.romm.downloads.lookup.get({ query: { ...filter, page: pageParam } });
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return { data: data.matches, totalCount: data.totalCount, nextPage: pageParam + 1 };
|
return { data: data.matches, totalCount: data.totalCount, hadMatchers: data.hadMatchers, nextPage: pageParam + 1 };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
0
src/packages/gameflow-sdk/build.ts
Normal file → Executable file
0
src/packages/gameflow-sdk/build.ts
Normal file → Executable file
Loading…
Add table
Add a link
Reference in a new issue