mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
feat: migrating repacks to a worker
This commit is contained in:
parent
eb3eb88f23
commit
5a85033486
19 changed files with 204 additions and 147 deletions
|
@ -2,7 +2,9 @@ import { getSteamAppAsset } from "@main/helpers";
|
||||||
import type { CatalogueEntry, GameShop } from "@types";
|
import type { CatalogueEntry, GameShop } from "@types";
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { SearchEngine, requestSteam250 } from "@main/services";
|
import { requestSteam250 } from "@main/services";
|
||||||
|
import { repacksWorker } from "@main/workers";
|
||||||
|
import { formatName } from "@shared";
|
||||||
|
|
||||||
const resultSize = 12;
|
const resultSize = 12;
|
||||||
|
|
||||||
|
@ -17,7 +19,10 @@ const getCatalogue = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, objectID } = trendingGames[i]!;
|
const { title, objectID } = trendingGames[i]!;
|
||||||
const repacks = SearchEngine.searchRepacks(title);
|
const repacks = await repacksWorker.run(
|
||||||
|
{ query: formatName(title) },
|
||||||
|
{ name: "search" }
|
||||||
|
);
|
||||||
|
|
||||||
const catalogueEntry = {
|
const catalogueEntry = {
|
||||||
objectID,
|
objectID,
|
||||||
|
|
|
@ -1,22 +1,29 @@
|
||||||
import type { CatalogueEntry } from "@types";
|
import type { CatalogueEntry } from "@types";
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
import { repacksWorker, steamGamesWorker } from "@main/workers";
|
||||||
import { convertSteamGameToCatalogueEntry } from "../helpers/search-games";
|
import { convertSteamGameToCatalogueEntry } from "../helpers/search-games";
|
||||||
import { steamGamesWorker } from "@main/workers";
|
|
||||||
|
|
||||||
const getGames = async (
|
const getGames = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
take = 12,
|
take = 12,
|
||||||
cursor = 0
|
cursor = 0
|
||||||
): Promise<{ results: CatalogueEntry[]; cursor: number }> => {
|
): Promise<{ results: CatalogueEntry[]; cursor: number }> => {
|
||||||
const results = await steamGamesWorker.run(
|
const steamGames = await steamGamesWorker.run(
|
||||||
{ limit: take, offset: cursor },
|
{ limit: take, offset: cursor },
|
||||||
{ name: "list" }
|
{ name: "list" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const entries = await repacksWorker.run(
|
||||||
|
steamGames.map((game) => convertSteamGameToCatalogueEntry(game)),
|
||||||
|
{
|
||||||
|
name: "findRepacksForCatalogueEntries",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
results: results.map((result) => convertSteamGameToCatalogueEntry(result)),
|
results: entries,
|
||||||
cursor: cursor + results.length,
|
cursor: cursor + entries.length,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { shuffle } from "lodash-es";
|
import { shuffle } from "lodash-es";
|
||||||
|
|
||||||
import { SearchEngine, getSteam250List } from "@main/services";
|
import { getSteam250List } from "@main/services";
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { searchSteamGames } from "../helpers/search-games";
|
import { searchSteamGames } from "../helpers/search-games";
|
||||||
import type { Steam250Game } from "@types";
|
import type { Steam250Game } from "@types";
|
||||||
|
import { repacksWorker } from "@main/workers";
|
||||||
|
import { formatName } from "@shared";
|
||||||
|
|
||||||
const state = { games: Array<Steam250Game>(), index: 0 };
|
const state = { games: Array<Steam250Game>(), index: 0 };
|
||||||
|
|
||||||
|
@ -15,7 +17,10 @@ const filterGames = async (games: Steam250Game[]) => {
|
||||||
const catalogue = await searchSteamGames({ query: game.title });
|
const catalogue = await searchSteamGames({ query: game.title });
|
||||||
|
|
||||||
if (catalogue.length) {
|
if (catalogue.length) {
|
||||||
const repacks = SearchEngine.searchRepacks(catalogue[0].title);
|
const repacks = await repacksWorker.run(
|
||||||
|
{ query: formatName(catalogue[0].title) },
|
||||||
|
{ name: "search" }
|
||||||
|
);
|
||||||
|
|
||||||
if (repacks.length) {
|
if (repacks.length) {
|
||||||
results.push(game);
|
results.push(game);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { SearchEngine } from "@main/services";
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
import { repacksWorker } from "@main/workers";
|
||||||
|
|
||||||
const searchGameRepacks = (
|
const searchGameRepacks = (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
query: string
|
query: string
|
||||||
) => SearchEngine.searchRepacks(query);
|
) => repacksWorker.run({ query }, { name: "search" });
|
||||||
|
|
||||||
registerEvent("searchGameRepacks", searchGameRepacks);
|
registerEvent("searchGameRepacks", searchGameRepacks);
|
||||||
|
|
|
@ -5,7 +5,8 @@ import { DownloadSource, Repack } from "@main/entity";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
import { downloadSourceSchema } from "../helpers/validators";
|
import { downloadSourceSchema } from "../helpers/validators";
|
||||||
import { SearchEngine } from "@main/services";
|
import { repackRepository } from "@main/repository";
|
||||||
|
import { repacksWorker } from "@main/workers";
|
||||||
|
|
||||||
const addDownloadSource = async (
|
const addDownloadSource = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
@ -49,7 +50,15 @@ const addDownloadSource = async (
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await SearchEngine.updateRepacks();
|
repackRepository
|
||||||
|
.find({
|
||||||
|
order: {
|
||||||
|
createdAt: "DESC",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((repacks) => {
|
||||||
|
repacksWorker.run(repacks, { name: "setRepacks" });
|
||||||
|
});
|
||||||
|
|
||||||
return downloadSource;
|
return downloadSource;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
import { downloadSourceRepository } from "@main/repository";
|
import { downloadSourceRepository, repackRepository } from "@main/repository";
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { SearchEngine } from "@main/services";
|
import { repacksWorker } from "@main/workers";
|
||||||
|
|
||||||
const removeDownloadSource = async (
|
const removeDownloadSource = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
id: number
|
id: number
|
||||||
) => {
|
) => {
|
||||||
await downloadSourceRepository.delete(id);
|
await downloadSourceRepository.delete(id);
|
||||||
await SearchEngine.updateRepacks();
|
|
||||||
|
repackRepository
|
||||||
|
.find({
|
||||||
|
order: {
|
||||||
|
createdAt: "DESC",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((repacks) => {
|
||||||
|
repacksWorker.run(repacks, { name: "setRepacks" });
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("removeDownloadSource", removeDownloadSource);
|
registerEvent("removeDownloadSource", removeDownloadSource);
|
||||||
|
|
|
@ -1,22 +1,9 @@
|
||||||
import { z } from "zod";
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { downloadSourceRepository } from "@main/repository";
|
import { downloadSourceRepository } from "@main/repository";
|
||||||
|
import { downloadSourceSchema } from "../helpers/validators";
|
||||||
const downloadSourceSchema = z.object({
|
import { repacksWorker } from "@main/workers";
|
||||||
name: z.string().max(255),
|
import { GameRepack } from "@types";
|
||||||
downloads: z.array(
|
|
||||||
z.object({
|
|
||||||
title: z.string().max(255),
|
|
||||||
objectId: z.string().max(255).nullable(),
|
|
||||||
shop: z.enum(["steam"]).nullable(),
|
|
||||||
downloaders: z.array(z.enum(["real_debrid", "torrent"])),
|
|
||||||
uris: z.array(z.string()),
|
|
||||||
uploadDate: z.string().max(255),
|
|
||||||
fileSize: z.string().max(255),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const validateDownloadSource = async (
|
const validateDownloadSource = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
@ -27,13 +14,26 @@ const validateDownloadSource = async (
|
||||||
const source = downloadSourceSchema.parse(response.data);
|
const source = downloadSourceSchema.parse(response.data);
|
||||||
|
|
||||||
const existingSource = await downloadSourceRepository.findOne({
|
const existingSource = await downloadSourceRepository.findOne({
|
||||||
where: [{ url }, { name: source.name }],
|
where: { url },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingSource?.url === url)
|
if (existingSource)
|
||||||
throw new Error("Source with the same url already exists");
|
throw new Error("Source with the same url already exists");
|
||||||
|
|
||||||
return { name: source.name, downloadCount: source.downloads.length };
|
const repacks = (await repacksWorker.run(undefined, {
|
||||||
|
name: "list",
|
||||||
|
})) as GameRepack[];
|
||||||
|
|
||||||
|
console.log(repacks);
|
||||||
|
|
||||||
|
const existingUris = source.downloads
|
||||||
|
.flatMap((download) => download.uris)
|
||||||
|
.filter((uri) => repacks.some((repack) => repack.magnet === uri));
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: source.name,
|
||||||
|
downloadCount: source.downloads.length - existingUris.length,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("validateDownloadSource", validateDownloadSource);
|
registerEvent("validateDownloadSource", validateDownloadSource);
|
||||||
|
|
|
@ -4,8 +4,7 @@ import flexSearch from "flexsearch";
|
||||||
import type { GameShop, CatalogueEntry, SteamGame } from "@types";
|
import type { GameShop, CatalogueEntry, SteamGame } from "@types";
|
||||||
|
|
||||||
import { getSteamAppAsset } from "@main/helpers";
|
import { getSteamAppAsset } from "@main/helpers";
|
||||||
import { SearchEngine } from "@main/services";
|
import { repacksWorker, steamGamesWorker } from "@main/workers";
|
||||||
import { steamGamesWorker } from "@main/workers";
|
|
||||||
|
|
||||||
export interface SearchGamesArgs {
|
export interface SearchGamesArgs {
|
||||||
query?: string;
|
query?: string;
|
||||||
|
@ -14,24 +13,31 @@ export interface SearchGamesArgs {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const convertSteamGameToCatalogueEntry = (
|
export const convertSteamGameToCatalogueEntry = (
|
||||||
result: SteamGame
|
game: SteamGame
|
||||||
): CatalogueEntry => {
|
): CatalogueEntry => ({
|
||||||
return {
|
objectID: String(game.id),
|
||||||
objectID: String(result.id),
|
title: game.name,
|
||||||
title: result.name,
|
shop: "steam" as GameShop,
|
||||||
shop: "steam" as GameShop,
|
cover: getSteamAppAsset("library", String(game.id)),
|
||||||
cover: getSteamAppAsset("library", String(result.id)),
|
repacks: [],
|
||||||
repacks: SearchEngine.searchRepacks(result.name),
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const searchSteamGames = async (
|
export const searchSteamGames = async (
|
||||||
options: flexSearch.SearchOptions
|
options: flexSearch.SearchOptions
|
||||||
): Promise<CatalogueEntry[]> => {
|
): Promise<CatalogueEntry[]> => {
|
||||||
const steamGames = await steamGamesWorker.run(options, { name: "search" });
|
const steamGames = (await steamGamesWorker.run(options, {
|
||||||
|
name: "search",
|
||||||
|
})) as SteamGame[];
|
||||||
|
|
||||||
|
const result = await repacksWorker.run(
|
||||||
|
steamGames.map((game) => convertSteamGameToCatalogueEntry(game)),
|
||||||
|
{
|
||||||
|
name: "findRepacksForCatalogueEntries",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return orderBy(
|
return orderBy(
|
||||||
steamGames.map((result) => convertSteamGameToCatalogueEntry(result)),
|
result,
|
||||||
[({ repacks }) => repacks.length, "repacks"],
|
[({ repacks }) => repacks.length, "repacks"],
|
||||||
["desc"]
|
["desc"]
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@ const getLibrary = async () =>
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
createdAt: "desc",
|
updatedAt: "desc",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import { DownloadManager, SearchEngine, startMainLoop } from "./services";
|
import { DownloadManager, startMainLoop } from "./services";
|
||||||
import { gameRepository, userPreferencesRepository } from "./repository";
|
import {
|
||||||
|
gameRepository,
|
||||||
|
repackRepository,
|
||||||
|
userPreferencesRepository,
|
||||||
|
} from "./repository";
|
||||||
import { UserPreferences } from "./entity";
|
import { UserPreferences } from "./entity";
|
||||||
import { RealDebridClient } from "./services/real-debrid";
|
import { RealDebridClient } from "./services/real-debrid";
|
||||||
import { Not } from "typeorm";
|
import { Not } from "typeorm";
|
||||||
|
import { repacksWorker } from "./workers";
|
||||||
|
|
||||||
startMainLoop();
|
startMainLoop();
|
||||||
|
|
||||||
|
@ -21,7 +26,16 @@ const loadState = async (userPreferences: UserPreferences | null) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (game) DownloadManager.startDownload(game);
|
if (game) DownloadManager.startDownload(game);
|
||||||
await SearchEngine.updateRepacks();
|
|
||||||
|
repackRepository
|
||||||
|
.find({
|
||||||
|
order: {
|
||||||
|
createdAt: "DESC",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((repacks) => {
|
||||||
|
repacksWorker.run(repacks, { name: "setRepacks" });
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
userPreferencesRepository
|
userPreferencesRepository
|
||||||
|
|
|
@ -7,4 +7,3 @@ export * from "./download-manager";
|
||||||
export * from "./how-long-to-beat";
|
export * from "./how-long-to-beat";
|
||||||
export * from "./process-watcher";
|
export * from "./process-watcher";
|
||||||
export * from "./main-loop";
|
export * from "./main-loop";
|
||||||
export * from "./search-engine";
|
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import flexSearch from "flexsearch";
|
|
||||||
|
|
||||||
import { repackRepository } from "@main/repository";
|
|
||||||
import { formatName } from "@shared";
|
|
||||||
import type { GameRepack } from "@types";
|
|
||||||
|
|
||||||
export class SearchEngine {
|
|
||||||
public static repacks: GameRepack[] = [];
|
|
||||||
|
|
||||||
private static repacksIndex = new flexSearch.Index();
|
|
||||||
|
|
||||||
public static searchRepacks(query: string): GameRepack[] {
|
|
||||||
return this.repacksIndex
|
|
||||||
.search(formatName(query))
|
|
||||||
.map((index) => this.repacks[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async updateRepacks() {
|
|
||||||
this.repacks = [];
|
|
||||||
|
|
||||||
const repacks = await repackRepository.find({
|
|
||||||
order: {
|
|
||||||
createdAt: "desc",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < repacks.length; i++) {
|
|
||||||
const repack = repacks[i];
|
|
||||||
|
|
||||||
const formattedTitle = formatName(repack.title);
|
|
||||||
|
|
||||||
this.repacks = [...this.repacks, { ...repack, title: formattedTitle }];
|
|
||||||
this.repacksIndex.add(i, formattedTitle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import steamGamesWorkerPath from "./steam-games.worker?modulePath";
|
import steamGamesWorkerPath from "./steam-games.worker?modulePath";
|
||||||
|
import repacksWorkerPath from "./repacks.worker?modulePath";
|
||||||
|
|
||||||
import Piscina from "piscina";
|
import Piscina from "piscina";
|
||||||
|
|
||||||
|
@ -11,3 +12,7 @@ export const steamGamesWorker = new Piscina({
|
||||||
steamGamesPath: path.join(seedsPath, "steam-games.json"),
|
steamGamesPath: path.join(seedsPath, "steam-games.json"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const repacksWorker = new Piscina({
|
||||||
|
filename: repacksWorkerPath,
|
||||||
|
});
|
||||||
|
|
31
src/main/workers/repacks.worker.ts
Normal file
31
src/main/workers/repacks.worker.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { formatName } from "@shared";
|
||||||
|
import { CatalogueEntry, GameRepack } from "@types";
|
||||||
|
import flexSearch from "flexsearch";
|
||||||
|
|
||||||
|
const repacksIndex = new flexSearch.Index();
|
||||||
|
|
||||||
|
const state: { repacks: GameRepack[] } = { repacks: [] };
|
||||||
|
|
||||||
|
export const setRepacks = (repacks: GameRepack[]) => {
|
||||||
|
state.repacks = repacks;
|
||||||
|
|
||||||
|
for (let i = 0; i < repacks.length; i++) {
|
||||||
|
const repack = repacks[i];
|
||||||
|
|
||||||
|
const formattedTitle = formatName(repack.title);
|
||||||
|
|
||||||
|
repacksIndex.add(i, formattedTitle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const search = (options: flexSearch.SearchOptions) =>
|
||||||
|
repacksIndex.search(options).map((index) => state.repacks[index]);
|
||||||
|
|
||||||
|
export const list = () => state.repacks;
|
||||||
|
|
||||||
|
export const findRepacksForCatalogueEntries = (entries: CatalogueEntry[]) => {
|
||||||
|
return entries.map((entry) => {
|
||||||
|
const repacks = search({ query: formatName(entry.title) });
|
||||||
|
return { ...entry, repacks };
|
||||||
|
});
|
||||||
|
};
|
|
@ -25,7 +25,7 @@ for (let i = 0; i < steamGames.length; i++) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const search = (options: flexSearch.SearchOptions) => {
|
export const search = (options: flexSearch.SearchOptions) => {
|
||||||
const results = steamGamesIndex.search(options.query ?? "", options);
|
const results = steamGamesIndex.search(options);
|
||||||
const games = results.map((index) => steamGames[index]);
|
const games = results.map((index) => steamGames[index]);
|
||||||
|
|
||||||
return orderBy(games, ["name"], ["asc"]);
|
return orderBy(games, ["name"], ["asc"]);
|
||||||
|
|
|
@ -40,7 +40,3 @@ export const buildGameDetailsPath = (
|
||||||
const searchParams = new URLSearchParams({ title: game.title, ...params });
|
const searchParams = new URLSearchParams({ title: game.title, ...params });
|
||||||
return `/game/${game.shop}/${game.objectID}?${searchParams.toString()}`;
|
return `/game/${game.shop}/${game.objectID}?${searchParams.toString()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const numberFormatter = new Intl.NumberFormat("en-US", {
|
|
||||||
maximumSignificantDigits: 3,
|
|
||||||
});
|
|
||||||
|
|
|
@ -224,47 +224,51 @@ export function HeroPanelActions() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (game) {
|
||||||
<>
|
return (
|
||||||
{game?.progress === 1 ? (
|
<>
|
||||||
<>
|
{game?.progress === 1 ? (
|
||||||
<BinaryNotFoundModal
|
<>
|
||||||
visible={showBinaryNotFoundModal}
|
<BinaryNotFoundModal
|
||||||
onClose={() => setShowBinaryNotFoundModal(false)}
|
visible={showBinaryNotFoundModal}
|
||||||
/>
|
onClose={() => setShowBinaryNotFoundModal(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={openGameInstaller}
|
||||||
|
theme="outline"
|
||||||
|
disabled={deleting || isGameRunning}
|
||||||
|
className={styles.heroPanelAction}
|
||||||
|
>
|
||||||
|
{t("install")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
toggleGameOnLibraryButton
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isGameRunning ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={openGameInstaller}
|
onClick={closeGame}
|
||||||
|
theme="outline"
|
||||||
|
disabled={deleting}
|
||||||
|
className={styles.heroPanelAction}
|
||||||
|
>
|
||||||
|
{t("close")}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={openGame}
|
||||||
theme="outline"
|
theme="outline"
|
||||||
disabled={deleting || isGameRunning}
|
disabled={deleting || isGameRunning}
|
||||||
className={styles.heroPanelAction}
|
className={styles.heroPanelAction}
|
||||||
>
|
>
|
||||||
{t("install")}
|
{t("play")}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
)}
|
||||||
) : (
|
</>
|
||||||
toggleGameOnLibraryButton
|
);
|
||||||
)}
|
}
|
||||||
|
|
||||||
{isGameRunning ? (
|
return toggleGameOnLibraryButton;
|
||||||
<Button
|
|
||||||
onClick={closeGame}
|
|
||||||
theme="outline"
|
|
||||||
disabled={deleting}
|
|
||||||
className={styles.heroPanelAction}
|
|
||||||
>
|
|
||||||
{t("close")}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
onClick={openGame}
|
|
||||||
theme="outline"
|
|
||||||
disabled={deleting || isGameRunning}
|
|
||||||
className={styles.heroPanelAction}
|
|
||||||
>
|
|
||||||
{t("play")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { Button, Modal, TextField } from "@renderer/components";
|
import { Button, Modal, TextField } from "@renderer/components";
|
||||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import * as styles from "./settings-download-sources.css";
|
import * as styles from "./settings-download-sources.css";
|
||||||
import { numberFormatter } from "@renderer/helpers";
|
|
||||||
|
|
||||||
interface AddDownloadSourceModalProps {
|
interface AddDownloadSourceModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
@ -25,6 +24,12 @@ export function AddDownloadSourceModal({
|
||||||
downloadCount: number;
|
downloadCount: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue("");
|
||||||
|
setIsLoading(false);
|
||||||
|
setValidationResult(null);
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
const handleValidateDownloadSource = async () => {
|
const handleValidateDownloadSource = async () => {
|
||||||
|
@ -32,8 +37,8 @@ export function AddDownloadSourceModal({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await window.electron.validateDownloadSource(value);
|
const result = await window.electron.validateDownloadSource(value);
|
||||||
setValidationResult(result);
|
|
||||||
console.log(result);
|
console.log(result);
|
||||||
|
setValidationResult(result);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
@ -96,7 +101,8 @@ export function AddDownloadSourceModal({
|
||||||
>
|
>
|
||||||
<h4>{validationResult?.name}</h4>
|
<h4>{validationResult?.name}</h4>
|
||||||
<small>
|
<small>
|
||||||
Found {numberFormatter.format(validationResult?.downloadCount)}{" "}
|
Found{" "}
|
||||||
|
{validationResult?.downloadCount.toLocaleString(undefined)}{" "}
|
||||||
download options
|
download options
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,6 @@ import type { DownloadSource } from "@types";
|
||||||
import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react";
|
import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react";
|
||||||
import { AddDownloadSourceModal } from "./add-download-source-modal";
|
import { AddDownloadSourceModal } from "./add-download-source-modal";
|
||||||
import { useToast } from "@renderer/hooks";
|
import { useToast } from "@renderer/hooks";
|
||||||
import { numberFormatter } from "@renderer/helpers";
|
|
||||||
|
|
||||||
export function SettingsDownloadSources() {
|
export function SettingsDownloadSources() {
|
||||||
const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] =
|
const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] =
|
||||||
|
@ -53,6 +52,16 @@ export function SettingsDownloadSources() {
|
||||||
{t("download_sources_description")}
|
{t("download_sources_description")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
theme="outline"
|
||||||
|
style={{ alignSelf: "flex-start" }}
|
||||||
|
onClick={() => setShowAddDownloadSourceModal(true)}
|
||||||
|
>
|
||||||
|
<PlusCircleIcon />
|
||||||
|
{t("add_download_source")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
{downloadSources.map((downloadSource) => (
|
{downloadSources.map((downloadSource) => (
|
||||||
<div key={downloadSource.id} className={styles.downloadSourceItem}>
|
<div key={downloadSource.id} className={styles.downloadSourceItem}>
|
||||||
<div className={styles.downloadSourceItemHeader}>
|
<div className={styles.downloadSourceItemHeader}>
|
||||||
|
@ -60,9 +69,7 @@ export function SettingsDownloadSources() {
|
||||||
<small>
|
<small>
|
||||||
{t("download_options", {
|
{t("download_options", {
|
||||||
count: downloadSource.repackCount,
|
count: downloadSource.repackCount,
|
||||||
countFormatted: numberFormatter.format(
|
countFormatted: downloadSource.repackCount.toLocaleString(),
|
||||||
downloadSource.repackCount
|
|
||||||
),
|
|
||||||
})}
|
})}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,16 +94,6 @@ export function SettingsDownloadSources() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
theme="outline"
|
|
||||||
style={{ alignSelf: "flex-start" }}
|
|
||||||
onClick={() => setShowAddDownloadSourceModal(true)}
|
|
||||||
>
|
|
||||||
<PlusCircleIcon />
|
|
||||||
{t("add_download_source")}
|
|
||||||
</Button>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue