mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
Merge remote-tracking branch 'origin/main' into feat/add-select-folder-modal-in-game-installation
This commit is contained in:
commit
af715aa110
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -57,6 +57,8 @@ jobs:
|
|||||||
STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }}
|
STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
||||||
|
ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
||||||
|
ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
||||||
|
|
||||||
- name: Create artifact
|
- name: Create artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
@ -89,7 +89,6 @@
|
|||||||
"lottie-react": "^2.4.0",
|
"lottie-react": "^2.4.0",
|
||||||
"parse-torrent": "9.1.5",
|
"parse-torrent": "9.1.5",
|
||||||
"ps-list": "^8.1.1",
|
"ps-list": "^8.1.1",
|
||||||
"qs": "^6.12.0",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^14.1.0",
|
"react-i18next": "^14.1.0",
|
||||||
|
12
src/index.ts
12
src/index.ts
@ -19,7 +19,17 @@ if (process.platform !== "darwin") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.SENTRY_DSN) {
|
if (process.env.SENTRY_DSN) {
|
||||||
init({ dsn: process.env.SENTRY_DSN });
|
init({
|
||||||
|
dsn: process.env.SENTRY_DSN,
|
||||||
|
beforeSend: async (event) => {
|
||||||
|
const userPreferences = await userPreferencesRepository.findOne({
|
||||||
|
where: { id: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userPreferences?.telemetryEnabled) return event;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
i18n.init({
|
i18n.init({
|
||||||
|
@ -121,7 +121,9 @@
|
|||||||
"change": "Update",
|
"change": "Update",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"enable_download_notifications": "When a download is complete",
|
"enable_download_notifications": "When a download is complete",
|
||||||
"enable_repack_list_notifications": "When a new repack is added"
|
"enable_repack_list_notifications": "When a new repack is added",
|
||||||
|
"telemetry": "Telemetry",
|
||||||
|
"telemetry_description": "Enable anonymous usage statistics"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download complete",
|
"download_complete": "Download complete",
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"downloads": "Descargas",
|
"downloads": "Descargas",
|
||||||
"search_results": "Resultados de búsqueda",
|
"search_results": "Resultados de búsqueda",
|
||||||
"settings": "Ajustes",
|
"settings": "Ajustes",
|
||||||
"home": "Hogar"
|
"home": "Início"
|
||||||
},
|
},
|
||||||
"bottom_panel": {
|
"bottom_panel": {
|
||||||
"no_downloads_in_progress": "Sin descargas en progreso",
|
"no_downloads_in_progress": "Sin descargas en progreso",
|
||||||
@ -111,7 +111,9 @@
|
|||||||
"change": "Cambiar",
|
"change": "Cambiar",
|
||||||
"notifications": "Notificaciones",
|
"notifications": "Notificaciones",
|
||||||
"enable_download_notifications": "Cuando se completa una descarga",
|
"enable_download_notifications": "Cuando se completa una descarga",
|
||||||
"enable_repack_list_notifications": "Cuando se añade un repack nuevo"
|
"enable_repack_list_notifications": "Cuando se añade un repack nuevo",
|
||||||
|
"telemetry": "Telemetria",
|
||||||
|
"telemetry_description": "Habilitar estadísticas de uso anónimas"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Descarga completada",
|
"download_complete": "Descarga completada",
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"paused": "{{title}} (En pause)",
|
"paused": "{{title}} (En pause)",
|
||||||
"downloading": "{{title}} ({{percentage}} - Téléchargement en cours…)",
|
"downloading": "{{title}} ({{percentage}} - Téléchargement en cours…)",
|
||||||
"filter": "Filtrer la bibliothèque",
|
"filter": "Filtrer la bibliothèque",
|
||||||
"home": "Maison",
|
"home": "Page d’accueil",
|
||||||
"follow_us": "Suivez-nous"
|
"follow_us": "Suivez-nous"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@ -111,7 +111,9 @@
|
|||||||
"change": "Mettre à jour",
|
"change": "Mettre à jour",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"enable_download_notifications": "Quand un téléchargement est terminé",
|
"enable_download_notifications": "Quand un téléchargement est terminé",
|
||||||
"enable_repack_list_notifications": "Quand une nouvelle réduction est ajoutée"
|
"enable_repack_list_notifications": "Quand une nouvelle réduction est ajoutée",
|
||||||
|
"telemetry": "Télémétrie",
|
||||||
|
"telemetry_description": "Activer les statistiques d'utilisation anonymes"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Téléchargement terminé",
|
"download_complete": "Téléchargement terminé",
|
||||||
|
@ -117,7 +117,9 @@
|
|||||||
"change": "Mudar",
|
"change": "Mudar",
|
||||||
"notifications": "Notificações",
|
"notifications": "Notificações",
|
||||||
"enable_download_notifications": "Quando um download for concluído",
|
"enable_download_notifications": "Quando um download for concluído",
|
||||||
"enable_repack_list_notifications": "Quando a lista de repacks for atualizada"
|
"enable_repack_list_notifications": "Quando a lista de repacks for atualizada",
|
||||||
|
"telemetry": "Telemetria",
|
||||||
|
"telemetry_description": "Habilitar estatísticas de uso anônimas"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download concluído",
|
"download_complete": "Download concluído",
|
||||||
|
@ -23,6 +23,9 @@ export class UserPreferences {
|
|||||||
@Column("boolean", { default: false })
|
@Column("boolean", { default: false })
|
||||||
repackUpdatesNotificationsEnabled: boolean;
|
repackUpdatesNotificationsEnabled: boolean;
|
||||||
|
|
||||||
|
@Column("boolean", { default: true })
|
||||||
|
telemetryEnabled: boolean;
|
||||||
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ -1,29 +1,39 @@
|
|||||||
import type { CatalogueEntry } from "@types";
|
import type { CatalogueEntry, GameShop } from "@types";
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { searchGames } from "../helpers/search-games";
|
import { searchRepacks } from "../helpers/search-games";
|
||||||
import slice from "lodash/slice";
|
import { stateManager } from "@main/state-manager";
|
||||||
|
import { getSteamAppAsset } from "@main/helpers";
|
||||||
|
|
||||||
|
const steamGames = stateManager.getValue("steamGames");
|
||||||
|
|
||||||
const getGames = async (
|
const getGames = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
take?: number,
|
take?: number,
|
||||||
prevCursor = 0
|
cursor = 0
|
||||||
): Promise<{ results: CatalogueEntry[]; cursor: number }> => {
|
): Promise<{ results: CatalogueEntry[]; cursor: number }> => {
|
||||||
let results: CatalogueEntry[] = [];
|
const results: CatalogueEntry[] = [];
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
const batchSize = 100;
|
let i = 0 + cursor;
|
||||||
|
|
||||||
while (results.length < take) {
|
while (results.length < take) {
|
||||||
const games = await searchGames({
|
const game = steamGames[i];
|
||||||
take: batchSize,
|
const repacks = searchRepacks(game.name);
|
||||||
skip: (i + prevCursor) * batchSize,
|
|
||||||
});
|
if (repacks.length) {
|
||||||
results = [...results, ...games.filter((game) => game.repacks.length)];
|
results.push({
|
||||||
|
objectID: String(game.id),
|
||||||
|
title: game.name,
|
||||||
|
shop: "steam" as GameShop,
|
||||||
|
cover: getSteamAppAsset("library", String(game.id)),
|
||||||
|
repacks,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { results: slice(results, 0, take), cursor: prevCursor + i };
|
return { results, cursor: i };
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent(getGames, {
|
registerEvent(getGames, {
|
||||||
|
@ -5,14 +5,13 @@ import type { GameRepack, GameShop, CatalogueEntry } from "@types";
|
|||||||
|
|
||||||
import { formatName, getSteamAppAsset, repackerFormatter } from "@main/helpers";
|
import { formatName, getSteamAppAsset, repackerFormatter } from "@main/helpers";
|
||||||
import { stateManager } from "@main/state-manager";
|
import { stateManager } from "@main/state-manager";
|
||||||
import { steamGameRepository } from "@main/repository";
|
|
||||||
import { FindManyOptions, Like } from "typeorm";
|
|
||||||
import { SteamGame } from "@main/entity";
|
|
||||||
|
|
||||||
const { Index } = flexSearch;
|
const { Index } = flexSearch;
|
||||||
const repacksIndex = new Index();
|
const repacksIndex = new Index();
|
||||||
|
const steamGamesIndex = new Index({ tokenize: "reverse" });
|
||||||
|
|
||||||
const repacks = stateManager.getValue("repacks");
|
const repacks = stateManager.getValue("repacks");
|
||||||
|
const steamGames = stateManager.getValue("steamGames");
|
||||||
|
|
||||||
for (let i = 0; i < repacks.length; i++) {
|
for (let i = 0; i < repacks.length; i++) {
|
||||||
const repack = repacks[i];
|
const repack = repacks[i];
|
||||||
@ -22,9 +21,12 @@ for (let i = 0; i < repacks.length; i++) {
|
|||||||
repacksIndex.add(i, formatName(formatter(repack.title)));
|
repacksIndex.add(i, formatName(formatter(repack.title)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const searchRepacks = (title: string): GameRepack[] => {
|
for (let i = 0; i < steamGames.length; i++) {
|
||||||
const repacks = stateManager.getValue("repacks");
|
const steamGame = steamGames[i];
|
||||||
|
steamGamesIndex.add(i, formatName(steamGame.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const searchRepacks = (title: string): GameRepack[] => {
|
||||||
return orderBy(
|
return orderBy(
|
||||||
repacksIndex
|
repacksIndex
|
||||||
.search(formatName(title))
|
.search(formatName(title))
|
||||||
@ -45,34 +47,21 @@ export const searchGames = async ({
|
|||||||
take,
|
take,
|
||||||
skip,
|
skip,
|
||||||
}: SearchGamesArgs): Promise<CatalogueEntry[]> => {
|
}: SearchGamesArgs): Promise<CatalogueEntry[]> => {
|
||||||
const options: FindManyOptions<SteamGame> = {};
|
const results = steamGamesIndex
|
||||||
|
.search(formatName(query || ""), { limit: take, offset: skip })
|
||||||
|
.map((index) => {
|
||||||
|
const result = steamGames.at(index as number)!;
|
||||||
|
|
||||||
if (query) {
|
return {
|
||||||
options.where = {
|
objectID: String(result.id),
|
||||||
name: query ? Like(`%${formatName(query)}%`) : undefined,
|
title: result.name,
|
||||||
};
|
shop: "steam" as GameShop,
|
||||||
}
|
cover: getSteamAppAsset("library", String(result.id)),
|
||||||
|
repacks: searchRepacks(result.name),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const steamResults = await steamGameRepository.find({
|
return Promise.all(results).then((resultsWithRepacks) =>
|
||||||
...options,
|
|
||||||
take,
|
|
||||||
skip,
|
|
||||||
order: { name: "ASC" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const results = steamResults.map((result) => ({
|
|
||||||
objectID: String(result.id),
|
|
||||||
title: result.name,
|
|
||||||
shop: "steam" as GameShop,
|
|
||||||
cover: getSteamAppAsset("library", String(result.id)),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return Promise.all(
|
|
||||||
results.map(async (result) => ({
|
|
||||||
...result,
|
|
||||||
repacks: searchRepacks(result.title),
|
|
||||||
}))
|
|
||||||
).then((resultsWithRepacks) =>
|
|
||||||
orderBy(
|
orderBy(
|
||||||
resultsWithRepacks,
|
resultsWithRepacks,
|
||||||
[({ repacks }) => repacks.length, "repacks"],
|
[({ repacks }) => repacks.length, "repacks"],
|
||||||
|
@ -10,7 +10,8 @@ const addGameToLibrary = async (
|
|||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
objectID: string,
|
objectID: string,
|
||||||
title: string,
|
title: string,
|
||||||
gameShop: GameShop
|
gameShop: GameShop,
|
||||||
|
executablePath: string
|
||||||
) => {
|
) => {
|
||||||
const iconUrl = await getImageBase64(await getSteamGameIconUrl(objectID));
|
const iconUrl = await getImageBase64(await getSteamGameIconUrl(objectID));
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ const addGameToLibrary = async (
|
|||||||
iconUrl,
|
iconUrl,
|
||||||
objectID,
|
objectID,
|
||||||
shop: gameShop,
|
shop: gameShop,
|
||||||
|
executablePath,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
getNewRepacksFromCPG,
|
getNewRepacksFromCPG,
|
||||||
getNewRepacksFromUser,
|
getNewRepacksFromUser,
|
||||||
getNewRepacksFromXatab,
|
getNewRepacksFromXatab,
|
||||||
getNewRepacksFromOnlineFix,
|
// getNewRepacksFromOnlineFix,
|
||||||
readPipe,
|
readPipe,
|
||||||
startProcessWatcher,
|
startProcessWatcher,
|
||||||
writePipe,
|
writePipe,
|
||||||
@ -14,6 +14,7 @@ import {
|
|||||||
gameRepository,
|
gameRepository,
|
||||||
repackRepository,
|
repackRepository,
|
||||||
repackerFriendlyNameRepository,
|
repackerFriendlyNameRepository,
|
||||||
|
steamGameRepository,
|
||||||
userPreferencesRepository,
|
userPreferencesRepository,
|
||||||
} from "./repository";
|
} from "./repository";
|
||||||
import { TorrentClient } from "./services/torrent-client";
|
import { TorrentClient } from "./services/torrent-client";
|
||||||
@ -78,9 +79,9 @@ const checkForNewRepacks = async () => {
|
|||||||
getNewRepacksFromCPG(
|
getNewRepacksFromCPG(
|
||||||
existingRepacks.filter((repack) => repack.repacker === "CPG")
|
existingRepacks.filter((repack) => repack.repacker === "CPG")
|
||||||
),
|
),
|
||||||
getNewRepacksFromOnlineFix(
|
// getNewRepacksFromOnlineFix(
|
||||||
existingRepacks.filter((repack) => repack.repacker === "onlinefix")
|
// existingRepacks.filter((repack) => repack.repacker === "onlinefix")
|
||||||
),
|
// ),
|
||||||
track1337xUsers(existingRepacks),
|
track1337xUsers(existingRepacks),
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
repackRepository.count().then((count) => {
|
repackRepository.count().then((count) => {
|
||||||
@ -104,17 +105,23 @@ const checkForNewRepacks = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadState = async () => {
|
const loadState = async () => {
|
||||||
const [friendlyNames, repacks] = await Promise.all([
|
const [friendlyNames, repacks, steamGames] = await Promise.all([
|
||||||
repackerFriendlyNameRepository.find(),
|
repackerFriendlyNameRepository.find(),
|
||||||
repackRepository.find({
|
repackRepository.find({
|
||||||
order: {
|
order: {
|
||||||
createdAt: "desc",
|
createdAt: "desc",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
steamGameRepository.find({
|
||||||
|
order: {
|
||||||
|
name: "asc",
|
||||||
|
},
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
stateManager.setValue("repackersFriendlyNames", friendlyNames);
|
stateManager.setValue("repackersFriendlyNames", friendlyNames);
|
||||||
stateManager.setValue("repacks", repacks);
|
stateManager.setValue("repacks", repacks);
|
||||||
|
stateManager.setValue("steamGames", steamGames);
|
||||||
|
|
||||||
import("./events");
|
import("./events");
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,6 @@ import path from "node:path";
|
|||||||
import { IsNull, Not } from "typeorm";
|
import { IsNull, Not } from "typeorm";
|
||||||
|
|
||||||
import { gameRepository } from "@main/repository";
|
import { gameRepository } from "@main/repository";
|
||||||
import { GameStatus } from "@main/constants";
|
|
||||||
import { getProcesses } from "@main/helpers";
|
import { getProcesses } from "@main/helpers";
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
|
|
||||||
@ -18,7 +17,6 @@ export const startProcessWatcher = async () => {
|
|||||||
const games = await gameRepository.find({
|
const games = await gameRepository.find({
|
||||||
where: {
|
where: {
|
||||||
executablePath: Not(IsNull()),
|
executablePath: Not(IsNull()),
|
||||||
status: GameStatus.Seeding,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -54,15 +52,16 @@ export const startProcessWatcher = async () => {
|
|||||||
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
|
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gameRepository.update(game.id, {
|
||||||
|
lastTimePlayed: new Date().toUTCString(),
|
||||||
|
});
|
||||||
|
|
||||||
gamesPlaytime.set(game.id, performance.now());
|
gamesPlaytime.set(game.id, performance.now());
|
||||||
await sleep(sleepTime);
|
await sleep(sleepTime);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
gamesPlaytime.set(game.id, performance.now());
|
gamesPlaytime.set(game.id, performance.now());
|
||||||
gameRepository.update(game.id, {
|
|
||||||
lastTimePlayed: new Date().toUTCString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await sleep(sleepTime);
|
await sleep(sleepTime);
|
||||||
continue;
|
continue;
|
||||||
|
@ -2,4 +2,4 @@ export * from "./1337x";
|
|||||||
export * from "./xatab";
|
export * from "./xatab";
|
||||||
export * from "./cpg-repacks";
|
export * from "./cpg-repacks";
|
||||||
export * from "./gog";
|
export * from "./gog";
|
||||||
export * from "./online-fix";
|
// export * from "./online-fix";
|
||||||
|
@ -2,7 +2,6 @@ import { Repack } from "@main/entity";
|
|||||||
import { savePage } from "./helpers";
|
import { savePage } from "./helpers";
|
||||||
import type { GameRepackInput } from "./helpers";
|
import type { GameRepackInput } from "./helpers";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import { stringify } from "qs";
|
|
||||||
import parseTorrent, {
|
import parseTorrent, {
|
||||||
toMagnetURI,
|
toMagnetURI,
|
||||||
Instance as TorrentInstance,
|
Instance as TorrentInstance,
|
||||||
@ -58,8 +57,12 @@ export const getNewRepacksFromOnlineFix = async (
|
|||||||
|
|
||||||
if (!preLogin.field || !preLogin.value) return;
|
if (!preLogin.field || !preLogin.value) return;
|
||||||
|
|
||||||
const tokenField = preLogin.field;
|
const params = new URLSearchParams({
|
||||||
const tokenValue = preLogin.value;
|
login_name: process.env.ONLINEFIX_USERNAME,
|
||||||
|
login_password: process.env.ONLINEFIX_PASSWORD,
|
||||||
|
login: "submit",
|
||||||
|
[preLogin.field]: preLogin.value,
|
||||||
|
});
|
||||||
|
|
||||||
await http
|
await http
|
||||||
.post("https://online-fix.me/", {
|
.post("https://online-fix.me/", {
|
||||||
@ -69,12 +72,7 @@ export const getNewRepacksFromOnlineFix = async (
|
|||||||
Origin: "https://online-fix.me",
|
Origin: "https://online-fix.me",
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
},
|
},
|
||||||
body: stringify({
|
body: params.toString(),
|
||||||
login_name: process.env.ONLINEFIX_USERNAME,
|
|
||||||
login_password: process.env.ONLINEFIX_PASSWORD,
|
|
||||||
login: "submit",
|
|
||||||
[tokenField]: tokenValue,
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
.text();
|
.text();
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import type { Repack, RepackerFriendlyName } from "@main/entity";
|
import type { Repack, RepackerFriendlyName, SteamGame } from "@main/entity";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
repacks: Repack[];
|
repacks: Repack[];
|
||||||
repackersFriendlyNames: RepackerFriendlyName[];
|
repackersFriendlyNames: RepackerFriendlyName[];
|
||||||
|
steamGames: SteamGame[];
|
||||||
eventResults: Map<[string, any[]], any>;
|
eventResults: Map<[string, any[]], any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
repacks: [],
|
repacks: [],
|
||||||
repackersFriendlyNames: [],
|
repackersFriendlyNames: [],
|
||||||
|
steamGames: [],
|
||||||
eventResults: new Map(),
|
eventResults: new Map(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -59,15 +59,26 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
ipcRenderer.invoke("updateUserPreferences", preferences),
|
ipcRenderer.invoke("updateUserPreferences", preferences),
|
||||||
|
|
||||||
/* Library */
|
/* Library */
|
||||||
addGameToLibrary: (objectID: string, title: string, shop: GameShop) =>
|
addGameToLibrary: (
|
||||||
ipcRenderer.invoke("addGameToLibrary", objectID, title, shop),
|
objectID: string,
|
||||||
|
title: string,
|
||||||
|
shop: GameShop,
|
||||||
|
executablePath: string
|
||||||
|
) =>
|
||||||
|
ipcRenderer.invoke(
|
||||||
|
"addGameToLibrary",
|
||||||
|
objectID,
|
||||||
|
title,
|
||||||
|
shop,
|
||||||
|
executablePath
|
||||||
|
),
|
||||||
getLibrary: () => ipcRenderer.invoke("getLibrary"),
|
getLibrary: () => ipcRenderer.invoke("getLibrary"),
|
||||||
getRepackersFriendlyNames: () =>
|
getRepackersFriendlyNames: () =>
|
||||||
ipcRenderer.invoke("getRepackersFriendlyNames"),
|
ipcRenderer.invoke("getRepackersFriendlyNames"),
|
||||||
openGameInstaller: (gameId: number) =>
|
openGameInstaller: (gameId: number) =>
|
||||||
ipcRenderer.invoke("openGameInstaller", gameId),
|
ipcRenderer.invoke("openGameInstaller", gameId),
|
||||||
openGame: (gameId: number, path: string) =>
|
openGame: (gameId: number, executablePath: string) =>
|
||||||
ipcRenderer.invoke("openGame", gameId, path),
|
ipcRenderer.invoke("openGame", gameId, executablePath),
|
||||||
closeGame: (gameId: number) => ipcRenderer.invoke("closeGame", gameId),
|
closeGame: (gameId: number) => ipcRenderer.invoke("closeGame", gameId),
|
||||||
removeGame: (gameId: number) => ipcRenderer.invoke("removeGame", gameId),
|
removeGame: (gameId: number) => ipcRenderer.invoke("removeGame", gameId),
|
||||||
deleteGameFolder: (gameId: number) =>
|
deleteGameFolder: (gameId: number) =>
|
||||||
|
@ -203,7 +203,7 @@ export function Sidebar() {
|
|||||||
className={styles.menuItem({
|
className={styles.menuItem({
|
||||||
active:
|
active:
|
||||||
location.pathname === `/game/${game.shop}/${game.objectID}`,
|
location.pathname === `/game/${game.shop}/${game.objectID}`,
|
||||||
muted: game.status === null || game.status === "cancelled",
|
muted: game.status === "cancelled",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
5
src/renderer/declaration.d.ts
vendored
5
src/renderer/declaration.d.ts
vendored
@ -55,12 +55,13 @@ declare global {
|
|||||||
addGameToLibrary: (
|
addGameToLibrary: (
|
||||||
objectID: string,
|
objectID: string,
|
||||||
title: string,
|
title: string,
|
||||||
shop: GameShop
|
shop: GameShop,
|
||||||
|
executablePath: string
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
getLibrary: () => Promise<Game[]>;
|
getLibrary: () => Promise<Game[]>;
|
||||||
getRepackersFriendlyNames: () => Promise<Record<string, string>>;
|
getRepackersFriendlyNames: () => Promise<Record<string, string>>;
|
||||||
openGameInstaller: (gameId: number) => Promise<boolean>;
|
openGameInstaller: (gameId: number) => Promise<boolean>;
|
||||||
openGame: (gameId: number, path: string) => Promise<void>;
|
openGame: (gameId: number, executablePath: string) => Promise<void>;
|
||||||
closeGame: (gameId: number) => Promise<boolean>;
|
closeGame: (gameId: number) => Promise<boolean>;
|
||||||
removeGame: (gameId: number) => Promise<void>;
|
removeGame: (gameId: number) => Promise<void>;
|
||||||
deleteGameFolder: (gameId: number) => Promise<unknown>;
|
deleteGameFolder: (gameId: number) => Promise<unknown>;
|
||||||
|
@ -32,7 +32,18 @@ import { store } from "./store";
|
|||||||
import * as resources from "@locales";
|
import * as resources from "@locales";
|
||||||
|
|
||||||
if (process.env.SENTRY_DSN) {
|
if (process.env.SENTRY_DSN) {
|
||||||
init({ dsn: process.env.SENTRY_DSN }, reactInit);
|
init(
|
||||||
|
{
|
||||||
|
dsn: process.env.SENTRY_DSN,
|
||||||
|
beforeSend: async (event) => {
|
||||||
|
const userPreferences = await window.electron.getUserPreferences();
|
||||||
|
|
||||||
|
if (userPreferences?.telemetryEnabled) return event;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reactInit
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const router = createHashRouter([
|
const router = createHashRouter([
|
||||||
|
@ -71,6 +71,7 @@ export function Catalogue() {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
borderBottom: `1px solid ${vars.color.borderColor}`,
|
borderBottom: `1px solid ${vars.color.borderColor}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -103,7 +104,6 @@ export function Catalogue() {
|
|||||||
key={game.objectID}
|
key={game.objectID}
|
||||||
game={game}
|
game={game}
|
||||||
onClick={() => handleGameClick(game)}
|
onClick={() => handleGameClick(game)}
|
||||||
disabled={!game.repacks.length}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -83,7 +83,9 @@ export function GameDetails() {
|
|||||||
}, [getGame, gameDownloading?.id]);
|
}, [getGame, gameDownloading?.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setGame(null);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
setIsGamePlaying(false);
|
||||||
dispatch(setHeaderTitle(""));
|
dispatch(setHeaderTitle(""));
|
||||||
|
|
||||||
getRandomGame();
|
getRandomGame();
|
||||||
|
212
src/renderer/pages/game-details/hero-panel-actions.tsx
Normal file
212
src/renderer/pages/game-details/hero-panel-actions.tsx
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react";
|
||||||
|
|
||||||
|
import { Button } from "@renderer/components";
|
||||||
|
import { useDownload, useLibrary } from "@renderer/hooks";
|
||||||
|
import type { Game, ShopDetails } from "@types";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export interface HeroPanelActionsProps {
|
||||||
|
game: Game | null;
|
||||||
|
gameDetails: ShopDetails | null;
|
||||||
|
isGamePlaying: boolean;
|
||||||
|
isGameDownloading: boolean;
|
||||||
|
openRepacksModal: () => void;
|
||||||
|
openBinaryNotFoundModal: () => void;
|
||||||
|
getGame: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HeroPanelActions({
|
||||||
|
game,
|
||||||
|
gameDetails,
|
||||||
|
isGamePlaying,
|
||||||
|
isGameDownloading,
|
||||||
|
openRepacksModal,
|
||||||
|
openBinaryNotFoundModal,
|
||||||
|
getGame,
|
||||||
|
}: HeroPanelActionsProps) {
|
||||||
|
const [toggleLibraryGameDisabled, setToggleLibraryGameDisabled] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
resumeDownload,
|
||||||
|
pauseDownload,
|
||||||
|
cancelDownload,
|
||||||
|
removeGame,
|
||||||
|
isGameDeleting,
|
||||||
|
} = useDownload();
|
||||||
|
|
||||||
|
const { updateLibrary } = useLibrary();
|
||||||
|
|
||||||
|
const { t } = useTranslation("game_details");
|
||||||
|
|
||||||
|
const selectGameExecutable = async () => {
|
||||||
|
return window.electron
|
||||||
|
.showOpenDialog({
|
||||||
|
properties: ["openFile"],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "Game executable",
|
||||||
|
extensions: window.electron.platform === "win32" ? ["exe"] : [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.then(({ filePaths }) => {
|
||||||
|
if (filePaths && filePaths.length > 0) {
|
||||||
|
return filePaths[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleGameOnLibrary = async () => {
|
||||||
|
setToggleLibraryGameDisabled(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (game) {
|
||||||
|
await removeGame(game.id);
|
||||||
|
} else {
|
||||||
|
const gameExecutablePath = await selectGameExecutable();
|
||||||
|
|
||||||
|
await window.electron.addGameToLibrary(
|
||||||
|
gameDetails.objectID,
|
||||||
|
gameDetails.name,
|
||||||
|
"steam",
|
||||||
|
gameExecutablePath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLibrary();
|
||||||
|
getGame();
|
||||||
|
} finally {
|
||||||
|
setToggleLibraryGameDisabled(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openGameInstaller = () => {
|
||||||
|
window.electron.openGameInstaller(game.id).then((isBinaryInPath) => {
|
||||||
|
if (!isBinaryInPath) openBinaryNotFoundModal();
|
||||||
|
updateLibrary();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const openGame = async () => {
|
||||||
|
if (game.executablePath) {
|
||||||
|
window.electron.openGame(game.id, game.executablePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game?.executablePath) {
|
||||||
|
window.electron.openGame(game.id, game.executablePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gameExecutablePath = await selectGameExecutable();
|
||||||
|
window.electron.openGame(game.id, gameExecutablePath);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeGame = () => window.electron.closeGame(game.id);
|
||||||
|
|
||||||
|
const deleting = isGameDeleting(game?.id);
|
||||||
|
|
||||||
|
const toggleGameOnLibraryButton = (
|
||||||
|
<Button
|
||||||
|
theme="outline"
|
||||||
|
disabled={!gameDetails || toggleLibraryGameDisabled}
|
||||||
|
onClick={toggleGameOnLibrary}
|
||||||
|
>
|
||||||
|
{game ? <NoEntryIcon /> : <PlusCircleIcon />}
|
||||||
|
{game ? t("remove_from_library") : t("add_to_library")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isGameDownloading) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={() => pauseDownload(game.id)} theme="outline">
|
||||||
|
{t("pause")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => cancelDownload(game.id)} theme="outline">
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game?.status === "paused") {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={() => resumeDownload(game.id)} theme="outline">
|
||||||
|
{t("resume")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => cancelDownload(game.id).then(getGame)}
|
||||||
|
theme="outline"
|
||||||
|
>
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game?.status === "seeding" || (game && !game.status)) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{game?.status === "seeding" ? (
|
||||||
|
<Button
|
||||||
|
onClick={openGameInstaller}
|
||||||
|
theme="outline"
|
||||||
|
disabled={deleting || isGamePlaying}
|
||||||
|
>
|
||||||
|
{t("install")}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
toggleGameOnLibraryButton
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isGamePlaying ? (
|
||||||
|
<Button onClick={closeGame} theme="outline" disabled={deleting}>
|
||||||
|
{t("close")}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={openGame}
|
||||||
|
theme="outline"
|
||||||
|
disabled={deleting || isGamePlaying}
|
||||||
|
>
|
||||||
|
{t("play")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game?.status === "cancelled") {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={openRepacksModal} theme="outline" disabled={deleting}>
|
||||||
|
{t("open_download_options")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => removeGame(game.id).then(getGame)}
|
||||||
|
theme="outline"
|
||||||
|
disabled={deleting}
|
||||||
|
>
|
||||||
|
{t("remove_from_list")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameDetails && gameDetails.repacks.length) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{toggleGameOnLibraryButton}
|
||||||
|
<Button onClick={openRepacksModal} theme="outline">
|
||||||
|
{t("open_download_options")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toggleGameOnLibraryButton;
|
||||||
|
}
|
@ -2,16 +2,15 @@ import { format } from "date-fns";
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Button } from "@renderer/components";
|
import { useDownload } from "@renderer/hooks";
|
||||||
import { useDownload, useLibrary } from "@renderer/hooks";
|
|
||||||
import type { Game, ShopDetails } from "@types";
|
import type { Game, ShopDetails } from "@types";
|
||||||
|
|
||||||
import { formatDownloadProgress } from "@renderer/helpers";
|
import { formatDownloadProgress } from "@renderer/helpers";
|
||||||
import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react";
|
|
||||||
import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal";
|
import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal";
|
||||||
import * as styles from "./hero-panel.css";
|
import * as styles from "./hero-panel.css";
|
||||||
import { useDate } from "@renderer/hooks/use-date";
|
import { useDate } from "@renderer/hooks/use-date";
|
||||||
import { formatBytes } from "@renderer/utils";
|
import { formatBytes } from "@renderer/utils";
|
||||||
|
import { HeroPanelActions } from "./hero-panel-actions";
|
||||||
|
|
||||||
export interface HeroPanelProps {
|
export interface HeroPanelProps {
|
||||||
game: Game | null;
|
game: Game | null;
|
||||||
@ -44,21 +43,8 @@ export function HeroPanel({
|
|||||||
eta,
|
eta,
|
||||||
numPeers,
|
numPeers,
|
||||||
numSeeds,
|
numSeeds,
|
||||||
resumeDownload,
|
|
||||||
pauseDownload,
|
|
||||||
cancelDownload,
|
|
||||||
removeGame,
|
|
||||||
isGameDeleting,
|
isGameDeleting,
|
||||||
} = useDownload();
|
} = useDownload();
|
||||||
const { updateLibrary, library } = useLibrary();
|
|
||||||
|
|
||||||
const [toggleLibraryGameDisabled, setToggleLibraryGameDisabled] =
|
|
||||||
useState(false);
|
|
||||||
|
|
||||||
const gameOnLibrary = library.find(
|
|
||||||
({ objectID }) => objectID === gameDetails?.objectID
|
|
||||||
);
|
|
||||||
|
|
||||||
const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;
|
const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;
|
||||||
|
|
||||||
const updateLastTimePlayed = useCallback(() => {
|
const updateLastTimePlayed = useCallback(() => {
|
||||||
@ -83,41 +69,6 @@ export function HeroPanel({
|
|||||||
}
|
}
|
||||||
}, [game?.lastTimePlayed, updateLastTimePlayed]);
|
}, [game?.lastTimePlayed, updateLastTimePlayed]);
|
||||||
|
|
||||||
const openGameInstaller = () => {
|
|
||||||
window.electron.openGameInstaller(game.id).then((isBinaryInPath) => {
|
|
||||||
if (!isBinaryInPath) setShowBinaryNotFoundModal(true);
|
|
||||||
updateLibrary();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const openGame = () => {
|
|
||||||
if (game.executablePath) {
|
|
||||||
window.electron.openGame(game.id, game.executablePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (game?.executablePath) {
|
|
||||||
window.electron.openGame(game.id, game.executablePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.electron
|
|
||||||
.showOpenDialog({
|
|
||||||
properties: ["openFile"],
|
|
||||||
filters: [{ name: "Game executable (.exe)", extensions: ["exe"] }],
|
|
||||||
})
|
|
||||||
.then(({ filePaths }) => {
|
|
||||||
if (filePaths && filePaths.length > 0) {
|
|
||||||
const path = filePaths[0];
|
|
||||||
window.electron.openGame(game.id, path);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeGame = () => {
|
|
||||||
window.electron.closeGame(game.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const finalDownloadSize = useMemo(() => {
|
const finalDownloadSize = useMemo(() => {
|
||||||
if (!game) return "N/A";
|
if (!game) return "N/A";
|
||||||
if (game.fileSize) return formatBytes(game.fileSize);
|
if (game.fileSize) return formatBytes(game.fileSize);
|
||||||
@ -128,26 +79,6 @@ export function HeroPanel({
|
|||||||
return game.repack?.fileSize ?? "N/A";
|
return game.repack?.fileSize ?? "N/A";
|
||||||
}, [game, isGameDownloading, gameDownloading]);
|
}, [game, isGameDownloading, gameDownloading]);
|
||||||
|
|
||||||
const toggleLibraryGame = async () => {
|
|
||||||
setToggleLibraryGameDisabled(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (gameOnLibrary) {
|
|
||||||
await window.electron.removeGame(gameOnLibrary.id);
|
|
||||||
} else {
|
|
||||||
await window.electron.addGameToLibrary(
|
|
||||||
gameDetails.objectID,
|
|
||||||
gameDetails.name,
|
|
||||||
"steam"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateLibrary();
|
|
||||||
} finally {
|
|
||||||
setToggleLibraryGameDisabled(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getInfo = () => {
|
const getInfo = () => {
|
||||||
if (!gameDetails) return null;
|
if (!gameDetails) return null;
|
||||||
|
|
||||||
@ -196,7 +127,7 @@ export function HeroPanel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game?.status === "seeding") {
|
if (game?.status === "seeding" || (game && !game.status)) {
|
||||||
if (!game.lastTimePlayed) {
|
if (!game.lastTimePlayed) {
|
||||||
return <p>{t("not_played_yet", { title: game.title })}</p>;
|
return <p>{t("not_played_yet", { title: game.title })}</p>;
|
||||||
}
|
}
|
||||||
@ -239,121 +170,26 @@ export function HeroPanel({
|
|||||||
return <p>{t("no_downloads")}</p>;
|
return <p>{t("no_downloads")}</p>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getActions = () => {
|
|
||||||
const deleting = isGameDeleting(game?.id);
|
|
||||||
|
|
||||||
const toggleGameOnLibraryButton = (
|
|
||||||
<Button
|
|
||||||
theme="outline"
|
|
||||||
disabled={!gameDetails || toggleLibraryGameDisabled}
|
|
||||||
onClick={toggleLibraryGame}
|
|
||||||
>
|
|
||||||
{gameOnLibrary ? <NoEntryIcon /> : <PlusCircleIcon />}
|
|
||||||
{gameOnLibrary ? t("remove_from_library") : t("add_to_library")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isGameDownloading) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button onClick={() => pauseDownload(game.id)} theme="outline">
|
|
||||||
{t("pause")}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => cancelDownload(game.id)} theme="outline">
|
|
||||||
{t("cancel")}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (game?.status === "paused") {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button onClick={() => resumeDownload(game.id)} theme="outline">
|
|
||||||
{t("resume")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => cancelDownload(game.id).then(getGame)}
|
|
||||||
theme="outline"
|
|
||||||
>
|
|
||||||
{t("cancel")}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (game?.status === "seeding") {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
onClick={openGameInstaller}
|
|
||||||
theme="outline"
|
|
||||||
disabled={deleting || isGamePlaying}
|
|
||||||
>
|
|
||||||
{t("install")}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{isGamePlaying ? (
|
|
||||||
<Button onClick={closeGame} theme="outline" disabled={deleting}>
|
|
||||||
{t("close")}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
onClick={openGame}
|
|
||||||
theme="outline"
|
|
||||||
disabled={deleting || isGamePlaying}
|
|
||||||
>
|
|
||||||
{t("play")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (game?.status === "cancelled") {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
onClick={openRepacksModal}
|
|
||||||
theme="outline"
|
|
||||||
disabled={deleting}
|
|
||||||
>
|
|
||||||
{t("open_download_options")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => removeGame(game.id).then(getGame)}
|
|
||||||
theme="outline"
|
|
||||||
disabled={deleting}
|
|
||||||
>
|
|
||||||
{t("remove_from_list")}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameDetails && gameDetails.repacks.length) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{toggleGameOnLibraryButton}
|
|
||||||
<Button onClick={openRepacksModal} theme="outline">
|
|
||||||
{t("open_download_options")}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return toggleGameOnLibraryButton;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BinaryNotFoundModal
|
<BinaryNotFoundModal
|
||||||
visible={showBinaryNotFoundModal}
|
visible={showBinaryNotFoundModal}
|
||||||
onClose={() => setShowBinaryNotFoundModal(false)}
|
onClose={() => setShowBinaryNotFoundModal(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={{ backgroundColor: color }} className={styles.panel}>
|
<div style={{ backgroundColor: color }} className={styles.panel}>
|
||||||
<div className={styles.content}>{getInfo()}</div>
|
<div className={styles.content}>{getInfo()}</div>
|
||||||
<div className={styles.actions}>{getActions()}</div>
|
<div className={styles.actions}>
|
||||||
|
<HeroPanelActions
|
||||||
|
game={game}
|
||||||
|
gameDetails={gameDetails}
|
||||||
|
getGame={getGame}
|
||||||
|
openRepacksModal={openRepacksModal}
|
||||||
|
openBinaryNotFoundModal={() => setShowBinaryNotFoundModal(true)}
|
||||||
|
isGamePlaying={isGamePlaying}
|
||||||
|
isGameDownloading={isGameDownloading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -10,6 +10,7 @@ import type { DiskSpace } from "check-disk-space";
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
import { formatBytes } from "@renderer/utils";
|
import { formatBytes } from "@renderer/utils";
|
||||||
|
import { useAppSelector } from "@renderer/hooks";
|
||||||
import { SelectFolderModal } from "./select-folder-modal";
|
import { SelectFolderModal } from "./select-folder-modal";
|
||||||
|
|
||||||
export interface RepacksModalProps {
|
export interface RepacksModalProps {
|
||||||
@ -30,6 +31,10 @@ export function RepacksModal({
|
|||||||
const [filteredRepacks, setFilteredRepacks] = useState<GameRepack[]>([]);
|
const [filteredRepacks, setFilteredRepacks] = useState<GameRepack[]>([]);
|
||||||
const [repack, setRepack] = useState<GameRepack>(null);
|
const [repack, setRepack] = useState<GameRepack>(null);
|
||||||
|
|
||||||
|
const repackersFriendlyNames = useAppSelector(
|
||||||
|
(state) => state.repackersFriendlyNames.value
|
||||||
|
);
|
||||||
|
|
||||||
const { t } = useTranslation("game_details");
|
const { t } = useTranslation("game_details");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -91,7 +96,7 @@ export function RepacksModal({
|
|||||||
>
|
>
|
||||||
<p style={{ color: "#DADBE1" }}>{repack.title}</p>
|
<p style={{ color: "#DADBE1" }}>{repack.title}</p>
|
||||||
<p style={{ fontSize: "12px" }}>
|
<p style={{ fontSize: "12px" }}>
|
||||||
{repack.fileSize} - {repack.repacker} -{" "}
|
{repack.fileSize} - {repackersFriendlyNames[repack.repacker]} -{" "}
|
||||||
{format(repack.uploadDate, "dd/MM/yyyy")}
|
{format(repack.uploadDate, "dd/MM/yyyy")}
|
||||||
</p>
|
</p>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -67,7 +67,6 @@ export function SearchResults() {
|
|||||||
key={game.objectID}
|
key={game.objectID}
|
||||||
game={game}
|
game={game}
|
||||||
onClick={() => handleGameClick(game)}
|
onClick={() => handleGameClick(game)}
|
||||||
disabled={!game.repacks.length}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -10,6 +10,7 @@ export function Settings() {
|
|||||||
downloadsPath: "",
|
downloadsPath: "",
|
||||||
downloadNotificationsEnabled: false,
|
downloadNotificationsEnabled: false,
|
||||||
repackUpdatesNotificationsEnabled: false,
|
repackUpdatesNotificationsEnabled: false,
|
||||||
|
telemetryEnabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
@ -25,6 +26,7 @@ export function Settings() {
|
|||||||
userPreferences?.downloadNotificationsEnabled,
|
userPreferences?.downloadNotificationsEnabled,
|
||||||
repackUpdatesNotificationsEnabled:
|
repackUpdatesNotificationsEnabled:
|
||||||
userPreferences?.repackUpdatesNotificationsEnabled,
|
userPreferences?.repackUpdatesNotificationsEnabled,
|
||||||
|
telemetryEnabled: userPreferences?.telemetryEnabled,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
@ -95,6 +97,16 @@ export function Settings() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<h3>{t("telemetry")}</h3>
|
||||||
|
|
||||||
|
<CheckboxField
|
||||||
|
label={t("telemetry_description")}
|
||||||
|
checked={form.telemetryEnabled}
|
||||||
|
onChange={() =>
|
||||||
|
updateUserPreferences("telemetryEnabled", !form.telemetryEnabled)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
@ -104,6 +104,7 @@ export interface UserPreferences {
|
|||||||
language: string;
|
language: string;
|
||||||
downloadNotificationsEnabled: boolean;
|
downloadNotificationsEnabled: boolean;
|
||||||
repackUpdatesNotificationsEnabled: boolean;
|
repackUpdatesNotificationsEnabled: boolean;
|
||||||
|
telemetryEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HowLongToBeatCategory {
|
export interface HowLongToBeatCategory {
|
||||||
|
@ -12,5 +12,9 @@ setup(
|
|||||||
version="0.1",
|
version="0.1",
|
||||||
description="Hydra Torrent Client",
|
description="Hydra Torrent Client",
|
||||||
options={"build_exe": build_exe_options},
|
options={"build_exe": build_exe_options},
|
||||||
executables=[Executable("torrent-client/main.py", target_name="hydra-download-manager")]
|
executables=[Executable(
|
||||||
|
"torrent-client/main.py",
|
||||||
|
target_name="hydra-download-manager",
|
||||||
|
icon="images/icon.ico"
|
||||||
|
)]
|
||||||
)
|
)
|
||||||
|
38
yarn.lock
38
yarn.lock
@ -8894,13 +8894,6 @@ qs@6.11.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
side-channel "^1.0.4"
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
qs@^6.12.0:
|
|
||||||
version "6.12.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a"
|
|
||||||
integrity sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==
|
|
||||||
dependencies:
|
|
||||||
side-channel "^1.0.6"
|
|
||||||
|
|
||||||
querystringify@^2.1.1:
|
querystringify@^2.1.1:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz"
|
resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz"
|
||||||
@ -9896,7 +9889,16 @@ stream-transform@^2.1.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mixme "^0.5.1"
|
mixme "^0.5.1"
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -9974,7 +9976,14 @@ string_decoder@~1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@ -11010,7 +11019,16 @@ word-wrap@^1.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||||
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
||||||
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.0.0"
|
||||||
|
string-width "^4.1.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
|
wrap-ansi@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
Loading…
Reference in New Issue
Block a user