mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-09 03:37:45 +03:00
feat: adding new messages to hero panel
This commit is contained in:
parent
a240c3ae24
commit
d431c01d1b
@ -3,9 +3,10 @@ productName: Hydra
|
|||||||
directories:
|
directories:
|
||||||
buildResources: build
|
buildResources: build
|
||||||
extraResources:
|
extraResources:
|
||||||
|
- aria2
|
||||||
|
- seeds
|
||||||
- hydra.db
|
- hydra.db
|
||||||
- fastlist.exe
|
- fastlist.exe
|
||||||
- seeds
|
|
||||||
files:
|
files:
|
||||||
- "!**/.vscode/*"
|
- "!**/.vscode/*"
|
||||||
- "!src/*"
|
- "!src/*"
|
||||||
|
@ -50,7 +50,6 @@
|
|||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
"color.js": "^1.2.0",
|
"color.js": "^1.2.0",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"easydl": "^1.1.1",
|
|
||||||
"electron-updater": "^6.1.8",
|
"electron-updater": "^6.1.8",
|
||||||
"fetch-cookie": "^3.0.1",
|
"fetch-cookie": "^3.0.1",
|
||||||
"flexsearch": "^0.7.43",
|
"flexsearch": "^0.7.43",
|
||||||
@ -59,7 +58,6 @@
|
|||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lottie-react": "^2.4.0",
|
"lottie-react": "^2.4.0",
|
||||||
"node-7z-archive": "^1.1.7",
|
|
||||||
"parse-torrent": "^11.0.16",
|
"parse-torrent": "^11.0.16",
|
||||||
"ps-list": "^8.1.1",
|
"ps-list": "^8.1.1",
|
||||||
"react-i18next": "^14.1.0",
|
"react-i18next": "^14.1.0",
|
||||||
|
@ -1,4 +1,41 @@
|
|||||||
const fs = require("fs");
|
const { default: axios } = require("axios");
|
||||||
|
const util = require("node:util");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
|
||||||
|
const exec = util.promisify(require("node:child_process").exec);
|
||||||
|
|
||||||
|
const downloadAria2 = async () => {
|
||||||
|
if (fs.existsSync("aria2")) {
|
||||||
|
console.log("Aria2 already exists, skipping download...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file =
|
||||||
|
process.platform === "win32"
|
||||||
|
? "aria2-1.37.0-win-64bit-build1.zip"
|
||||||
|
: "aria2-1.37.0-aarch64-linux-android-build1.zip";
|
||||||
|
|
||||||
|
console.log(`Downloading ${file}...`);
|
||||||
|
|
||||||
|
const response = await axios.get(
|
||||||
|
`https://github.com/aria2/aria2/releases/download/release-1.37.0/${file}`,
|
||||||
|
{ responseType: "stream" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const stream = response.data.pipe(fs.createWriteStream(file));
|
||||||
|
|
||||||
|
stream.on("finish", async () => {
|
||||||
|
console.log(`Downloaded ${file}, extracting...`);
|
||||||
|
|
||||||
|
await exec(`npx extract-zip ${file}`);
|
||||||
|
console.log("Extracted. Renaming folder...");
|
||||||
|
|
||||||
|
fs.renameSync(file.replace(".zip", ""), "aria2");
|
||||||
|
|
||||||
|
console.log(`Extracted ${file}, removing zip file...`);
|
||||||
|
fs.rmSync(file);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
fs.copyFileSync(
|
fs.copyFileSync(
|
||||||
@ -6,3 +43,5 @@ if (process.platform === "win32") {
|
|||||||
"fastlist.exe"
|
"fastlist.exe"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadAria2();
|
||||||
|
@ -10,7 +10,7 @@ const addGameToLibrary = async (
|
|||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
objectID: string,
|
objectID: string,
|
||||||
title: string,
|
title: string,
|
||||||
gameShop: GameShop,
|
shop: GameShop,
|
||||||
executablePath: string | null
|
executablePath: string | null
|
||||||
) => {
|
) => {
|
||||||
return gameRepository
|
return gameRepository
|
||||||
@ -19,7 +19,7 @@ const addGameToLibrary = async (
|
|||||||
objectID,
|
objectID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shop: gameShop,
|
shop,
|
||||||
status: null,
|
status: null,
|
||||||
executablePath,
|
executablePath,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
@ -40,7 +40,7 @@ const addGameToLibrary = async (
|
|||||||
title,
|
title,
|
||||||
iconUrl,
|
iconUrl,
|
||||||
objectID,
|
objectID,
|
||||||
shop: gameShop,
|
shop,
|
||||||
executablePath,
|
executablePath,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -1,33 +1,18 @@
|
|||||||
import {
|
import { gameRepository, repackRepository } from "@main/repository";
|
||||||
gameRepository,
|
|
||||||
repackRepository,
|
|
||||||
userPreferencesRepository,
|
|
||||||
} from "@main/repository";
|
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
|
||||||
import type { GameShop } from "@types";
|
import type { StartGameDownloadPayload } from "@types";
|
||||||
import { getFileBase64, getSteamAppAsset } from "@main/helpers";
|
import { getFileBase64, getSteamAppAsset } from "@main/helpers";
|
||||||
import { DownloadManager } from "@main/services";
|
import { DownloadManager } from "@main/services";
|
||||||
import { Downloader } from "@shared";
|
|
||||||
import { stateManager } from "@main/state-manager";
|
import { stateManager } from "@main/state-manager";
|
||||||
import { Not } from "typeorm";
|
import { Not } from "typeorm";
|
||||||
|
|
||||||
const startGameDownload = async (
|
const startGameDownload = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
repackId: number,
|
payload: StartGameDownloadPayload
|
||||||
objectID: string,
|
|
||||||
title: string,
|
|
||||||
gameShop: GameShop,
|
|
||||||
downloadPath: string
|
|
||||||
) => {
|
) => {
|
||||||
const userPreferences = await userPreferencesRepository.findOne({
|
const { repackId, objectID, title, shop, downloadPath, downloader } = payload;
|
||||||
where: { id: 1 },
|
|
||||||
});
|
|
||||||
|
|
||||||
const downloader = userPreferences?.realDebridApiToken
|
|
||||||
? Downloader.RealDebrid
|
|
||||||
: Downloader.Torrent;
|
|
||||||
|
|
||||||
const [game, repack] = await Promise.all([
|
const [game, repack] = await Promise.all([
|
||||||
gameRepository.findOne({
|
gameRepository.findOne({
|
||||||
@ -83,7 +68,7 @@ const startGameDownload = async (
|
|||||||
iconUrl,
|
iconUrl,
|
||||||
objectID,
|
objectID,
|
||||||
downloader,
|
downloader,
|
||||||
shop: gameShop,
|
shop,
|
||||||
status: "active",
|
status: "active",
|
||||||
downloadPath,
|
downloadPath,
|
||||||
repack: { id: repackId },
|
repack: { id: repackId },
|
||||||
|
@ -5,8 +5,8 @@ import {
|
|||||||
getNewRepacksFromUser,
|
getNewRepacksFromUser,
|
||||||
getNewRepacksFromXatab,
|
getNewRepacksFromXatab,
|
||||||
getNewRepacksFromOnlineFix,
|
getNewRepacksFromOnlineFix,
|
||||||
startProcessWatcher,
|
|
||||||
DownloadManager,
|
DownloadManager,
|
||||||
|
startMainLoop,
|
||||||
} from "./services";
|
} from "./services";
|
||||||
import {
|
import {
|
||||||
gameRepository,
|
gameRepository,
|
||||||
@ -23,7 +23,7 @@ import { orderBy } from "lodash-es";
|
|||||||
import { SteamGame } from "@types";
|
import { SteamGame } from "@types";
|
||||||
import { Not } from "typeorm";
|
import { Not } from "typeorm";
|
||||||
|
|
||||||
startProcessWatcher();
|
startMainLoop();
|
||||||
|
|
||||||
const track1337xUsers = async (existingRepacks: Repack[]) => {
|
const track1337xUsers = async (existingRepacks: Repack[]) => {
|
||||||
for (const repacker of repackersOn1337x) {
|
for (const repacker of repackersOn1337x) {
|
||||||
@ -88,8 +88,6 @@ const loadState = async (userPreferences: UserPreferences | null) => {
|
|||||||
if (userPreferences?.realDebridApiToken)
|
if (userPreferences?.realDebridApiToken)
|
||||||
await RealDebridClient.authorize(userPreferences?.realDebridApiToken);
|
await RealDebridClient.authorize(userPreferences?.realDebridApiToken);
|
||||||
|
|
||||||
await DownloadManager.connect();
|
|
||||||
|
|
||||||
const game = await gameRepository.findOne({
|
const game = await gameRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
status: "active",
|
status: "active",
|
||||||
@ -99,9 +97,7 @@ const loadState = async (userPreferences: UserPreferences | null) => {
|
|||||||
relations: { repack: true },
|
relations: { repack: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (game) {
|
if (game) DownloadManager.startDownload(game.id);
|
||||||
DownloadManager.startDownload(game.id);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
userPreferencesRepository
|
userPreferencesRepository
|
||||||
|
@ -6,7 +6,7 @@ import { gameRepository, userPreferencesRepository } from "@main/repository";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
import { RealDebridClient } from "./real-debrid";
|
import { RealDebridClient } from "./real-debrid";
|
||||||
import { Notification } from "electron";
|
import { Notification, app } from "electron";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { Downloader } from "@shared";
|
import { Downloader } from "@shared";
|
||||||
import { DownloadProgress } from "@types";
|
import { DownloadProgress } from "@types";
|
||||||
@ -16,33 +16,36 @@ import { Game } from "@main/entity";
|
|||||||
export class DownloadManager {
|
export class DownloadManager {
|
||||||
private static downloads = new Map<number, string>();
|
private static downloads = new Map<number, string>();
|
||||||
|
|
||||||
|
private static connected = false;
|
||||||
private static gid: string | null = null;
|
private static gid: string | null = null;
|
||||||
private static gameId: number | null = null;
|
private static gameId: number | null = null;
|
||||||
|
|
||||||
private static aria2 = new Aria2({});
|
private static aria2 = new Aria2({});
|
||||||
|
|
||||||
static async connect() {
|
private static connect(): Promise<boolean> {
|
||||||
const binary = path.join(
|
return new Promise((resolve) => {
|
||||||
__dirname,
|
const binaryPath = app.isPackaged
|
||||||
"..",
|
? path.join(process.resourcesPath, "aria2", "aria2c")
|
||||||
"..",
|
: path.join(__dirname, "..", "..", "aria2", "aria2c");
|
||||||
"aria2-1.37.0-win-64bit-build1",
|
|
||||||
"aria2c"
|
|
||||||
);
|
|
||||||
|
|
||||||
spawn(
|
const cp = spawn(binaryPath, [
|
||||||
binary,
|
|
||||||
[
|
|
||||||
"--enable-rpc",
|
"--enable-rpc",
|
||||||
"--rpc-listen-all",
|
"--rpc-listen-all",
|
||||||
"--file-allocation=none",
|
"--file-allocation=none",
|
||||||
"--allow-overwrite=true",
|
"--allow-overwrite=true",
|
||||||
],
|
]);
|
||||||
{ stdio: "inherit" }
|
|
||||||
);
|
|
||||||
|
|
||||||
|
cp.stdout.on("data", async (data) => {
|
||||||
|
const msg = Buffer.from(data).toString("utf-8");
|
||||||
|
|
||||||
|
if (msg.includes("IPv6 RPC: listening on TCP")) {
|
||||||
await this.aria2.open();
|
await this.aria2.open();
|
||||||
this.attachListener();
|
this.connected = true;
|
||||||
|
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getETA(status: StatusResponse) {
|
private static getETA(status: StatusResponse) {
|
||||||
@ -84,23 +87,17 @@ export class DownloadManager {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async attachListener() {
|
public static async watchDownloads() {
|
||||||
// eslint-disable-next-line no-constant-condition
|
if (!this.gid || !this.gameId) return;
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
if (!this.gid || !this.gameId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = await this.aria2.call("tellStatus", this.gid);
|
const status = await this.aria2.call("tellStatus", this.gid);
|
||||||
|
|
||||||
const downloadingMetadata =
|
const downloadingMetadata = status.bittorrent && !status.bittorrent?.info;
|
||||||
status.bittorrent && !status.bittorrent?.info;
|
|
||||||
|
|
||||||
if (status.followedBy?.length) {
|
if (status.followedBy?.length) {
|
||||||
this.gid = status.followedBy[0];
|
this.gid = status.followedBy[0];
|
||||||
this.downloads.set(this.gameId, this.gid);
|
this.downloads.set(this.gameId, this.gid);
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const progress =
|
const progress =
|
||||||
@ -143,10 +140,7 @@ export class DownloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (WindowManager.mainWindow && game) {
|
if (WindowManager.mainWindow && game) {
|
||||||
WindowManager.mainWindow.setProgressBar(
|
WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress);
|
||||||
progress === 1 || downloadingMetadata ? -1 : progress,
|
|
||||||
{ mode: downloadingMetadata ? "indeterminate" : "normal" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
progress,
|
progress,
|
||||||
@ -165,10 +159,6 @@ export class DownloadManager {
|
|||||||
JSON.parse(JSON.stringify(payload))
|
JSON.parse(JSON.stringify(payload))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getGame(gameId: number) {
|
static async getGame(gameId: number) {
|
||||||
@ -227,6 +217,8 @@ export class DownloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async startDownload(gameId: number) {
|
static async startDownload(gameId: number) {
|
||||||
|
if (!this.connected) await this.connect();
|
||||||
|
|
||||||
const game = await this.getGame(gameId)!;
|
const game = await this.getGame(gameId)!;
|
||||||
|
|
||||||
if (game) {
|
if (game) {
|
||||||
|
@ -8,3 +8,4 @@ export * from "./window-manager";
|
|||||||
export * from "./download-manager";
|
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";
|
||||||
|
16
src/main/services/main-loop.ts
Normal file
16
src/main/services/main-loop.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { DownloadManager } from "./download-manager";
|
||||||
|
import { watchProcesses } from "./process-watcher";
|
||||||
|
|
||||||
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
export const startMainLoop = async () => {
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
await Promise.allSettled([
|
||||||
|
watchProcesses(),
|
||||||
|
DownloadManager.watchDownloads(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
}
|
||||||
|
};
|
@ -5,14 +5,9 @@ import { gameRepository } from "@main/repository";
|
|||||||
import { getProcesses } from "@main/helpers";
|
import { getProcesses } from "@main/helpers";
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
|
|
||||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
export const startProcessWatcher = async () => {
|
|
||||||
const sleepTime = 500;
|
|
||||||
const gamesPlaytime = new Map<number, number>();
|
const gamesPlaytime = new Map<number, number>();
|
||||||
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
export const watchProcesses = async () => {
|
||||||
while (true) {
|
|
||||||
const games = await gameRepository.find({
|
const games = await gameRepository.find({
|
||||||
where: {
|
where: {
|
||||||
executablePath: Not(IsNull()),
|
executablePath: Not(IsNull()),
|
||||||
@ -20,10 +15,7 @@ export const startProcessWatcher = async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (games.length === 0) {
|
if (games.length === 0) return;
|
||||||
await sleep(sleepTime);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const processes = await getProcesses();
|
const processes = await getProcesses();
|
||||||
|
|
||||||
@ -40,9 +32,7 @@ export const startProcessWatcher = async () => {
|
|||||||
return runningProcess.name === basename;
|
return runningProcess.name === basename;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [basename, basenameWithoutExtension].includes(
|
return [basename, basenameWithoutExtension].includes(runningProcess.name);
|
||||||
runningProcess.name
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (gameProcess) {
|
if (gameProcess) {
|
||||||
@ -56,9 +46,6 @@ export const startProcessWatcher = async () => {
|
|||||||
|
|
||||||
await gameRepository.update(game.id, {
|
await gameRepository.update(game.id, {
|
||||||
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
|
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
|
||||||
});
|
|
||||||
|
|
||||||
gameRepository.update(game.id, {
|
|
||||||
lastTimePlayed: new Date().toUTCString(),
|
lastTimePlayed: new Date().toUTCString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -66,12 +53,10 @@ export const startProcessWatcher = async () => {
|
|||||||
gamesPlaytime.set(game.id, performance.now());
|
gamesPlaytime.set(game.id, performance.now());
|
||||||
} else if (gamesPlaytime.has(game.id)) {
|
} else if (gamesPlaytime.has(game.id)) {
|
||||||
gamesPlaytime.delete(game.id);
|
gamesPlaytime.delete(game.id);
|
||||||
|
|
||||||
if (WindowManager.mainWindow) {
|
if (WindowManager.mainWindow) {
|
||||||
WindowManager.mainWindow.webContents.send("on-game-close", game.id);
|
WindowManager.mainWindow.webContents.send("on-game-close", game.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await sleep(sleepTime);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -7,26 +7,14 @@ import type {
|
|||||||
GameShop,
|
GameShop,
|
||||||
DownloadProgress,
|
DownloadProgress,
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
AppUpdaterEvents,
|
AppUpdaterEvent,
|
||||||
|
StartGameDownloadPayload,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("electron", {
|
contextBridge.exposeInMainWorld("electron", {
|
||||||
/* Torrenting */
|
/* Torrenting */
|
||||||
startGameDownload: (
|
startGameDownload: (payload: StartGameDownloadPayload) =>
|
||||||
repackId: number,
|
ipcRenderer.invoke("startGameDownload", payload),
|
||||||
objectID: string,
|
|
||||||
title: string,
|
|
||||||
shop: GameShop,
|
|
||||||
downloadPath: string
|
|
||||||
) =>
|
|
||||||
ipcRenderer.invoke(
|
|
||||||
"startGameDownload",
|
|
||||||
repackId,
|
|
||||||
objectID,
|
|
||||||
title,
|
|
||||||
shop,
|
|
||||||
downloadPath
|
|
||||||
),
|
|
||||||
cancelGameDownload: (gameId: number) =>
|
cancelGameDownload: (gameId: number) =>
|
||||||
ipcRenderer.invoke("cancelGameDownload", gameId),
|
ipcRenderer.invoke("cancelGameDownload", gameId),
|
||||||
pauseGameDownload: (gameId: number) =>
|
pauseGameDownload: (gameId: number) =>
|
||||||
@ -115,10 +103,10 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
|
|
||||||
/* Splash */
|
/* Splash */
|
||||||
onAutoUpdaterEvent: (cb: (value: AppUpdaterEvents) => void) => {
|
onAutoUpdaterEvent: (cb: (value: AppUpdaterEvent) => void) => {
|
||||||
const listener = (
|
const listener = (
|
||||||
_event: Electron.IpcRendererEvent,
|
_event: Electron.IpcRendererEvent,
|
||||||
value: AppUpdaterEvents
|
value: AppUpdaterEvent
|
||||||
) => cb(value);
|
) => cb(value);
|
||||||
|
|
||||||
ipcRenderer.on("autoUpdaterEvent", listener);
|
ipcRenderer.on("autoUpdaterEvent", listener);
|
||||||
|
@ -15,8 +15,7 @@ export function BottomPanel() {
|
|||||||
|
|
||||||
const { lastPacket, progress, downloadSpeed, eta } = useDownload();
|
const { lastPacket, progress, downloadSpeed, eta } = useDownload();
|
||||||
|
|
||||||
const isGameDownloading =
|
const isGameDownloading = !!lastPacket?.game;
|
||||||
lastPacket?.game && lastPacket?.game.status === "active";
|
|
||||||
|
|
||||||
const [version, setVersion] = useState("");
|
const [version, setVersion] = useState("");
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ const base = style({
|
|||||||
},
|
},
|
||||||
":disabled": {
|
":disabled": {
|
||||||
opacity: vars.opacity.disabled,
|
opacity: vars.opacity.disabled,
|
||||||
pointerEvents: "none",
|
|
||||||
cursor: "not-allowed",
|
cursor: "not-allowed",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -30,6 +29,9 @@ export const button = styleVariants({
|
|||||||
":hover": {
|
":hover": {
|
||||||
backgroundColor: "#DADBE1",
|
backgroundColor: "#DADBE1",
|
||||||
},
|
},
|
||||||
|
":disabled": {
|
||||||
|
backgroundColor: vars.color.muted,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
outline: [
|
outline: [
|
||||||
@ -41,6 +43,9 @@ export const button = styleVariants({
|
|||||||
":hover": {
|
":hover": {
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
},
|
},
|
||||||
|
":disabled": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dark: [
|
dark: [
|
||||||
|
@ -28,7 +28,6 @@ export const content = recipe({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
padding: `${SPACING_UNIT * 2}px`,
|
padding: `${SPACING_UNIT * 2}px`,
|
||||||
gap: `${SPACING_UNIT * 2}px`,
|
gap: `${SPACING_UNIT * 2}px`,
|
||||||
paddingBottom: "0",
|
|
||||||
width: "100%",
|
width: "100%",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
},
|
},
|
||||||
|
@ -48,10 +48,8 @@ export const toast = recipe({
|
|||||||
|
|
||||||
export const toastContent = style({
|
export const toastContent = style({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
position: "relative",
|
gap: `${SPACING_UNIT * 2}px`,
|
||||||
gap: `${SPACING_UNIT}px`,
|
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`,
|
||||||
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 5}px`,
|
|
||||||
paddingLeft: `${SPACING_UNIT * 2}px`,
|
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
});
|
});
|
||||||
@ -63,13 +61,11 @@ export const progress = style({
|
|||||||
backgroundColor: vars.color.darkBackground,
|
backgroundColor: vars.color.darkBackground,
|
||||||
},
|
},
|
||||||
"::-webkit-progress-value": {
|
"::-webkit-progress-value": {
|
||||||
backgroundColor: "#1c9749",
|
backgroundColor: vars.color.muted,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const closeButton = style({
|
export const closeButton = style({
|
||||||
position: "absolute",
|
|
||||||
right: `${SPACING_UNIT}px`,
|
|
||||||
color: vars.color.bodyText,
|
color: vars.color.bodyText,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
padding: "0",
|
padding: "0",
|
||||||
@ -77,5 +73,9 @@ export const closeButton = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const successIcon = style({
|
export const successIcon = style({
|
||||||
color: "#1c9749",
|
color: vars.color.success,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const errorIcon = style({
|
||||||
|
color: vars.color.danger,
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
CheckCircleFillIcon,
|
CheckCircleFillIcon,
|
||||||
CheckCircleIcon,
|
XCircleFillIcon,
|
||||||
XCircleIcon,
|
|
||||||
XIcon,
|
XIcon,
|
||||||
} from "@primer/octicons-react";
|
} from "@primer/octicons-react";
|
||||||
|
|
||||||
import * as styles from "./toast.css";
|
import * as styles from "./toast.css";
|
||||||
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
|
|
||||||
export interface ToastProps {
|
export interface ToastProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@ -78,8 +78,13 @@ export function Toast({ visible, message, type, onClose }: ToastProps) {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.toast({ closing: isClosing })}>
|
<div className={styles.toast({ closing: isClosing })}>
|
||||||
<div className={styles.toastContent}>
|
<div className={styles.toastContent}>
|
||||||
|
<div style={{ display: "flex", gap: `${SPACING_UNIT}px` }}>
|
||||||
|
{type === "success" && (
|
||||||
<CheckCircleFillIcon className={styles.successIcon} />
|
<CheckCircleFillIcon className={styles.successIcon} />
|
||||||
|
)}
|
||||||
|
{type === "error" && <XCircleFillIcon className={styles.errorIcon} />}
|
||||||
<span style={{ fontWeight: "bold" }}>{message}</span>
|
<span style={{ fontWeight: "bold" }}>{message}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -87,7 +92,7 @@ export function Toast({ visible, message, type, onClose }: ToastProps) {
|
|||||||
onClick={startAnimateClosing}
|
onClick={startAnimateClosing}
|
||||||
aria-label="Close toast"
|
aria-label="Close toast"
|
||||||
>
|
>
|
||||||
<XCircleIcon />
|
<XIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
9
src/renderer/src/declaration.d.ts
vendored
9
src/renderer/src/declaration.d.ts
vendored
@ -10,6 +10,7 @@ import type {
|
|||||||
Steam250Game,
|
Steam250Game,
|
||||||
DownloadProgress,
|
DownloadProgress,
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
|
StartGameDownloadPayload,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
import type { DiskSpace } from "check-disk-space";
|
import type { DiskSpace } from "check-disk-space";
|
||||||
|
|
||||||
@ -21,13 +22,7 @@ declare global {
|
|||||||
|
|
||||||
interface Electron {
|
interface Electron {
|
||||||
/* Torrenting */
|
/* Torrenting */
|
||||||
startGameDownload: (
|
startGameDownload: (payload: StartGameDownloadPayload) => Promise<Game>;
|
||||||
repackId: number,
|
|
||||||
objectID: string,
|
|
||||||
title: string,
|
|
||||||
shop: GameShop,
|
|
||||||
downloadPath: string
|
|
||||||
) => Promise<Game>;
|
|
||||||
cancelGameDownload: (gameId: number) => Promise<void>;
|
cancelGameDownload: (gameId: number) => Promise<void>;
|
||||||
pauseGameDownload: (gameId: number) => Promise<void>;
|
pauseGameDownload: (gameId: number) => Promise<void>;
|
||||||
resumeGameDownload: (gameId: number) => Promise<void>;
|
resumeGameDownload: (gameId: number) => Promise<void>;
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
setGameDeleting,
|
setGameDeleting,
|
||||||
removeGameFromDeleting,
|
removeGameFromDeleting,
|
||||||
} from "@renderer/features";
|
} from "@renderer/features";
|
||||||
import type { DownloadProgress, GameShop } from "@types";
|
import type { DownloadProgress, StartGameDownloadPayload } from "@types";
|
||||||
import { useDate } from "./use-date";
|
import { useDate } from "./use-date";
|
||||||
import { formatBytes } from "@shared";
|
import { formatBytes } from "@shared";
|
||||||
|
|
||||||
@ -22,16 +22,8 @@ export function useDownload() {
|
|||||||
);
|
);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const startDownload = (
|
const startDownload = (payload: StartGameDownloadPayload) =>
|
||||||
repackId: number,
|
window.electron.startGameDownload(payload).then((game) => {
|
||||||
objectID: string,
|
|
||||||
title: string,
|
|
||||||
shop: GameShop,
|
|
||||||
downloadPath: string
|
|
||||||
) =>
|
|
||||||
window.electron
|
|
||||||
.startGameDownload(repackId, objectID, title, shop, downloadPath)
|
|
||||||
.then((game) => {
|
|
||||||
dispatch(clearDownload());
|
dispatch(clearDownload());
|
||||||
updateLibrary();
|
updateLibrary();
|
||||||
|
|
||||||
@ -62,11 +54,11 @@ export function useDownload() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getETA = () => {
|
const getETA = () => {
|
||||||
if (lastPacket && lastPacket.timeRemaining < 0) return "";
|
if (!lastPacket || lastPacket.timeRemaining < 0) return "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return formatDistance(
|
return formatDistance(
|
||||||
addMilliseconds(new Date(), lastPacket?.timeRemaining ?? 1),
|
addMilliseconds(new Date(), lastPacket.timeRemaining),
|
||||||
new Date(),
|
new Date(),
|
||||||
{ addSuffix: true }
|
{ addSuffix: true }
|
||||||
);
|
);
|
||||||
@ -94,7 +86,7 @@ export function useDownload() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
downloadSpeed: `${formatBytes(lastPacket?.downloadSpeed ?? 0)}/s`,
|
downloadSpeed: `${formatBytes(lastPacket?.downloadSpeed ?? 0)}/s`,
|
||||||
progress: formatDownloadProgress(lastPacket?.game.progress ?? 0),
|
progress: formatDownloadProgress(lastPacket?.game.progress),
|
||||||
lastPacket,
|
lastPacket,
|
||||||
eta: getETA(),
|
eta: getETA(),
|
||||||
startDownload,
|
startDownload,
|
||||||
|
@ -254,9 +254,7 @@ export function Downloads() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.downloadTitle}
|
className={styles.downloadTitle}
|
||||||
onClick={() =>
|
onClick={() => navigate(buildGameDetailsPath(game))}
|
||||||
navigate(`/game/${game.shop}/${game.objectID}`)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{game.title}
|
{game.title}
|
||||||
</button>
|
</button>
|
||||||
|
@ -67,6 +67,7 @@ export const mediaPreviewButton = recipe({
|
|||||||
transition: "translate 0.3s ease-in-out, opacity 0.2s ease",
|
transition: "translate 0.3s ease-in-out, opacity 0.2s ease",
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
border: `solid 1px ${vars.color.border}`,
|
border: `solid 1px ${vars.color.border}`,
|
||||||
|
overflow: "hidden",
|
||||||
":hover": {
|
":hover": {
|
||||||
opacity: "0.8",
|
opacity: "0.8",
|
||||||
},
|
},
|
||||||
@ -84,7 +85,6 @@ export const mediaPreview = style({
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flex: "1",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const gallerySliderButton = recipe({
|
export const gallerySliderButton = recipe({
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
OnlineFixInstallationGuide,
|
OnlineFixInstallationGuide,
|
||||||
RepacksModal,
|
RepacksModal,
|
||||||
} from "./modals";
|
} from "./modals";
|
||||||
|
import { Downloader } from "@shared";
|
||||||
|
|
||||||
export interface GameDetailsContext {
|
export interface GameDetailsContext {
|
||||||
game: Game | null;
|
game: Game | null;
|
||||||
@ -138,15 +139,17 @@ export function GameDetailsContextProvider({
|
|||||||
|
|
||||||
const handleStartDownload = async (
|
const handleStartDownload = async (
|
||||||
repack: GameRepack,
|
repack: GameRepack,
|
||||||
|
downloader: Downloader,
|
||||||
downloadPath: string
|
downloadPath: string
|
||||||
) => {
|
) => {
|
||||||
await startDownload(
|
await startDownload({
|
||||||
repack.id,
|
repackId: repack.id,
|
||||||
objectID!,
|
objectID: objectID!,
|
||||||
gameTitle,
|
title: gameTitle,
|
||||||
shop as GameShop,
|
downloader,
|
||||||
downloadPath
|
shop: shop as GameShop,
|
||||||
);
|
downloadPath,
|
||||||
|
});
|
||||||
|
|
||||||
await updateGame();
|
await updateGame();
|
||||||
setShowRepacksModal(false);
|
setShowRepacksModal(false);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { style } from "@vanilla-extract/css";
|
import { style } from "@vanilla-extract/css";
|
||||||
import { SPACING_UNIT, vars } from "../../../theme.css";
|
import { SPACING_UNIT, vars } from "../../../theme.css";
|
||||||
|
import { recipe } from "@vanilla-extract/recipes";
|
||||||
|
|
||||||
export const panel = style({
|
export const panel = style({
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@ -11,6 +12,8 @@ export const panel = style({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
transition: "all ease 0.2s",
|
transition: "all ease 0.2s",
|
||||||
borderBottom: `solid 1px ${vars.color.border}`,
|
borderBottom: `solid 1px ${vars.color.border}`,
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const content = style({
|
export const content = style({
|
||||||
@ -29,3 +32,27 @@ export const downloadDetailsRow = style({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "flex-end",
|
alignItems: "flex-end",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const progressBar = recipe({
|
||||||
|
base: {
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "0",
|
||||||
|
left: "0",
|
||||||
|
width: "100%",
|
||||||
|
height: "3px",
|
||||||
|
transition: "all ease 0.2s",
|
||||||
|
"::-webkit-progress-bar": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
"::-webkit-progress-value": {
|
||||||
|
backgroundColor: vars.color.muted,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
disabled: {
|
||||||
|
true: {
|
||||||
|
opacity: vars.opacity.disabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -33,29 +33,46 @@ export function HeroPanel() {
|
|||||||
return game.repack?.fileSize ?? "N/A";
|
return game.repack?.fileSize ?? "N/A";
|
||||||
}, [game, lastPacket?.game]);
|
}, [game, lastPacket?.game]);
|
||||||
|
|
||||||
|
const isGameDownloading =
|
||||||
|
game?.status === "active" && lastPacket?.game.id === game?.id;
|
||||||
|
|
||||||
const getInfo = () => {
|
const getInfo = () => {
|
||||||
if (isGameDeleting(game?.id ?? -1)) return <p>{t("deleting")}</p>;
|
if (isGameDeleting(game?.id ?? -1)) return <p>{t("deleting")}</p>;
|
||||||
|
|
||||||
if (game?.progress === 1) return <HeroPanelPlaytime />;
|
if (game?.progress === 1) return <HeroPanelPlaytime />;
|
||||||
|
|
||||||
console.log(lastPacket?.game.id, game?.id);
|
if (game?.status === "active") {
|
||||||
|
if (lastPacket?.downloadingMetadata && isGameDownloading) {
|
||||||
if (game?.status === "active" && lastPacket?.game.id === game?.id) {
|
return (
|
||||||
if (lastPacket?.downloadingMetadata) {
|
<>
|
||||||
return <p>{t("downloading_metadata")}</p>;
|
<p>{progress}</p>
|
||||||
|
<p>{t("downloading_metadata")}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sizeDownloaded = formatBytes(
|
||||||
|
lastPacket?.game?.bytesDownloaded ?? game?.bytesDownloaded
|
||||||
|
);
|
||||||
|
|
||||||
|
const showPeers =
|
||||||
|
game?.downloader === Downloader.Torrent &&
|
||||||
|
lastPacket?.numPeers !== undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className={styles.downloadDetailsRow}>
|
<p className={styles.downloadDetailsRow}>
|
||||||
{progress}
|
{isGameDownloading
|
||||||
|
? progress
|
||||||
|
: formatDownloadProgress(game?.progress)}
|
||||||
{eta && <small>{t("eta", { eta })}</small>}
|
{eta && <small>{t("eta", { eta })}</small>}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className={styles.downloadDetailsRow}>
|
<p className={styles.downloadDetailsRow}>
|
||||||
{formatBytes(lastPacket?.game?.bytesDownloaded ?? 0)} /{" "}
|
<span>
|
||||||
{finalDownloadSize}
|
{sizeDownloaded} / {finalDownloadSize}
|
||||||
{game?.downloader === Downloader.Torrent && (
|
</span>
|
||||||
|
{showPeers && (
|
||||||
<small>
|
<small>
|
||||||
{lastPacket?.numPeers} peers / {lastPacket?.numSeeds} seeds
|
{lastPacket?.numPeers} peers / {lastPacket?.numSeeds} seeds
|
||||||
</small>
|
</small>
|
||||||
@ -99,6 +116,10 @@ export function HeroPanel() {
|
|||||||
? (new Color(gameColor).darken(0.6).toString() as string)
|
? (new Color(gameColor).darken(0.6).toString() as string)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
|
const showProgressBar =
|
||||||
|
(game?.status === "active" && game?.progress < 1) ||
|
||||||
|
game?.status === "paused";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BinaryNotFoundModal
|
<BinaryNotFoundModal
|
||||||
@ -113,6 +134,18 @@ export function HeroPanel() {
|
|||||||
openBinaryNotFoundModal={() => setShowBinaryNotFoundModal(true)}
|
openBinaryNotFoundModal={() => setShowBinaryNotFoundModal(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showProgressBar && (
|
||||||
|
<progress
|
||||||
|
max={1}
|
||||||
|
value={
|
||||||
|
isGameDownloading ? lastPacket?.game.progress : game?.progress
|
||||||
|
}
|
||||||
|
className={styles.progressBar({
|
||||||
|
disabled: game?.status === "paused",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -10,10 +10,15 @@ import { SPACING_UNIT } from "../../../theme.css";
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { SelectFolderModal } from "./select-folder-modal";
|
import { SelectFolderModal } from "./select-folder-modal";
|
||||||
import { gameDetailsContext } from "../game-details.context";
|
import { gameDetailsContext } from "../game-details.context";
|
||||||
|
import { Downloader } from "@shared";
|
||||||
|
|
||||||
export interface RepacksModalProps {
|
export interface RepacksModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
startDownload: (repack: GameRepack, downloadPath: string) => Promise<void>;
|
startDownload: (
|
||||||
|
repack: GameRepack,
|
||||||
|
downloader: Downloader,
|
||||||
|
downloadPath: string
|
||||||
|
) => Promise<void>;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,15 +4,19 @@ import { Trans, useTranslation } from "react-i18next";
|
|||||||
import { DiskSpace } from "check-disk-space";
|
import { DiskSpace } from "check-disk-space";
|
||||||
import * as styles from "./select-folder-modal.css";
|
import * as styles from "./select-folder-modal.css";
|
||||||
import { Button, Link, Modal, TextField } from "@renderer/components";
|
import { Button, Link, Modal, TextField } from "@renderer/components";
|
||||||
import { DownloadIcon } from "@primer/octicons-react";
|
import { CheckCircleFillIcon, DownloadIcon } from "@primer/octicons-react";
|
||||||
import { formatBytes } from "@shared";
|
import { Downloader, formatBytes } from "@shared";
|
||||||
|
|
||||||
import type { GameRepack } from "@types";
|
import type { GameRepack, UserPreferences } from "@types";
|
||||||
|
|
||||||
export interface SelectFolderModalProps {
|
export interface SelectFolderModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
startDownload: (repack: GameRepack, downloadPath: string) => Promise<void>;
|
startDownload: (
|
||||||
|
repack: GameRepack,
|
||||||
|
downloader: Downloader,
|
||||||
|
downloadPath: string
|
||||||
|
) => Promise<void>;
|
||||||
repack: GameRepack | null;
|
repack: GameRepack | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +31,11 @@ export function SelectFolderModal({
|
|||||||
const [diskFreeSpace, setDiskFreeSpace] = useState<DiskSpace | null>(null);
|
const [diskFreeSpace, setDiskFreeSpace] = useState<DiskSpace | null>(null);
|
||||||
const [selectedPath, setSelectedPath] = useState("");
|
const [selectedPath, setSelectedPath] = useState("");
|
||||||
const [downloadStarting, setDownloadStarting] = useState(false);
|
const [downloadStarting, setDownloadStarting] = useState(false);
|
||||||
|
const [userPreferences, setUserPreferences] =
|
||||||
|
useState<UserPreferences | null>(null);
|
||||||
|
const [selectedDownloader, setSelectedDownloader] = useState(
|
||||||
|
Downloader.Torrent
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
visible && getDiskFreeSpace(selectedPath);
|
visible && getDiskFreeSpace(selectedPath);
|
||||||
@ -38,6 +47,11 @@ export function SelectFolderModal({
|
|||||||
window.electron.getUserPreferences(),
|
window.electron.getUserPreferences(),
|
||||||
]).then(([path, userPreferences]) => {
|
]).then(([path, userPreferences]) => {
|
||||||
setSelectedPath(userPreferences?.downloadsPath || path);
|
setSelectedPath(userPreferences?.downloadsPath || path);
|
||||||
|
setUserPreferences(userPreferences);
|
||||||
|
|
||||||
|
if (userPreferences?.realDebridApiToken) {
|
||||||
|
setSelectedDownloader(Downloader.RealDebrid);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -63,7 +77,7 @@ export function SelectFolderModal({
|
|||||||
if (repack) {
|
if (repack) {
|
||||||
setDownloadStarting(true);
|
setDownloadStarting(true);
|
||||||
|
|
||||||
startDownload(repack, selectedPath).finally(() => {
|
startDownload(repack, selectedDownloader, selectedPath).finally(() => {
|
||||||
setDownloadStarting(false);
|
setDownloadStarting(false);
|
||||||
onClose();
|
onClose();
|
||||||
});
|
});
|
||||||
@ -73,7 +87,7 @@ export function SelectFolderModal({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
visible={visible}
|
||||||
title={t("download_path")}
|
title="Download options"
|
||||||
description={t("space_left_on_disk", {
|
description={t("space_left_on_disk", {
|
||||||
space: formatBytes(diskFreeSpace?.free ?? 0),
|
space: formatBytes(diskFreeSpace?.free ?? 0),
|
||||||
})}
|
})}
|
||||||
@ -81,23 +95,43 @@ export function SelectFolderModal({
|
|||||||
>
|
>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ marginBottom: 0, padding: 0 }}>Download method</label>
|
<label style={{ marginBottom: 0, padding: 0 }}>Method</label>
|
||||||
|
|
||||||
<div className={styles.downloaders}>
|
<div className={styles.downloaders}>
|
||||||
<Button className={styles.downloaderOption} theme="outline">
|
<Button
|
||||||
|
className={styles.downloaderOption}
|
||||||
|
theme={
|
||||||
|
selectedDownloader === Downloader.Torrent
|
||||||
|
? "primary"
|
||||||
|
: "outline"
|
||||||
|
}
|
||||||
|
onClick={() => setSelectedDownloader(Downloader.Torrent)}
|
||||||
|
>
|
||||||
|
{selectedDownloader === Downloader.Torrent && (
|
||||||
|
<CheckCircleFillIcon />
|
||||||
|
)}
|
||||||
Torrent
|
Torrent
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={styles.downloaderOption}>Real Debrid</Button>
|
<Button
|
||||||
|
className={styles.downloaderOption}
|
||||||
|
theme={
|
||||||
|
selectedDownloader === Downloader.RealDebrid
|
||||||
|
? "primary"
|
||||||
|
: "outline"
|
||||||
|
}
|
||||||
|
onClick={() => setSelectedDownloader(Downloader.RealDebrid)}
|
||||||
|
disabled={!userPreferences?.realDebridApiToken}
|
||||||
|
>
|
||||||
|
{selectedDownloader === Downloader.RealDebrid && (
|
||||||
|
<CheckCircleFillIcon />
|
||||||
|
)}
|
||||||
|
Real Debrid
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.downloadsPathField}>
|
<div className={styles.downloadsPathField}>
|
||||||
<TextField
|
<TextField value={selectedPath} readOnly disabled label="Path" />
|
||||||
value={selectedPath}
|
|
||||||
readOnly
|
|
||||||
disabled
|
|
||||||
label="Download path"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style={{ alignSelf: "flex-end" }}
|
style={{ alignSelf: "flex-end" }}
|
||||||
|
@ -9,6 +9,8 @@ export const [themeClass, vars] = createTheme({
|
|||||||
muted: "#c0c1c7",
|
muted: "#c0c1c7",
|
||||||
bodyText: "#8e919b",
|
bodyText: "#8e919b",
|
||||||
border: "#424244",
|
border: "#424244",
|
||||||
|
success: "#1c9749",
|
||||||
|
danger: "#e11d48",
|
||||||
},
|
},
|
||||||
opacity: {
|
opacity: {
|
||||||
disabled: "0.5",
|
disabled: "0.5",
|
||||||
|
@ -148,7 +148,7 @@ export interface SteamGame {
|
|||||||
clientIcon: string | null;
|
clientIcon: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppUpdaterEvents =
|
export type AppUpdaterEvent =
|
||||||
| { type: "error" }
|
| { type: "error" }
|
||||||
| { type: "checking-for-updates" }
|
| { type: "checking-for-updates" }
|
||||||
| { type: "update-not-available" }
|
| { type: "update-not-available" }
|
||||||
@ -156,3 +156,13 @@ export type AppUpdaterEvents =
|
|||||||
| { type: "update-downloaded" }
|
| { type: "update-downloaded" }
|
||||||
| { type: "download-progress"; info: ProgressInfo }
|
| { type: "download-progress"; info: ProgressInfo }
|
||||||
| { type: "update-cancelled" };
|
| { type: "update-cancelled" };
|
||||||
|
|
||||||
|
/* Events */
|
||||||
|
export interface StartGameDownloadPayload {
|
||||||
|
repackId: number;
|
||||||
|
objectID: string;
|
||||||
|
title: string;
|
||||||
|
shop: GameShop;
|
||||||
|
downloadPath: string;
|
||||||
|
downloader: Downloader;
|
||||||
|
}
|
||||||
|
74
yarn.lock
74
yarn.lock
@ -1323,7 +1323,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~5.26.4"
|
undici-types "~5.26.4"
|
||||||
|
|
||||||
"@types/node@^18.11.18", "@types/node@^18.7.13":
|
"@types/node@^18.11.18":
|
||||||
version "18.19.33"
|
version "18.19.33"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.33.tgz#98cd286a1b8a5e11aa06623210240bcc28e95c48"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.33.tgz#98cd286a1b8a5e11aa06623210240bcc28e95c48"
|
||||||
integrity sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==
|
integrity sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==
|
||||||
@ -1406,11 +1406,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.10.tgz#d5a4b56abac169bfbc8b23d291363a682e6fa087"
|
resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.10.tgz#d5a4b56abac169bfbc8b23d291363a682e6fa087"
|
||||||
integrity sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg==
|
integrity sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg==
|
||||||
|
|
||||||
"@types/when@^2.4.34":
|
|
||||||
version "2.4.41"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/when/-/when-2.4.41.tgz#e16e685aa739c696a582b10afc5f1306964846a2"
|
|
||||||
integrity sha512-o/j5X9Bnv6mMG4ZcNJur8UaU17Rl0mLbTZvWcODVVy+Xdh8LEc7s6I0CvbEuTP786LTa0OyJby5P4hI7C+ZJNg==
|
|
||||||
|
|
||||||
"@types/yauzl@^2.9.1":
|
"@types/yauzl@^2.9.1":
|
||||||
version "2.10.3"
|
version "2.10.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999"
|
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999"
|
||||||
@ -2686,11 +2681,6 @@ eastasianwidth@^0.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||||
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||||
|
|
||||||
easydl@^1.1.1:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/easydl/-/easydl-1.2.0.tgz#b2d8bd7e72c055fedc367db4b7b9585eed55c245"
|
|
||||||
integrity sha512-v0PnU1HGqWd6VAiJY6wwzjsznNVTeFV0YgJcv52qLexR9f5ndsPgwu9Dmf9bGC5j2ZKrO9wqORpulDM7WUBNKA==
|
|
||||||
|
|
||||||
ejs@^3.1.8:
|
ejs@^3.1.8:
|
||||||
version "3.1.10"
|
version "3.1.10"
|
||||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
|
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
|
||||||
@ -4548,11 +4538,6 @@ minimatch@^9.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^2.0.1"
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
minimist@1.2.6:
|
|
||||||
version "1.2.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
|
||||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
|
||||||
|
|
||||||
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.8:
|
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.8:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||||
@ -4662,20 +4647,6 @@ no-case@^3.0.4:
|
|||||||
lower-case "^2.0.2"
|
lower-case "^2.0.2"
|
||||||
tslib "^2.0.3"
|
tslib "^2.0.3"
|
||||||
|
|
||||||
node-7z-archive@^1.1.7:
|
|
||||||
version "1.1.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-7z-archive/-/node-7z-archive-1.1.7.tgz#0b037701e016a651d6040b63d8781b2e7102facd"
|
|
||||||
integrity sha512-gtpWpajFyzeObGiYI9RDq76x5ULnxInvZ1OfA0/MD+2VezcMmMQMK6ITqkvsGEqVy4w/psvmIyowVDoSURAJHg==
|
|
||||||
dependencies:
|
|
||||||
fs-extra "^10.1.0"
|
|
||||||
minimist "^1.2.8"
|
|
||||||
node-sys "^1.2.2"
|
|
||||||
node-unar "^1.0.8"
|
|
||||||
node-wget-fetch "^1.1.3"
|
|
||||||
when "^3.7.8"
|
|
||||||
optionalDependencies:
|
|
||||||
"@types/when" "^2.4.34"
|
|
||||||
|
|
||||||
node-abi@^3.3.0:
|
node-abi@^3.3.0:
|
||||||
version "3.62.0"
|
version "3.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.62.0.tgz#017958ed120f89a3a14a7253da810f5d724e3f36"
|
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.62.0.tgz#017958ed120f89a3a14a7253da810f5d724e3f36"
|
||||||
@ -4693,7 +4664,7 @@ node-domexception@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
|
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
|
||||||
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
|
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
|
||||||
|
|
||||||
node-fetch@^2.6.1, node-fetch@^2.6.7:
|
node-fetch@^2.6.1:
|
||||||
version "2.7.0"
|
version "2.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
|
||||||
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
||||||
@ -4714,40 +4685,6 @@ node-releases@^2.0.14:
|
|||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
|
||||||
integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
|
integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
|
||||||
|
|
||||||
node-stream-zip@^1.12.0:
|
|
||||||
version "1.15.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea"
|
|
||||||
integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==
|
|
||||||
|
|
||||||
node-sys@^1.1.7, node-sys@^1.2.2:
|
|
||||||
version "1.2.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-sys/-/node-sys-1.2.4.tgz#db9c50fd93c8fc62bc4eafe93eae0fd3696c8028"
|
|
||||||
integrity sha512-71sIz+zgaHfSmP1vHTHXUVb77PqncIB1MBij+Q43fQSz7ceSLrrO5RTTBlnYWDU/M0fEFTZw3Zui/lVeJvoeag==
|
|
||||||
dependencies:
|
|
||||||
minimist "1.2.6"
|
|
||||||
which "^2.0.2"
|
|
||||||
optionalDependencies:
|
|
||||||
"@types/node" "^18.7.13"
|
|
||||||
|
|
||||||
node-unar@^1.0.8:
|
|
||||||
version "1.0.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-unar/-/node-unar-1.0.8.tgz#fbf5b05da2ac24278b6160f3b46231d56a73a673"
|
|
||||||
integrity sha512-AnEdWmV8/Dx1qMB5O2VcemoBmNzW1mhibYNl3YDUI7cVohVuobuIZwxrtRedItO05A6PiLp/HNw1ryg7M17H5g==
|
|
||||||
dependencies:
|
|
||||||
node-sys "^1.1.7"
|
|
||||||
when "^3.7.8"
|
|
||||||
optionalDependencies:
|
|
||||||
fs-extra "^9.0.1"
|
|
||||||
node-stream-zip "^1.12.0"
|
|
||||||
node-wget-fetch "^1.1.2"
|
|
||||||
|
|
||||||
node-wget-fetch@^1.1.2, node-wget-fetch@^1.1.3:
|
|
||||||
version "1.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-wget-fetch/-/node-wget-fetch-1.1.3.tgz#1e4aea2d7093393a961bb9c07cf5c5e33913c437"
|
|
||||||
integrity sha512-TmjZeeL/zAcB4fpok2iJ6FLbjVzSsjKi7rdk0womqvUY2ouitsEN0kGekndshaB7ENnXocrcgUudpvB4Jo3+LA==
|
|
||||||
dependencies:
|
|
||||||
node-fetch "^2.6.7"
|
|
||||||
|
|
||||||
normalize-url@^6.0.1:
|
normalize-url@^6.0.1:
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
||||||
@ -6234,11 +6171,6 @@ whatwg-url@^5.0.0:
|
|||||||
tr46 "~0.0.3"
|
tr46 "~0.0.3"
|
||||||
webidl-conversions "^3.0.0"
|
webidl-conversions "^3.0.0"
|
||||||
|
|
||||||
when@^3.7.8:
|
|
||||||
version "3.7.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82"
|
|
||||||
integrity sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==
|
|
||||||
|
|
||||||
which-boxed-primitive@^1.0.2:
|
which-boxed-primitive@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||||
@ -6289,7 +6221,7 @@ which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9:
|
|||||||
gopd "^1.0.1"
|
gopd "^1.0.1"
|
||||||
has-tostringtag "^1.0.2"
|
has-tostringtag "^1.0.2"
|
||||||
|
|
||||||
which@^2.0.1, which@^2.0.2:
|
which@^2.0.1:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||||
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
|
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
|
||||||
|
Loading…
Reference in New Issue
Block a user