From e64a414309c3d3ff43dffaf411c7bca546111a13 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 25 Sep 2024 19:37:28 +0100 Subject: [PATCH 01/22] feat: adding cloud sync --- .gitignore | 5 +- electron-builder.yml | 1 + package.json | 7 +- postinstall.cjs | 44 +++ src/main/constants.ts | 2 + .../check-game-cloud-sync-support.ts | 14 + .../events/cloud-sync/delete-game-artifact.ts | 9 + .../cloud-sync/download-game-artifact.ts | 56 ++++ .../events/cloud-sync/get-game-artifacts.ts | 18 ++ .../cloud-sync/get-game-backup-preview.ts | 17 ++ .../events/cloud-sync/upload-save-game.ts | 101 +++++++ src/main/events/index.ts | 6 + src/main/events/profile/update-profile.ts | 3 + src/main/services/index.ts | 1 + src/main/services/ludusavi.ts | 63 ++++ src/main/services/steam-grid.ts | 3 +- src/main/workers/ludusavi.worker.ts | 61 ++++ src/preload/index.ts | 36 +++ src/renderer/src/assets/lottie/cloud.json | 1 + .../context/cloud-sync/cloud-sync.context.tsx | 187 ++++++++++++ .../game-details/game-details.context.tsx | 27 +- src/renderer/src/context/index.ts | 1 + src/renderer/src/declaration.d.ts | 33 +++ src/renderer/src/dexie.ts | 12 +- .../cloud-sync-modal/cloud-sync-modal.css.ts | 26 ++ .../cloud-sync-modal/cloud-sync-modal.tsx | 178 ++++++++++++ .../game-details/game-details-content.tsx | 35 ++- .../pages/game-details/game-details.css.ts | 36 ++- .../src/pages/game-details/game-details.tsx | 134 +++++---- src/types/index.ts | 10 + src/types/ludusavi.types.ts | 23 ++ torrent-client/torrent_downloader.py | 11 +- yarn.lock | 275 +++++++++++++++++- 33 files changed, 1352 insertions(+), 84 deletions(-) create mode 100644 postinstall.cjs create mode 100644 src/main/events/cloud-sync/check-game-cloud-sync-support.ts create mode 100644 src/main/events/cloud-sync/delete-game-artifact.ts create mode 100644 src/main/events/cloud-sync/download-game-artifact.ts create mode 100644 src/main/events/cloud-sync/get-game-artifacts.ts create mode 100644 src/main/events/cloud-sync/get-game-backup-preview.ts create mode 100644 src/main/events/cloud-sync/upload-save-game.ts create mode 100644 src/main/services/ludusavi.ts create mode 100644 src/main/workers/ludusavi.worker.ts create mode 100644 src/renderer/src/assets/lottie/cloud.json create mode 100644 src/renderer/src/context/cloud-sync/cloud-sync.context.tsx create mode 100644 src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.css.ts create mode 100644 src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx create mode 100644 src/types/ludusavi.types.ts diff --git a/.gitignore b/.gitignore index fb4badd7..b9dcfecb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -.vscode -node_modules +.vscode/ +node_modules/ hydra-download-manager/ fastlist.exe __pycache__ @@ -10,3 +10,4 @@ out .env .vite sentry.properties +ludusavi/ \ No newline at end of file diff --git a/electron-builder.yml b/electron-builder.yml index a085b1e9..46f4a872 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -3,6 +3,7 @@ productName: Hydra directories: buildResources: build extraResources: + - ludusavi - hydra-download-manager - seeds - from: node_modules/create-desktop-shortcuts/src/windows.vbs diff --git a/package.json b/package.json index 6fd3f905..08f096f7 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "electron-vite preview", "dev": "electron-vite dev", "build": "npm run typecheck && electron-vite build", - "postinstall": "electron-builder install-app-deps", + "postinstall": "electron-builder install-app-deps && node ./postinstall.cjs", "build:unpack": "npm run build && electron-builder --dir", "build:win": "electron-vite build && electron-builder --win", "build:mac": "electron-vite build && electron-builder --mac", @@ -42,6 +42,8 @@ "@vanilla-extract/css": "^1.14.2", "@vanilla-extract/dynamic": "^2.1.1", "@vanilla-extract/recipes": "^0.5.2", + "adm-zip": "^0.5.16", + "archiver": "^7.0.1", "auto-launch": "^5.0.6", "axios": "^1.7.7", "better-sqlite3": "^11.2.1", @@ -86,8 +88,11 @@ "@electron-toolkit/tsconfig": "^1.0.1", "@sentry/vite-plugin": "^2.20.1", "@swc/core": "^1.4.16", + "@types/adm-zip": "^0.5.5", + "@types/archiver": "^6.0.2", "@types/auto-launch": "^5.0.5", "@types/color": "^3.0.6", + "@types/folder-hash": "^4.0.4", "@types/jsdom": "^21.1.6", "@types/jsonwebtoken": "^9.0.6", "@types/lodash-es": "^4.17.12", diff --git a/postinstall.cjs b/postinstall.cjs new file mode 100644 index 00000000..ce9c5909 --- /dev/null +++ b/postinstall.cjs @@ -0,0 +1,44 @@ +const { default: axios } = require("axios"); +const util = require("node:util"); +const fs = require("node:fs"); +const path = require("node:path"); + +const exec = util.promisify(require("node:child_process").exec); + +const fileName = { + win32: "ludusavi-v0.25.0-win64.zip", + linux: "ludusavi-v0.25.0-linux.zip", + darwin: "ludusavi-v0.25.0-mac.zip", +}; + +const downloadLudusavi = async () => { + if (fs.existsSync("ludusavi")) { + console.log("Ludusavi already exists, skipping download..."); + return; + } + + const file = fileName[process.platform]; + const downloadUrl = `https://github.com/mtkennerly/ludusavi/releases/download/v0.25.0/${file}`; + + console.log(`Downloading ${file}...`); + + const response = await axios.get(downloadUrl, { responseType: "stream" }); + + const stream = response.data.pipe(fs.createWriteStream(file)); + + stream.on("finish", async () => { + console.log(`Downloaded ${file}, extracting...`); + + const pwd = process.cwd(); + const targetPath = path.join(pwd, "ludusavi"); + + await exec(`npx extract-zip ${file} ${targetPath}`); + fs.chmodSync(path.join(targetPath, "ludusavi"), 0o755); + console.log("Extracted. Renaming folder..."); + + console.log(`Extracted ${file}, removing compressed downloaded file...`); + fs.rmSync(file); + }); +}; + +downloadLudusavi(); diff --git a/src/main/constants.ts b/src/main/constants.ts index 92973118..8af17a44 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -12,4 +12,6 @@ export const seedsPath = app.isPackaged ? path.join(process.resourcesPath, "seeds") : path.join(__dirname, "..", "..", "seeds"); +export const backupsPath = path.join(app.getPath("userData"), "Backups"); + export const appVersion = app.getVersion(); diff --git a/src/main/events/cloud-sync/check-game-cloud-sync-support.ts b/src/main/events/cloud-sync/check-game-cloud-sync-support.ts new file mode 100644 index 00000000..4054d430 --- /dev/null +++ b/src/main/events/cloud-sync/check-game-cloud-sync-support.ts @@ -0,0 +1,14 @@ +import { registerEvent } from "../register-event"; +import { GameShop } from "@types"; +import { Ludusavi } from "@main/services"; + +const checkGameCloudSyncSupport = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop +) => { + const games = await Ludusavi.findGames(shop, objectId); + return games.length === 1; +}; + +registerEvent("checkGameCloudSyncSupport", checkGameCloudSyncSupport); diff --git a/src/main/events/cloud-sync/delete-game-artifact.ts b/src/main/events/cloud-sync/delete-game-artifact.ts new file mode 100644 index 00000000..fa869896 --- /dev/null +++ b/src/main/events/cloud-sync/delete-game-artifact.ts @@ -0,0 +1,9 @@ +import { HydraApi } from "@main/services"; +import { registerEvent } from "../register-event"; + +const deleteGameArtifact = async ( + _event: Electron.IpcMainInvokeEvent, + gameArtifactId: string +) => HydraApi.delete<{ ok: boolean }>(`/games/artifacts/${gameArtifactId}`); + +registerEvent("deleteGameArtifact", deleteGameArtifact); diff --git a/src/main/events/cloud-sync/download-game-artifact.ts b/src/main/events/cloud-sync/download-game-artifact.ts new file mode 100644 index 00000000..a1254dc3 --- /dev/null +++ b/src/main/events/cloud-sync/download-game-artifact.ts @@ -0,0 +1,56 @@ +import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services"; +import fs from "node:fs"; +import AdmZip from "adm-zip"; +import { registerEvent } from "../register-event"; +import axios from "axios"; +import { app } from "electron"; +import path from "node:path"; +import { backupsPath } from "@main/constants"; +import type { GameShop } from "@types"; + +const downloadGameArtifact = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop, + gameArtifactId: string +) => { + const { downloadUrl, objectKey } = await HydraApi.post<{ + downloadUrl: string; + objectKey: string; + }>(`/games/artifacts/${gameArtifactId}/download`); + + const response = await axios.get(downloadUrl, { + responseType: "stream", + }); + + const zipLocation = path.join(app.getPath("userData"), objectKey); + const backupPath = path.join(backupsPath, `${shop}-${objectId}`); + + const writer = fs.createWriteStream(zipLocation); + + response.data.pipe(writer); + + writer.on("error", (err) => { + logger.error("Failed to write zip", err); + throw err; + }); + + writer.on("close", () => { + const zip = new AdmZip(zipLocation); + zip.extractAllToAsync(backupPath, true, true, (err) => { + if (err) { + logger.error("Failed to extract zip", err); + throw err; + } + + Ludusavi.restoreBackup(backupPath).then(() => { + WindowManager.mainWindow?.webContents.send( + `on-download-complete-${objectId}-${shop}`, + true + ); + }); + }); + }); +}; + +registerEvent("downloadGameArtifact", downloadGameArtifact); diff --git a/src/main/events/cloud-sync/get-game-artifacts.ts b/src/main/events/cloud-sync/get-game-artifacts.ts new file mode 100644 index 00000000..b32dfd79 --- /dev/null +++ b/src/main/events/cloud-sync/get-game-artifacts.ts @@ -0,0 +1,18 @@ +import { HydraApi } from "@main/services"; +import { registerEvent } from "../register-event"; +import type { GameArtifact, GameShop } from "@types"; + +const getGameArtifacts = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop +) => { + const params = new URLSearchParams({ + objectId, + shop, + }); + + return HydraApi.get(`/games/artifacts?${params.toString()}`); +}; + +registerEvent("getGameArtifacts", getGameArtifacts); diff --git a/src/main/events/cloud-sync/get-game-backup-preview.ts b/src/main/events/cloud-sync/get-game-backup-preview.ts new file mode 100644 index 00000000..433fccc4 --- /dev/null +++ b/src/main/events/cloud-sync/get-game-backup-preview.ts @@ -0,0 +1,17 @@ +import { registerEvent } from "../register-event"; +import { GameShop } from "@types"; +import { Ludusavi } from "@main/services"; +import path from "node:path"; +import { backupsPath } from "@main/constants"; + +const getGameBackupPreview = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop +) => { + const backupPath = path.join(backupsPath, `${shop}-${objectId}`); + + return Ludusavi.getBackupPreview(shop, objectId, backupPath); +}; + +registerEvent("getGameBackupPreview", getGameBackupPreview); diff --git a/src/main/events/cloud-sync/upload-save-game.ts b/src/main/events/cloud-sync/upload-save-game.ts new file mode 100644 index 00000000..0c9a4fbd --- /dev/null +++ b/src/main/events/cloud-sync/upload-save-game.ts @@ -0,0 +1,101 @@ +import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services"; +import { registerEvent } from "../register-event"; +import fs from "node:fs"; +import path from "node:path"; +import archiver from "archiver"; +import crypto from "node:crypto"; +import { GameShop } from "@types"; +import axios from "axios"; +import os from "node:os"; +import { app } from "electron"; +import { backupsPath } from "@main/constants"; + +const compressBackupToArtifact = async ( + shop: GameShop, + objectId: string, + cb: (zipLocation: string) => void +) => { + const backupPath = path.join(backupsPath, `${shop}-${objectId}`); + + await Ludusavi.backupGame(shop, objectId, backupPath); + + const archive = archiver("zip", { + zlib: { level: 9 }, + }); + + const zipLocation = path.join( + app.getPath("userData"), + `${crypto.randomUUID()}.zip` + ); + + const output = fs.createWriteStream(zipLocation); + + output.on("close", () => { + cb(zipLocation); + }); + + output.on("error", (err) => { + logger.error("Failed to compress folder", err); + throw err; + }); + + archive.pipe(output); + + archive.directory(backupPath, false); + archive.finalize(); +}; + +const uploadSaveGame = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop +) => { + compressBackupToArtifact(shop, objectId, (zipLocation) => { + fs.stat(zipLocation, async (err, stat) => { + if (err) { + logger.error("Failed to get zip file stats", err); + throw err; + } + + const { uploadUrl } = await HydraApi.post<{ + id: string; + uploadUrl: string; + }>("/games/artifacts", { + artifactLengthInBytes: stat.size, + shop, + objectId, + hostname: os.hostname(), + }); + + fs.readFile(zipLocation, async (err, fileBuffer) => { + if (err) { + logger.error("Failed to read zip file", err); + throw err; + } + + axios.put(uploadUrl, fileBuffer, { + headers: { + "Content-Type": "application/zip", + }, + onUploadProgress: (progressEvent) => { + if (progressEvent.progress === 1) { + fs.rm(zipLocation, (err) => { + if (err) { + logger.error("Failed to remove zip file", err); + throw err; + } + + WindowManager.mainWindow?.webContents.send( + `on-upload-complete-${objectId}-${shop}`, + true + ); + }); + } + }, + }); + }); + }); + }); +}; + +registerEvent("uploadSaveGame", uploadSaveGame); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 73bf38f4..4caa577c 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -58,6 +58,12 @@ import "./profile/update-profile"; import "./profile/process-profile-image"; import "./profile/send-friend-request"; import "./profile/sync-friend-requests"; +import "./cloud-sync/download-game-artifact"; +import "./cloud-sync//get-game-artifacts"; +import "./cloud-sync/get-game-backup-preview"; +import "./cloud-sync/upload-save-game"; +import "./cloud-sync/check-game-cloud-sync-support"; +import "./cloud-sync/delete-game-artifact"; import { isPortableVersion } from "@main/helpers"; ipcMain.handle("ping", () => "pong"); diff --git a/src/main/events/profile/update-profile.ts b/src/main/events/profile/update-profile.ts index eb80bc47..4135aae5 100644 --- a/src/main/events/profile/update-profile.ts +++ b/src/main/events/profile/update-profile.ts @@ -33,6 +33,9 @@ const getNewProfileImageUrl = async (localImageUrl: string) => { headers: { "Content-Type": mimeType, }, + onUploadProgress: (progressEvent) => { + console.log(progressEvent); + }, }); return profileImageUrl; diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 255b3871..8c6e6cda 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -9,3 +9,4 @@ export * from "./process-watcher"; export * from "./main-loop"; export * from "./repacks-manager"; export * from "./hydra-api"; +export * from "./ludusavi"; diff --git a/src/main/services/ludusavi.ts b/src/main/services/ludusavi.ts new file mode 100644 index 00000000..838b5f9b --- /dev/null +++ b/src/main/services/ludusavi.ts @@ -0,0 +1,63 @@ +import { GameShop, LudusaviBackup } from "@types"; +import Piscina from "piscina"; + +import { app } from "electron"; +import path from "node:path"; + +import ludusaviWorkerPath from "../workers/ludusavi.worker?modulePath"; + +const binaryPath = app.isPackaged + ? path.join(process.resourcesPath, "ludusavi", "ludusavi") + : path.join(__dirname, "..", "..", "ludusavi", "ludusavi"); + +export class Ludusavi { + private static worker = new Piscina({ + filename: ludusaviWorkerPath, + workerData: { + binaryPath, + }, + }); + + static async findGames(shop: GameShop, objectId: string): Promise { + const games = await this.worker.run( + { objectId, shop }, + { name: "findGames" } + ); + + return games; + } + + static async backupGame( + shop: GameShop, + objectId: string, + backupPath: string + ): Promise { + const games = await this.findGames(shop, objectId); + if (!games.length) throw new Error("Game not found"); + + return this.worker.run( + { title: games[0], backupPath }, + { name: "backupGame" } + ); + } + + static async getBackupPreview( + shop: GameShop, + objectId: string, + backupPath: string + ): Promise { + const games = await this.findGames(shop, objectId); + if (!games.length) return null; + + const backupData = await this.worker.run( + { title: games[0], backupPath, preview: true }, + { name: "backupGame" } + ); + + return backupData; + } + + static async restoreBackup(backupPath: string) { + return this.worker.run(backupPath, { name: "restoreBackup" }); + } +} diff --git a/src/main/services/steam-grid.ts b/src/main/services/steam-grid.ts index c762eaf6..2bdee28d 100644 --- a/src/main/services/steam-grid.ts +++ b/src/main/services/steam-grid.ts @@ -1,3 +1,4 @@ +import type { GameShop } from "@types"; import axios from "axios"; export interface SteamGridResponse { @@ -22,7 +23,7 @@ export interface SteamGridGameResponse { export const getSteamGridData = async ( objectID: string, path: string, - shop: string, + shop: GameShop, params: Record = {} ): Promise => { const searchParams = new URLSearchParams(params); diff --git a/src/main/workers/ludusavi.worker.ts b/src/main/workers/ludusavi.worker.ts new file mode 100644 index 00000000..2a1d266c --- /dev/null +++ b/src/main/workers/ludusavi.worker.ts @@ -0,0 +1,61 @@ +import type { GameShop, LudusaviBackup, LudusaviFindResult } from "@types"; +import cp from "node:child_process"; + +import { workerData } from "node:worker_threads"; + +const { binaryPath } = workerData; + +export const findGames = ({ + shop, + objectId, +}: { + shop: GameShop; + objectId: string; +}) => { + const args = ["find", "--api"]; + + if (shop === "steam") { + args.push("--steam-id", objectId); + } + + const result = cp.execFileSync(binaryPath, args); + + const games = JSON.parse(result.toString("utf-8")) as LudusaviFindResult; + return Object.keys(games.games); +}; + +export const backupGame = ({ + title, + backupPath, + preview = false, +}: { + title: string; + backupPath: string; + preview?: boolean; +}) => { + const args = ["backup", title, "--api", "--force"]; + + if (preview) { + args.push("--preview"); + } + + if (backupPath) { + args.push("--path", backupPath); + } + + const result = cp.execFileSync(binaryPath, args); + + return JSON.parse(result.toString("utf-8")) as LudusaviBackup; +}; + +export const restoreBackup = (backupPath: string) => { + const result = cp.execFileSync(binaryPath, [ + "restore", + "--path", + backupPath, + "--api", + "--force", + ]); + + return JSON.parse(result.toString("utf-8")) as LudusaviBackup; +}; diff --git a/src/preload/index.ts b/src/preload/index.ts index 4d7b7183..32a747a5 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -115,6 +115,42 @@ contextBridge.exposeInMainWorld("electron", { getDiskFreeSpace: (path: string) => ipcRenderer.invoke("getDiskFreeSpace", path), + /* Cloud sync */ + uploadSaveGame: (objectId: string, shop: GameShop) => + ipcRenderer.invoke("uploadSaveGame", objectId, shop), + downloadGameArtifact: ( + objectId: string, + shop: GameShop, + gameArtifactId: string + ) => + ipcRenderer.invoke("downloadGameArtifact", objectId, shop, gameArtifactId), + getGameArtifacts: (objectId: string, shop: GameShop) => + ipcRenderer.invoke("getGameArtifacts", objectId, shop), + getGameBackupPreview: (objectId: string, shop: GameShop) => + ipcRenderer.invoke("getGameBackupPreview", objectId, shop), + checkGameCloudSyncSupport: (objectId: string, shop: GameShop) => + ipcRenderer.invoke("checkGameCloudSyncSupport", objectId, shop), + deleteGameArtifact: (gameArtifactId: string) => + ipcRenderer.invoke("deleteGameArtifact", gameArtifactId), + onUploadComplete: (objectId: string, shop: GameShop, cb: () => void) => { + const listener = (_event: Electron.IpcRendererEvent) => cb(); + ipcRenderer.on(`on-upload-complete-${objectId}-${shop}`, listener); + return () => + ipcRenderer.removeListener( + `on-upload-complete-${objectId}-${shop}`, + listener + ); + }, + onDownloadComplete: (objectId: string, shop: GameShop, cb: () => void) => { + const listener = (_event: Electron.IpcRendererEvent) => cb(); + ipcRenderer.on(`on-download-complete-${objectId}-${shop}`, listener); + return () => + ipcRenderer.removeListener( + `on-download-complete-${objectId}-${shop}`, + listener + ); + }, + /* Misc */ ping: () => ipcRenderer.invoke("ping"), getVersion: () => ipcRenderer.invoke("getVersion"), diff --git a/src/renderer/src/assets/lottie/cloud.json b/src/renderer/src/assets/lottie/cloud.json new file mode 100644 index 00000000..9df1e119 --- /dev/null +++ b/src/renderer/src/assets/lottie/cloud.json @@ -0,0 +1 @@ +{"v":"5.12.1","fr":30,"ip":0,"op":60,"w":400,"h":400,"nm":"Cloud","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[322.789,202.565,0],"to":[-1.5,-0.167,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[313.789,201.565,0],"to":[0,0,0],"ti":[-1.5,-0.167,0]},{"t":60,"s":[322.789,202.565,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-38.564],[38.564,0],[0,38.564],[-38.564,0]],"o":[[0,38.564],[-38.564,0],[0,-38.564],[38.564,0]],"v":[[69.827,0],[0,69.827],[-69.827,0],[0,-69.827]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[243.704,202.565,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[233.704,202.565,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"t":60,"s":[243.704,202.565,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-38.564],[38.564,0],[0,38.564],[-38.564,0]],"o":[[0,38.564],[-38.564,0],[0,-38.564],[38.564,0]],"v":[[69.827,0],[0,69.827],[-69.827,0],[0,-69.827]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[260.681,151.053,0],"to":[1.333,-1.333,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[268.681,143.053,0],"to":[0,0,0],"ti":[1.333,-1.333,0]},{"t":60,"s":[260.681,151.053,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-38.564],[38.564,0],[0,38.564],[-38.564,0]],"o":[[0,38.564],[-38.564,0],[0,-38.564],[38.564,0]],"v":[[69.827,0],[0,69.827],[-69.827,0],[0,-69.827]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[162.135,206.563,0],"to":[-0.833,-0.167,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[157.135,205.563,0],"to":[0,0,0],"ti":[-0.833,-0.167,0]},{"t":60,"s":[162.135,206.563,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-36.66],[36.66,0],[0,36.66],[-36.66,0]],"o":[[0,36.66],[-36.66,0],[0,-36.66],[36.66,0]],"v":[[66.378,0],[0,66.378],[-66.378,0],[0,-66.378]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[180.178,132.225,0],"to":[-0.5,-2.333,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[177.178,118.225,0],"to":[0,0,0],"ti":[-0.5,-2.333,0]},{"t":60,"s":[180.178,132.225,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-50.068],[50.068,0],[0,50.068],[-50.068,0]],"o":[[0,50.068],[-50.068,0],[0,-50.068],[50.068,0]],"v":[[90.655,0],[0,90.655],[-90.655,0],[0,-90.655]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[95.756,208.288,0],"to":[-1.167,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[88.756,208.288,0],"to":[0,0,0],"ti":[-1.167,0,0]},{"t":60,"s":[95.756,208.288,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-35.403],[35.403,0],[0,35.403],[-35.403,0]],"o":[[0,35.403],[-35.403,0],[0,-35.403],[35.403,0]],"v":[[64.103,0],[0,64.103],[-64.103,0],[0,-64.103]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":3,"nm":"Null 1","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[19.822,67.775,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":270,"st":0,"bm":0}],"markers":[],"props":{}} \ No newline at end of file diff --git a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx new file mode 100644 index 00000000..38bbdb40 --- /dev/null +++ b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx @@ -0,0 +1,187 @@ +import { gameBackupsTable } from "@renderer/dexie"; +import { useToast } from "@renderer/hooks"; +import type { LudusaviBackup, GameArtifact, GameShop } from "@types"; +import React, { + createContext, + useCallback, + useEffect, + useMemo, + useState, +} from "react"; + +export enum CloudSyncState { + New, + Different, + Same, + Unknown, +} + +export interface CloudSyncContext { + backupPreview: LudusaviBackup | null; + artifacts: GameArtifact[]; + showCloudSyncModal: boolean; + supportsCloudSync: boolean | null; + backupState: CloudSyncState; + setShowCloudSyncModal: React.Dispatch>; + downloadGameArtifact: (gameArtifactId: string) => Promise; + uploadSaveGame: () => Promise; + deleteGameArtifact: (gameArtifactId: string) => Promise<{ ok: boolean }>; + restoringBackup: boolean; + uploadingBackup: boolean; +} + +export const cloudSyncContext = createContext({ + backupPreview: null, + showCloudSyncModal: false, + supportsCloudSync: null, + backupState: CloudSyncState.Unknown, + setShowCloudSyncModal: () => {}, + downloadGameArtifact: async () => {}, + uploadSaveGame: async () => {}, + artifacts: [], + deleteGameArtifact: async () => ({ ok: false }), + restoringBackup: false, + uploadingBackup: false, +}); + +const { Provider } = cloudSyncContext; +export const { Consumer: CloudSyncContextConsumer } = cloudSyncContext; + +export interface CloudSyncContextProviderProps { + children: React.ReactNode; + objectId: string; + shop: GameShop; +} + +export function CloudSyncContextProvider({ + children, + objectId, + shop, +}: CloudSyncContextProviderProps) { + const [supportsCloudSync, setSupportsCloudSync] = useState( + null + ); + const [artifacts, setArtifacts] = useState([]); + const [showCloudSyncModal, setShowCloudSyncModal] = useState(false); + const [backupPreview, setBackupPreview] = useState( + null + ); + const [restoringBackup, setRestoringBackup] = useState(false); + const [uploadingBackup, setUploadingBackup] = useState(false); + + const { showSuccessToast } = useToast(); + + const downloadGameArtifact = useCallback( + async (gameArtifactId: string) => { + setRestoringBackup(true); + window.electron.downloadGameArtifact(objectId, shop, gameArtifactId); + }, + [objectId, shop] + ); + + const getGameBackupPreview = useCallback(async () => { + window.electron.getGameArtifacts(objectId, shop).then((results) => { + setArtifacts(results); + }); + + window.electron.getGameBackupPreview(objectId, shop).then((preview) => { + if (preview && Object.keys(preview.games).length) { + setBackupPreview(preview); + } + }); + }, [objectId, shop]); + + const uploadSaveGame = useCallback(async () => { + setUploadingBackup(true); + window.electron.uploadSaveGame(objectId, shop); + }, [objectId, shop]); + + useEffect(() => { + const removeUploadCompleteListener = window.electron.onUploadComplete( + objectId, + shop, + () => { + showSuccessToast("backup_uploaded"); + + setUploadingBackup(false); + gameBackupsTable.add({ + objectId, + shop, + createdAt: new Date(), + }); + + getGameBackupPreview(); + } + ); + + const removeDownloadCompleteListener = window.electron.onDownloadComplete( + objectId, + shop, + () => { + showSuccessToast("backup_restored"); + + setRestoringBackup(false); + getGameBackupPreview(); + } + ); + + return () => { + removeUploadCompleteListener(); + removeDownloadCompleteListener(); + }; + }, [objectId, shop, showSuccessToast, getGameBackupPreview]); + + const deleteGameArtifact = useCallback( + async (gameArtifactId: string) => { + return window.electron.deleteGameArtifact(gameArtifactId).then(() => { + getGameBackupPreview(); + return { ok: true }; + }); + }, + [getGameBackupPreview] + ); + + useEffect(() => { + getGameBackupPreview(); + + window.electron.checkGameCloudSyncSupport(objectId, shop).then((result) => { + setSupportsCloudSync(result); + }); + }, [objectId, shop, getGameBackupPreview]); + + useEffect(() => { + if (showCloudSyncModal) { + getGameBackupPreview(); + } + }, [getGameBackupPreview, showCloudSyncModal]); + + const backupState = useMemo(() => { + if (!backupPreview) return CloudSyncState.Unknown; + if (backupPreview.overall.changedGames.new) return CloudSyncState.New; + if (backupPreview.overall.changedGames.different) + return CloudSyncState.Different; + if (backupPreview.overall.changedGames.same) return CloudSyncState.Same; + + return CloudSyncState.Unknown; + }, [backupPreview]); + + return ( + + {children} + + ); +} diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 120728b1..82984d9a 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -5,7 +5,6 @@ import { useEffect, useState, } from "react"; -import { useParams, useSearchParams } from "react-router-dom"; import { setHeaderTitle } from "@renderer/features"; import { getSteamLanguage } from "@renderer/helpers"; @@ -51,13 +50,17 @@ export const { Consumer: GameDetailsContextConsumer } = gameDetailsContext; export interface GameDetailsContextProps { children: React.ReactNode; + objectId: string; + gameTitle: string; + shop: GameShop; } export function GameDetailsContextProvider({ children, + objectId, + gameTitle, + shop, }: GameDetailsContextProps) { - const { objectID, shop } = useParams(); - const [shopDetails, setShopDetails] = useState(null); const [game, setGame] = useState(null); const [hasNSFWContentBlocked, setHasNSFWContentBlocked] = useState(false); @@ -72,10 +75,6 @@ export function GameDetailsContextProvider({ const [repacks, setRepacks] = useState([]); - const [searchParams] = useSearchParams(); - - const gameTitle = searchParams.get("title")!; - const { searchRepacks, isIndexingRepacks } = useContext(repacksContext); useEffect(() => { @@ -98,9 +97,9 @@ export function GameDetailsContextProvider({ const updateGame = useCallback(async () => { return window.electron - .getGameByObjectID(objectID!) + .getGameByObjectID(objectId!) .then((result) => setGame(result)); - }, [setGame, objectID]); + }, [setGame, objectId]); const isGameDownloading = lastPacket?.game.id === game?.id; @@ -111,7 +110,7 @@ export function GameDetailsContextProvider({ useEffect(() => { window.electron .getGameShopDetails( - objectID!, + objectId!, shop as GameShop, getSteamLanguage(i18n.language) ) @@ -130,12 +129,12 @@ export function GameDetailsContextProvider({ setIsLoading(false); }); - window.electron.getGameStats(objectID!, shop as GameShop).then((result) => { + window.electron.getGameStats(objectId!, shop as GameShop).then((result) => { setStats(result); }); updateGame(); - }, [updateGame, dispatch, gameTitle, objectID, shop, i18n.language]); + }, [updateGame, dispatch, gameTitle, objectId, shop, i18n.language]); useEffect(() => { setShopDetails(null); @@ -143,7 +142,7 @@ export function GameDetailsContextProvider({ setIsLoading(true); setisGameRunning(false); dispatch(setHeaderTitle(gameTitle)); - }, [objectID, gameTitle, dispatch]); + }, [objectId, gameTitle, dispatch]); useEffect(() => { const unsubscribe = window.electron.onGamesRunning((gamesIds) => { @@ -200,7 +199,7 @@ export function GameDetailsContextProvider({ gameTitle, isGameRunning, isLoading, - objectID, + objectID: objectId, gameColor, showGameOptionsModal, showRepacksModal, diff --git a/src/renderer/src/context/index.ts b/src/renderer/src/context/index.ts index 8d8b9223..948b90b2 100644 --- a/src/renderer/src/context/index.ts +++ b/src/renderer/src/context/index.ts @@ -2,3 +2,4 @@ export * from "./game-details/game-details.context"; export * from "./settings/settings.context"; export * from "./user-profile/user-profile.context"; export * from "./repacks/repacks.context"; +export * from "./cloud-sync/cloud-sync.context"; diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 28c5caf7..6fab054a 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -26,6 +26,8 @@ import type { UserDetails, FriendRequestSync, DownloadSourceValidationResult, + GameArtifact, + LudusaviBackup, } from "@types"; import type { DiskSpace } from "check-disk-space"; @@ -113,6 +115,37 @@ declare global { /* Hardware */ getDiskFreeSpace: (path: string) => Promise; + /* Cloud sync */ + uploadSaveGame: (objectId: string, shop: GameShop) => Promise; + downloadGameArtifact: ( + objectId: string, + shop: GameShop, + gameArtifactId: string + ) => Promise; + getGameArtifacts: ( + objectId: string, + shop: GameShop + ) => Promise; + getGameBackupPreview: ( + objectId: string, + shop: GameShop + ) => Promise; + checkGameCloudSyncSupport: ( + objectId: string, + shop: GameShop + ) => Promise; + deleteGameArtifact: (gameArtifactId: string) => Promise<{ ok: boolean }>; + onDownloadComplete: ( + objectId: string, + shop: GameShop, + cb: () => void + ) => () => Electron.IpcRenderer; + onUploadComplete: ( + objectId: string, + shop: GameShop, + cb: () => void + ) => () => Electron.IpcRenderer; + /* Misc */ openExternal: (src: string) => Promise; getVersion: () => Promise; diff --git a/src/renderer/src/dexie.ts b/src/renderer/src/dexie.ts index 23f0bf83..75dc6079 100644 --- a/src/renderer/src/dexie.ts +++ b/src/renderer/src/dexie.ts @@ -1,13 +1,23 @@ +import { GameShop } from "@types"; import { Dexie } from "dexie"; +export interface GameBackup { + id?: number; + shop: GameShop; + objectId: string; + createdAt: Date; +} + export const db = new Dexie("Hydra"); -db.version(1).stores({ +db.version(3).stores({ repacks: `++id, title, uris, fileSize, uploadDate, downloadSourceId, repacker, createdAt, updatedAt`, downloadSources: `++id, url, name, etag, downloadCount, status, createdAt, updatedAt`, + gameBackups: `++id, [shop+objectId], createdAt`, }); export const downloadSourcesTable = db.table("downloadSources"); export const repacksTable = db.table("repacks"); +export const gameBackupsTable = db.table("gameBackups"); db.open(); diff --git a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.css.ts b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.css.ts new file mode 100644 index 00000000..bb3335fa --- /dev/null +++ b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.css.ts @@ -0,0 +1,26 @@ +import { style } from "@vanilla-extract/css"; + +import { SPACING_UNIT, vars } from "../../../theme.css"; + +export const artifacts = style({ + display: "flex", + gap: `${SPACING_UNIT}px`, + flexDirection: "column", + listStyle: "none", + margin: "0", + padding: "0", +}); + +export const artifactButton = style({ + display: "flex", + textAlign: "left", + flexDirection: "row", + alignItems: "center", + gap: `${SPACING_UNIT}px`, + color: vars.color.body, + padding: `${SPACING_UNIT * 2}px`, + backgroundColor: vars.color.darkBackground, + border: `1px solid ${vars.color.border}`, + borderRadius: "4px", + justifyContent: "space-between", +}); diff --git a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx new file mode 100644 index 00000000..fd38eb76 --- /dev/null +++ b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx @@ -0,0 +1,178 @@ +import { Button, Modal, ModalProps } from "@renderer/components"; +import { useContext, useEffect, useMemo, useState } from "react"; +import { cloudSyncContext, gameDetailsContext } from "@renderer/context"; + +import * as styles from "./cloud-sync-modal.css"; +import { formatBytes } from "@shared"; +import { format } from "date-fns"; +import { + CheckCircleFillIcon, + ClockIcon, + DeviceDesktopIcon, + DownloadIcon, + SyncIcon, + TrashIcon, + UploadIcon, +} from "@primer/octicons-react"; +import { useToast } from "@renderer/hooks"; +import { GameBackup, gameBackupsTable } from "@renderer/dexie"; + +export interface CloudSyncModalProps + extends Omit {} + +export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { + const [deletingArtifact, setDeletingArtifact] = useState(false); + const [lastBackup, setLastBackup] = useState(null); + + const { + artifacts, + backupPreview, + uploadingBackup, + restoringBackup, + uploadSaveGame, + downloadGameArtifact, + deleteGameArtifact, + } = useContext(cloudSyncContext); + + const { objectID, shop, gameTitle } = useContext(gameDetailsContext); + + const { showSuccessToast, showErrorToast } = useToast(); + + const handleDeleteArtifactClick = async (gameArtifactId: string) => { + setDeletingArtifact(true); + + try { + await deleteGameArtifact(gameArtifactId); + + showSuccessToast("backup_successfully_deleted"); + } catch (err) { + showErrorToast("backup_deletion_failed"); + } finally { + setDeletingArtifact(false); + } + }; + + useEffect(() => { + gameBackupsTable + .where({ shop: shop, objectId: objectID }) + .last() + .then((lastBackup) => setLastBackup(lastBackup || null)); + }, [backupPreview, objectID, shop]); + + const backupStateLabel = useMemo(() => { + if (uploadingBackup) { + return ( + + + creating_backup + + ); + } + + if (restoringBackup) { + return ( + + + restoring_backup + + ); + } + + if (lastBackup) { + return ( +

+ + Último backup em {format(lastBackup.createdAt, "dd/MM/yyyy HH:mm")} +

+ ); + } + + return "no_backups"; + }, [uploadingBackup, lastBackup, restoringBackup]); + + const disableActions = uploadingBackup || restoringBackup || deletingArtifact; + + return ( + +
+
+

{gameTitle}

+ {backupStateLabel} +
+ + +
+ +

backups

+ +
    + {artifacts.map((artifact) => ( +
  • +
    +
    +

    Backup do dia {format(artifact.createdAt, "dd/MM")}

    + {formatBytes(artifact.artifactLengthInBytes)} +
    + + + + {artifact.hostname} + + + + + {format(artifact.createdAt, "dd/MM/yyyy HH:mm:ss")} + +
    + +
    + + +
    +
  • + ))} +
+
+ ); +} diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 2ba19246..80974a14 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -9,8 +9,11 @@ import { Sidebar } from "./sidebar/sidebar"; import * as styles from "./game-details.css"; import { useTranslation } from "react-i18next"; -import { gameDetailsContext } from "@renderer/context"; +import { cloudSyncContext, gameDetailsContext } from "@renderer/context"; import { steamUrlBuilder } from "@shared"; +import Lottie from "lottie-react"; + +import downloadingAnimation from "@renderer/assets/lottie/cloud.json"; const HERO_ANIMATION_THRESHOLD = 25; @@ -30,6 +33,9 @@ export function GameDetailsContent() { hasNSFWContentBlocked, } = useContext(gameDetailsContext); + const { supportsCloudSync, setShowCloudSyncModal } = + useContext(cloudSyncContext); + const [backdropOpactiy, setBackdropOpacity] = useState(1); const handleHeroLoad = async () => { @@ -102,6 +108,33 @@ export function GameDetailsContent() { className={styles.gameLogo} alt={game?.title} /> + + {supportsCloudSync && ( + + )} diff --git a/src/renderer/src/pages/game-details/game-details.css.ts b/src/renderer/src/pages/game-details/game-details.css.ts index 2de28f1f..228b2aeb 100644 --- a/src/renderer/src/pages/game-details/game-details.css.ts +++ b/src/renderer/src/pages/game-details/game-details.css.ts @@ -6,8 +6,8 @@ import { recipe } from "@vanilla-extract/recipes"; export const HERO_HEIGHT = 300; export const slideIn = keyframes({ - "0%": { transform: `translateY(${40 + SPACING_UNIT * 2}px)` }, - "100%": { transform: "translateY(0)" }, + "0%": { transform: `translateY(${40 + SPACING_UNIT * 2}px)`, opacity: "0px" }, + "100%": { transform: "translateY(0)", opacity: "1" }, }); export const wrapper = recipe({ @@ -49,6 +49,8 @@ export const heroContent = style({ height: "100%", width: "100%", display: "flex", + justifyContent: "space-between", + alignItems: "flex-end", }); export const heroLogoBackdrop = style({ @@ -200,3 +202,33 @@ globalStyle(`${description} img`, { globalStyle(`${description} a`, { color: vars.color.body, }); + +export const cloudSyncButton = style({ + padding: `${SPACING_UNIT * 1.5}px ${SPACING_UNIT * 2}px`, + backgroundColor: "rgba(0, 0, 0, 0.6)", + backdropFilter: "blur(20px)", + borderRadius: "8px", + transition: "all ease 0.2s", + cursor: "pointer", + minHeight: "40px", + display: "flex", + alignItems: "center", + justifyContent: "center", + gap: `${SPACING_UNIT}px`, + color: vars.color.muted, + fontSize: "14px", + border: `solid 1px ${vars.color.border}`, + boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.8)", + animation: `${slideIn} 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running`, + animationDuration: "0.3s", + ":active": { + opacity: "0.9", + }, + ":disabled": { + opacity: vars.opacity.disabled, + cursor: "not-allowed", + }, + ":hover": { + backgroundColor: "rgba(0, 0, 0, 0.5)", + }, +}); diff --git a/src/renderer/src/pages/game-details/game-details.tsx b/src/renderer/src/pages/game-details/game-details.tsx index 54fe75ac..fbd59488 100644 --- a/src/renderer/src/pages/game-details/game-details.tsx +++ b/src/renderer/src/pages/game-details/game-details.tsx @@ -18,21 +18,25 @@ import { vars } from "@renderer/theme.css"; import { GameDetailsContent } from "./game-details-content"; import { + CloudSyncContextConsumer, + CloudSyncContextProvider, GameDetailsContextConsumer, GameDetailsContextProvider, } from "@renderer/context"; import { useDownload } from "@renderer/hooks"; import { GameOptionsModal, RepacksModal } from "./modals"; import { Downloader, getDownloadersForUri } from "@shared"; +import { CloudSyncModal } from "./cloud-sync-modal/cloud-sync-modal"; export function GameDetails() { const [randomGame, setRandomGame] = useState(null); const [randomizerLocked, setRandomizerLocked] = useState(false); - const { objectID } = useParams(); + const { objectID, shop } = useParams(); const [searchParams] = useSearchParams(); const fromRandomizer = searchParams.get("fromRandomizer"); + const gameTitle = searchParams.get("title"); const { startDownload } = useDownload(); @@ -74,7 +78,11 @@ export function GameDetails() { repack.uris.find((uri) => getDownloadersForUri(uri).includes(downloader))!; return ( - + {({ isLoading, @@ -115,64 +123,80 @@ export function GameDetails() { }; return ( - - {isLoading ? : } + + {({ showCloudSyncModal, setShowCloudSyncModal }) => ( + setShowCloudSyncModal(false)} + visible={showCloudSyncModal} + /> + )} + - setShowRepacksModal(false)} - /> + + {isLoading ? : } - setHasNSFWContentBlocked(false)} - clickOutsideToClose={false} - /> - - {game && ( - { - setShowGameOptionsModal(false); - }} + setShowRepacksModal(false)} /> - )} - {fromRandomizer && ( - - )} - + setHasNSFWContentBlocked(false)} + clickOutsideToClose={false} + /> + + {game && ( + { + setShowGameOptionsModal(false); + }} + /> + )} + + {fromRandomizer && ( + + )} + + ); }} diff --git a/src/types/index.ts b/src/types/index.ts index 9e6f7def..caf9bdd0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -266,5 +266,15 @@ export interface UserStats { friendsCount: number; } +export interface GameArtifact { + id: string; + artifactLengthInBytes: number; + createdAt: string; + updatedAt: string; + hostname: string; + downloadCount: number; +} + export * from "./steam.types"; export * from "./real-debrid.types"; +export * from "./ludusavi.types"; diff --git a/src/types/ludusavi.types.ts b/src/types/ludusavi.types.ts new file mode 100644 index 00000000..a2adebf9 --- /dev/null +++ b/src/types/ludusavi.types.ts @@ -0,0 +1,23 @@ +export interface LudusaviScanChange { + change: "New" | "Different" | "Removed" | "Same" | "Unknown"; + decision: "Processed" | "Cancelled" | "Ignore"; +} + +export interface LudusaviBackup { + overall: { + totalGames: number; + totalBytes: number; + processedGames: number; + processedBytes: number; + changedGames: { + new: number; + different: number; + same: number; + }; + }; + games: Record; +} + +export interface LudusaviFindResult { + games: Record; +} diff --git a/torrent-client/torrent_downloader.py b/torrent-client/torrent_downloader.py index d59cd28b..b5280260 100644 --- a/torrent-client/torrent_downloader.py +++ b/torrent-client/torrent_downloader.py @@ -144,8 +144,8 @@ class TorrentDownloader: status = torrent_handle.status() info = torrent_handle.get_torrent_info() - - return { + + response = { 'folderName': info.name() if info else "", 'fileSize': info.total_size() if info else 0, 'gameId': self.downloading_game_id, @@ -156,3 +156,10 @@ class TorrentDownloader: 'status': status.state, 'bytesDownloaded': status.progress * info.total_size() if info else status.all_time_download, } + + if status.progress == 1: + torrent_handle.pause() + self.session.remove_torrent(torrent_handle) + self.downloading_game_id = -1 + + return response diff --git a/yarn.lock b/yarn.lock index 14651b4b..75b9a7d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1923,6 +1923,20 @@ dependencies: "@types/node" "*" +"@types/adm-zip@^0.5.5": + version "0.5.5" + resolved "https://registry.yarnpkg.com/@types/adm-zip/-/adm-zip-0.5.5.tgz#4588042726aa5f351d7ea88232e4a952f60e7c1a" + integrity sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw== + dependencies: + "@types/node" "*" + +"@types/archiver@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/archiver/-/archiver-6.0.2.tgz#0daf8c83359cbde69de1e4b33dcade6a48a929e2" + integrity sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw== + dependencies: + "@types/readdir-glob" "*" + "@types/auto-launch@^5.0.5": version "5.0.5" resolved "https://registry.npmjs.org/@types/auto-launch/-/auto-launch-5.0.5.tgz" @@ -2066,6 +2080,11 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/folder-hash@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/folder-hash/-/folder-hash-4.0.4.tgz#c3262d58a01b756ee2aae3694707fad1ef676a9f" + integrity sha512-c+PwHm51Dw3fXM8SDK+93PO3oXdk4XNouCCvV67lj4aijRkZz5g67myk+9wqWWnyv3go6q96hT6ywcd3XtoZiQ== + "@types/fs-extra@9.0.13", "@types/fs-extra@^9.0.11": version "9.0.13" resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz" @@ -2292,6 +2311,13 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/readdir-glob@*": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@types/readdir-glob/-/readdir-glob-1.1.5.tgz#21a4a98898fc606cb568ad815f2a0eedc24d412a" + integrity sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg== + dependencies: + "@types/node" "*" + "@types/responselike@^1.0.0": version "1.0.3" resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz" @@ -2583,6 +2609,11 @@ acorn@^8.8.1, acorn@^8.8.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== +adm-zip@^0.5.16: + version "0.5.16" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909" + integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== + agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" @@ -2712,6 +2743,32 @@ applescript@^1.0.0: resolved "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz" integrity sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ== +archiver-utils@^5.0.0, archiver-utils@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-5.0.2.tgz#63bc719d951803efc72cf961a56ef810760dd14d" + integrity sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA== + dependencies: + glob "^10.0.0" + graceful-fs "^4.2.0" + is-stream "^2.0.1" + lazystream "^1.0.0" + lodash "^4.17.15" + normalize-path "^3.0.0" + readable-stream "^4.0.0" + +archiver@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-7.0.1.tgz#c9d91c350362040b8927379c7aa69c0655122f61" + integrity sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ== + dependencies: + archiver-utils "^5.0.2" + async "^3.2.4" + buffer-crc32 "^1.0.0" + readable-stream "^4.0.0" + readdir-glob "^1.1.2" + tar-stream "^3.0.0" + zip-stream "^6.0.1" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -2851,6 +2908,11 @@ async@^3.2.3: resolved "https://registry.npmjs.org/async/-/async-3.2.5.tgz" integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== +async@^3.2.4: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" @@ -2900,11 +2962,21 @@ axobject-query@^3.2.1: dependencies: dequal "^2.0.3" +b4a@^1.6.4: + version "1.6.6" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.6.tgz#a4cc349a3851987c3c4ac2d7785c18744f6da9ba" + integrity sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +bare-events@^2.2.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.0.tgz#305b511e262ffd8b9d5616b056464f8e1b3329cc" + integrity sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A== + base64-arraybuffer@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz" @@ -3017,6 +3089,11 @@ browserslist@^4.22.2: node-releases "^2.0.14" update-browserslist-db "^1.0.13" +buffer-crc32@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405" + integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w== + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" @@ -3337,6 +3414,17 @@ compare-version@^0.1.2: resolved "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz" integrity sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A== +compress-commons@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-6.0.2.tgz#26d31251a66b9d6ba23a84064ecd3a6a71d2609e" + integrity sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg== + dependencies: + crc-32 "^1.2.0" + crc32-stream "^6.0.0" + is-stream "^2.0.1" + normalize-path "^3.0.0" + readable-stream "^4.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -3389,6 +3477,11 @@ core-util-is@1.0.2: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cosmiconfig-typescript-loader@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz" @@ -3416,6 +3509,19 @@ cosmiconfig@^9.0.0: js-yaml "^4.1.0" parse-json "^5.2.0" +crc-32@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + +crc32-stream@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-6.0.0.tgz#8529a3868f8b27abb915f6c3617c0fadedbf9430" + integrity sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g== + dependencies: + crc-32 "^1.2.0" + readable-stream "^4.0.0" + crc@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" @@ -4239,6 +4345,11 @@ event-target-shim@^5.0.0: resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + execa@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz" @@ -4285,6 +4396,11 @@ fast-diff@^1.1.2: resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== +fast-fifo@^1.2.0, fast-fifo@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== + fast-glob@^3.2.9: version "3.3.2" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" @@ -4605,6 +4721,18 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob@^10.0.0: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^10.3.10: version "10.3.15" resolved "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz" @@ -4973,7 +5101,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -5170,6 +5298,11 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: dependencies: call-bind "^1.0.7" +is-stream@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + is-stream@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz" @@ -5228,6 +5361,11 @@ isarray@^2.0.5: resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isbinaryfile@^4.0.8: version "4.0.10" resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz" @@ -5263,6 +5401,15 @@ jackspeak@^2.3.6: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jake@^10.8.5: version "10.9.1" resolved "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz" @@ -5475,6 +5622,13 @@ lazy-val@^1.0.4, lazy-val@^1.0.5: resolved "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz" integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== +lazystream@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== + dependencies: + readable-stream "^2.0.5" + levn@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" @@ -5763,7 +5917,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1, minimatch@^5.1.1: +minimatch@^5.0.1, minimatch@^5.1.0, minimatch@^5.1.1: version "5.1.6" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== @@ -5784,6 +5938,13 @@ minimatch@^9.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" @@ -5811,6 +5972,11 @@ minipass@^5.0.0: resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz" integrity sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA== +minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + minizlib@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" @@ -6116,6 +6282,11 @@ p-locate@^6.0.0: dependencies: p-limit "^4.0.0" +package-json-from-dist@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -6199,7 +6370,7 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.11.0, path-scurry@^1.6.1: +path-scurry@^1.11.0, path-scurry@^1.11.1, path-scurry@^1.6.1: version "1.11.1" resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz" integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== @@ -6409,6 +6580,16 @@ prettier@^3.2.4: resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz" integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + progress@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" @@ -6469,6 +6650,11 @@ queue-microtask@^1.2.2, queue-microtask@^1.2.3: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +queue-tick@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142" + integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag== + quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz" @@ -6562,6 +6748,19 @@ read-config-file@6.3.2: json5 "^2.2.0" lazy-val "^1.0.4" +readable-stream@^2.0.5: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" @@ -6571,6 +6770,17 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^4.0.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" + integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + readable-web-to-node-stream@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz" @@ -6578,6 +6788,13 @@ readable-web-to-node-stream@^3.0.2: dependencies: readable-stream "^3.6.0" +readdir-glob@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" + integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== + dependencies: + minimatch "^5.1.0" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -6795,6 +7012,11 @@ safe-buffer@^5.0.1, safe-buffer@~5.2.0: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz" @@ -7012,6 +7234,17 @@ stat-mode@^1.0.0: resolved "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz" integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg== +streamx@^2.15.0: + version "2.20.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.20.1.tgz#471c4f8b860f7b696feb83d5b125caab2fdbb93c" + integrity sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA== + dependencies: + fast-fifo "^1.3.2" + queue-tick "^1.0.1" + text-decoder "^1.1.0" + optionalDependencies: + bare-events "^2.2.0" + "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" @@ -7085,13 +7318,20 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.1.1: +string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + "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" @@ -7206,6 +7446,15 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" +tar-stream@^3.0.0: + version "3.1.7" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" + integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== + dependencies: + b4a "^1.6.4" + fast-fifo "^1.2.0" + streamx "^2.15.0" + tar@^6.1.12: version "6.2.1" resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz" @@ -7231,6 +7480,13 @@ temp-file@^3.4.0: async-exit-hook "^2.0.1" fs-extra "^10.0.0" +text-decoder@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.0.tgz#85f19d4d5088e0b45cd841bdfaeac458dbffeefc" + integrity sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg== + dependencies: + b4a "^1.6.4" + text-extensions@^2.0.0: version "2.4.0" resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz" @@ -7581,7 +7837,7 @@ utf8-byte-length@^1.0.1: resolved "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz" integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA== -util-deprecate@^1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -7914,6 +8170,15 @@ yup@^1.4.0: toposort "^2.0.2" type-fest "^2.19.0" +zip-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-6.0.1.tgz#e141b930ed60ccaf5d7fa9c8260e0d1748a2bbfb" + integrity sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA== + dependencies: + archiver-utils "^5.0.0" + compress-commons "^6.0.2" + readable-stream "^4.0.0" + zod@^3.23.8: version "3.23.8" resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz" From b87aade2a3c660a936ab091624301cbd911b781c Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 25 Sep 2024 19:48:32 +0100 Subject: [PATCH 02/22] ci: pointing build to staging --- .github/workflows/build.yml | 8 +- .github/workflows/release.yml | 16 + src/renderer/src/assets/lottie/cloud.json | 726 +++++++++++++++++++++- 3 files changed, 745 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5cc4aa4d..1a01d550 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,8 +40,8 @@ jobs: sudo apt-get install -y libarchive-tools yarn build:linux env: - MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }} - MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_AUTH_URL }} + MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_STAGING_API_URL }} + MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_STAGING_AUTH_URL }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -50,8 +50,8 @@ jobs: if: matrix.os == 'windows-latest' run: yarn build:win env: - MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }} - MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_AUTH_URL }} + MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_STAGING_API_URL }} + MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_STAGING_AUTH_URL }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 96b6a08d..9fc71ec1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,6 +58,22 @@ jobs: MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Create artifact + uses: actions/upload-artifact@v4 + with: + name: Build-${{ matrix.os }} + path: | + dist/win-unpacked/** + dist/*-portable.exe + dist/*.zip + dist/*.dmg + dist/*.deb + dist/*.rpm + dist/*.tar.gz + dist/*.yml + dist/*.blockmap + dist/*.pacman + - name: Release uses: softprops/action-gh-release@v1 with: diff --git a/src/renderer/src/assets/lottie/cloud.json b/src/renderer/src/assets/lottie/cloud.json index 9df1e119..c8e4bce7 100644 --- a/src/renderer/src/assets/lottie/cloud.json +++ b/src/renderer/src/assets/lottie/cloud.json @@ -1 +1,725 @@ -{"v":"5.12.1","fr":30,"ip":0,"op":60,"w":400,"h":400,"nm":"Cloud","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[322.789,202.565,0],"to":[-1.5,-0.167,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[313.789,201.565,0],"to":[0,0,0],"ti":[-1.5,-0.167,0]},{"t":60,"s":[322.789,202.565,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-38.564],[38.564,0],[0,38.564],[-38.564,0]],"o":[[0,38.564],[-38.564,0],[0,-38.564],[38.564,0]],"v":[[69.827,0],[0,69.827],[-69.827,0],[0,-69.827]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[243.704,202.565,0],"to":[-1.667,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[233.704,202.565,0],"to":[0,0,0],"ti":[-1.667,0,0]},{"t":60,"s":[243.704,202.565,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-38.564],[38.564,0],[0,38.564],[-38.564,0]],"o":[[0,38.564],[-38.564,0],[0,-38.564],[38.564,0]],"v":[[69.827,0],[0,69.827],[-69.827,0],[0,-69.827]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[260.681,151.053,0],"to":[1.333,-1.333,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[268.681,143.053,0],"to":[0,0,0],"ti":[1.333,-1.333,0]},{"t":60,"s":[260.681,151.053,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-38.564],[38.564,0],[0,38.564],[-38.564,0]],"o":[[0,38.564],[-38.564,0],[0,-38.564],[38.564,0]],"v":[[69.827,0],[0,69.827],[-69.827,0],[0,-69.827]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[162.135,206.563,0],"to":[-0.833,-0.167,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[157.135,205.563,0],"to":[0,0,0],"ti":[-0.833,-0.167,0]},{"t":60,"s":[162.135,206.563,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-36.66],[36.66,0],[0,36.66],[-36.66,0]],"o":[[0,36.66],[-36.66,0],[0,-36.66],[36.66,0]],"v":[[66.378,0],[0,66.378],[-66.378,0],[0,-66.378]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[180.178,132.225,0],"to":[-0.5,-2.333,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[177.178,118.225,0],"to":[0,0,0],"ti":[-0.5,-2.333,0]},{"t":60,"s":[180.178,132.225,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-50.068],[50.068,0],[0,50.068],[-50.068,0]],"o":[[0,50.068],[-50.068,0],[0,-50.068],[50.068,0]],"v":[[90.655,0],[0,90.655],[-90.655,0],[0,-90.655]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[95.756,208.288,0],"to":[-1.167,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[88.756,208.288,0],"to":[0,0,0],"ti":[-1.167,0,0]},{"t":60,"s":[95.756,208.288,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-35.403],[35.403,0],[0,35.403],[-35.403,0]],"o":[[0,35.403],[-35.403,0],[0,-35.403],[35.403,0]],"v":[[64.103,0],[0,64.103],[-64.103,0],[0,-64.103]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215686275,0.854901960784,0.933333333333,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":270,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":3,"nm":"Null 1","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[19.822,67.775,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":270,"st":0,"bm":0}],"markers":[],"props":{}} \ No newline at end of file +{ + "v": "5.12.1", + "fr": 30, + "ip": 0, + "op": 60, + "w": 400, + "h": 400, + "nm": "Cloud", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Layer 6", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 0, + "s": [322.789, 202.565, 0], + "to": [-1.5, -0.167, 0], + "ti": [0, 0, 0] + }, + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 30, + "s": [313.789, 201.565, 0], + "to": [0, 0, 0], + "ti": [-1.5, -0.167, 0] + }, + { "t": 60, "s": [322.789, 202.565, 0] } + ], + "ix": 2, + "l": 2 + }, + "a": { "a": 0, "k": [0, 0, 0], "ix": 1, "l": 2 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, -38.564], + [38.564, 0], + [0, 38.564], + [-38.564, 0] + ], + "o": [ + [0, 38.564], + [-38.564, 0], + [0, -38.564], + [38.564, 0] + ], + "v": [ + [69.827, 0], + [0, 69.827], + [-69.827, 0], + [0, -69.827] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.839215686275, 0.854901960784, 0.933333333333, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 270, + "st": 0, + "ct": 1, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Layer 5", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 0, + "s": [243.704, 202.565, 0], + "to": [-1.667, 0, 0], + "ti": [0, 0, 0] + }, + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 30, + "s": [233.704, 202.565, 0], + "to": [0, 0, 0], + "ti": [-1.667, 0, 0] + }, + { "t": 60, "s": [243.704, 202.565, 0] } + ], + "ix": 2, + "l": 2 + }, + "a": { "a": 0, "k": [0, 0, 0], "ix": 1, "l": 2 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, -38.564], + [38.564, 0], + [0, 38.564], + [-38.564, 0] + ], + "o": [ + [0, 38.564], + [-38.564, 0], + [0, -38.564], + [38.564, 0] + ], + "v": [ + [69.827, 0], + [0, 69.827], + [-69.827, 0], + [0, -69.827] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.839215686275, 0.854901960784, 0.933333333333, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 270, + "st": 0, + "ct": 1, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "Layer 4", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 0, + "s": [260.681, 151.053, 0], + "to": [1.333, -1.333, 0], + "ti": [0, 0, 0] + }, + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 30, + "s": [268.681, 143.053, 0], + "to": [0, 0, 0], + "ti": [1.333, -1.333, 0] + }, + { "t": 60, "s": [260.681, 151.053, 0] } + ], + "ix": 2, + "l": 2 + }, + "a": { "a": 0, "k": [0, 0, 0], "ix": 1, "l": 2 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, -38.564], + [38.564, 0], + [0, 38.564], + [-38.564, 0] + ], + "o": [ + [0, 38.564], + [-38.564, 0], + [0, -38.564], + [38.564, 0] + ], + "v": [ + [69.827, 0], + [0, 69.827], + [-69.827, 0], + [0, -69.827] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.839215686275, 0.854901960784, 0.933333333333, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 270, + "st": 0, + "ct": 1, + "bm": 0 + }, + { + "ddd": 0, + "ind": 5, + "ty": 4, + "nm": "Layer 3", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 0, + "s": [162.135, 206.563, 0], + "to": [-0.833, -0.167, 0], + "ti": [0, 0, 0] + }, + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 30, + "s": [157.135, 205.563, 0], + "to": [0, 0, 0], + "ti": [-0.833, -0.167, 0] + }, + { "t": 60, "s": [162.135, 206.563, 0] } + ], + "ix": 2, + "l": 2 + }, + "a": { "a": 0, "k": [0, 0, 0], "ix": 1, "l": 2 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, -36.66], + [36.66, 0], + [0, 36.66], + [-36.66, 0] + ], + "o": [ + [0, 36.66], + [-36.66, 0], + [0, -36.66], + [36.66, 0] + ], + "v": [ + [66.378, 0], + [0, 66.378], + [-66.378, 0], + [0, -66.378] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.839215686275, 0.854901960784, 0.933333333333, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 270, + "st": 0, + "ct": 1, + "bm": 0 + }, + { + "ddd": 0, + "ind": 6, + "ty": 4, + "nm": "Layer 2", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 0, + "s": [180.178, 132.225, 0], + "to": [-0.5, -2.333, 0], + "ti": [0, 0, 0] + }, + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 30, + "s": [177.178, 118.225, 0], + "to": [0, 0, 0], + "ti": [-0.5, -2.333, 0] + }, + { "t": 60, "s": [180.178, 132.225, 0] } + ], + "ix": 2, + "l": 2 + }, + "a": { "a": 0, "k": [0, 0, 0], "ix": 1, "l": 2 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, -50.068], + [50.068, 0], + [0, 50.068], + [-50.068, 0] + ], + "o": [ + [0, 50.068], + [-50.068, 0], + [0, -50.068], + [50.068, 0] + ], + "v": [ + [90.655, 0], + [0, 90.655], + [-90.655, 0], + [0, -90.655] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.839215686275, 0.854901960784, 0.933333333333, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 270, + "st": 0, + "ct": 1, + "bm": 0 + }, + { + "ddd": 0, + "ind": 7, + "ty": 4, + "nm": "Layer 1", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { + "a": 1, + "k": [ + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 0, + "s": [95.756, 208.288, 0], + "to": [-1.167, 0, 0], + "ti": [0, 0, 0] + }, + { + "i": { "x": 0.667, "y": 1 }, + "o": { "x": 0.333, "y": 0 }, + "t": 30, + "s": [88.756, 208.288, 0], + "to": [0, 0, 0], + "ti": [-1.167, 0, 0] + }, + { "t": 60, "s": [95.756, 208.288, 0] } + ], + "ix": 2, + "l": 2 + }, + "a": { "a": 0, "k": [0, 0, 0], "ix": 1, "l": 2 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [0, -35.403], + [35.403, 0], + [0, 35.403], + [-35.403, 0] + ], + "o": [ + [0, 35.403], + [-35.403, 0], + [0, -35.403], + [35.403, 0] + ], + "v": [ + [64.103, 0], + [0, 64.103], + [-64.103, 0], + [0, -64.103] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.839215686275, 0.854901960784, 0.933333333333, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 270, + "st": 0, + "ct": 1, + "bm": 0 + }, + { + "ddd": 0, + "ind": 8, + "ty": 3, + "nm": "Null 1", + "parent": 6, + "sr": 1, + "ks": { + "o": { "a": 0, "k": 0, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [19.822, 67.775, 0], "ix": 2, "l": 2 }, + "a": { "a": 0, "k": [0, 0, 0], "ix": 1, "l": 2 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 } + }, + "ao": 0, + "ip": 0, + "op": 270, + "st": 0, + "bm": 0 + } + ], + "markers": [], + "props": {} +} From 0ea7329aa3e928812ba112f50ede31d2876a2c5d Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 25 Sep 2024 19:53:37 +0100 Subject: [PATCH 03/22] fix: fixing chmod for windows on postinstall --- postinstall.cjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/postinstall.cjs b/postinstall.cjs index ce9c5909..25d27c0a 100644 --- a/postinstall.cjs +++ b/postinstall.cjs @@ -30,10 +30,15 @@ const downloadLudusavi = async () => { console.log(`Downloaded ${file}, extracting...`); const pwd = process.cwd(); + const targetPath = path.join(pwd, "ludusavi"); await exec(`npx extract-zip ${file} ${targetPath}`); - fs.chmodSync(path.join(targetPath, "ludusavi"), 0o755); + + if (process.platform !== "win32") { + fs.chmodSync(path.join(targetPath, "ludusavi"), 0o755); + } + console.log("Extracted. Renaming folder..."); console.log(`Extracted ${file}, removing compressed downloaded file...`); From 89b830fe9abb91d265af58e1a439bf234b7357ed Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 25 Sep 2024 20:44:56 +0100 Subject: [PATCH 04/22] feat: clearing backup history on sign out --- src/renderer/src/hooks/use-user-details.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 50e2fad9..ed2c3b14 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -14,6 +14,7 @@ import type { UserDetails, } from "@types"; import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal"; +import { gameBackupsTable } from "@renderer/dexie"; export function useUserDetails() { const dispatch = useAppDispatch(); @@ -32,6 +33,7 @@ export function useUserDetails() { dispatch(setUserDetails(null)); dispatch(setProfileBackground(null)); + await gameBackupsTable.clear(); window.localStorage.removeItem("userDetails"); }, [dispatch]); From 3e165e05fbe665a94bf25892611a399be0fdf33f Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 25 Sep 2024 21:07:41 +0100 Subject: [PATCH 05/22] fix: adding no backup preview condition --- src/main/events/profile/update-profile.ts | 3 --- .../context/cloud-sync/cloud-sync.context.tsx | 16 +++++++---- src/renderer/src/hooks/use-user-details.ts | 27 ++----------------- .../cloud-sync-modal/cloud-sync-modal.tsx | 10 ++++--- 4 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/main/events/profile/update-profile.ts b/src/main/events/profile/update-profile.ts index 4135aae5..eb80bc47 100644 --- a/src/main/events/profile/update-profile.ts +++ b/src/main/events/profile/update-profile.ts @@ -33,9 +33,6 @@ const getNewProfileImageUrl = async (localImageUrl: string) => { headers: { "Content-Type": mimeType, }, - onUploadProgress: (progressEvent) => { - console.log(progressEvent); - }, }); return profileImageUrl; diff --git a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx index 38bbdb40..2f0addaf 100644 --- a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx +++ b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx @@ -25,7 +25,7 @@ export interface CloudSyncContext { setShowCloudSyncModal: React.Dispatch>; downloadGameArtifact: (gameArtifactId: string) => Promise; uploadSaveGame: () => Promise; - deleteGameArtifact: (gameArtifactId: string) => Promise<{ ok: boolean }>; + deleteGameArtifact: (gameArtifactId: string) => Promise; restoringBackup: boolean; uploadingBackup: boolean; } @@ -39,7 +39,7 @@ export const cloudSyncContext = createContext({ downloadGameArtifact: async () => {}, uploadSaveGame: async () => {}, artifacts: [], - deleteGameArtifact: async () => ({ ok: false }), + deleteGameArtifact: async () => {}, restoringBackup: false, uploadingBackup: false, }); @@ -135,20 +135,26 @@ export function CloudSyncContextProvider({ async (gameArtifactId: string) => { return window.electron.deleteGameArtifact(gameArtifactId).then(() => { getGameBackupPreview(); - return { ok: true }; }); }, [getGameBackupPreview] ); useEffect(() => { - getGameBackupPreview(); - window.electron.checkGameCloudSyncSupport(objectId, shop).then((result) => { setSupportsCloudSync(result); }); }, [objectId, shop, getGameBackupPreview]); + useEffect(() => { + setBackupPreview(null); + setArtifacts([]); + setSupportsCloudSync(null); + setShowCloudSyncModal(false); + setRestoringBackup(false); + setUploadingBackup(false); + }, [objectId, shop]); + useEffect(() => { if (showCloudSyncModal) { getGameBackupPreview(); diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index ed2c3b14..7e08144d 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -46,32 +46,9 @@ export function useUserDetails() { const updateUserDetails = useCallback( async (userDetails: UserDetails) => { dispatch(setUserDetails(userDetails)); - - if (userDetails.profileImageUrl) { - // TODO: Decide if we want to use this - // const profileBackground = await profileBackgroundFromProfileImage( - // userDetails.profileImageUrl - // ).catch((err) => { - // logger.error("profileBackgroundFromProfileImage", err); - // return `#151515B3`; - // }); - // dispatch(setProfileBackground(profileBackground)); - - window.localStorage.setItem( - "userDetails", - JSON.stringify({ ...userDetails, profileBackground }) - ); - } else { - const profileBackground = `#151515B3`; - dispatch(setProfileBackground(profileBackground)); - - window.localStorage.setItem( - "userDetails", - JSON.stringify({ ...userDetails, profileBackground }) - ); - } + window.localStorage.setItem("userDetails", JSON.stringify(userDetails)); }, - [dispatch, profileBackground] + [dispatch] ); const fetchUserDetails = useCallback(async () => { diff --git a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx index fd38eb76..7461c4b2 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx @@ -87,8 +87,12 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { ); } - return "no_backups"; - }, [uploadingBackup, lastBackup, restoringBackup]); + if (!backupPreview) { + return "no_backup_preview"; + } + + return "no_artifacts"; + }, [uploadingBackup, lastBackup, backupPreview, restoringBackup]); const disableActions = uploadingBackup || restoringBackup || deletingArtifact; @@ -116,7 +120,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { -

backups

+

{t("backups")}

    {artifacts.map((artifact) => ( @@ -162,7 +167,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { disabled={disableActions} > - install_artifact + {t("install_backup")} diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 80974a14..3c49c3af 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -132,7 +132,7 @@ export function GameDetailsContent() { style={{ width: 26, position: "absolute", top: -3 }} /> - cloud_sync + {t("cloud_save")} )} From 5b9d860937890d2d5fe622bcfb21095ec5ec6ab9 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 25 Sep 2024 22:22:52 +0100 Subject: [PATCH 07/22] feat: adding i18n for cloud sync --- src/locales/pt-BR/translation.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 88c05250..24b971a2 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -127,7 +127,6 @@ "executable_path_in_use": "Executável em uso por \"{{game}}\"", "warning": "Aviso:", "hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.", - "cloud_save": "Salvamento em nuvem", "cloud_save_description": "Matenha seu progresso na nuvem e continue de onde parou em qualquer dispositivo", "backups": "Backups", From eebd09ccf22b2e6cf3ecb379051e05d02ae8dca2 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 28 Sep 2024 00:22:38 +0100 Subject: [PATCH 08/22] feat: adding logger --- .../events/cloud-save/upload-save-game.ts | 17 ++++------- src/renderer/src/app.tsx | 7 +++++ .../context/cloud-sync/cloud-sync.context.tsx | 29 ++++++++++++++----- src/renderer/src/logger.ts | 3 ++ .../cloud-sync-modal/cloud-sync-modal.tsx | 2 +- 5 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 src/renderer/src/logger.ts diff --git a/src/main/events/cloud-save/upload-save-game.ts b/src/main/events/cloud-save/upload-save-game.ts index 3a89b275..efa7cd22 100644 --- a/src/main/events/cloud-save/upload-save-game.ts +++ b/src/main/events/cloud-save/upload-save-game.ts @@ -87,17 +87,12 @@ const uploadSaveGame = async ( true ); - // fs.rm(zipLocation, (err) => { - // if (err) { - // logger.error("Failed to remove zip file", err); - // throw err; - // } - - // WindowManager.mainWindow?.webContents.send( - // `on-upload-complete-${objectId}-${shop}`, - // true - // ); - // }); + fs.rm(zipLocation, (err) => { + if (err) { + logger.error("Failed to remove zip file", err); + throw err; + } + }); }); }); }); diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 37e63154..7c572a56 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -28,6 +28,7 @@ import { useTranslation } from "react-i18next"; import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; import { downloadSourcesWorker } from "./workers"; import { repacksContext } from "./context"; +import { logger } from "./logger"; export interface AppProps { children: React.ReactNode; @@ -231,6 +232,8 @@ export function App() { } for (const downloadSource of downloadSources) { + logger.info("Migrating download source", downloadSource.url); + const channel = new BroadcastChannel( `download_sources:import:${downloadSource.url}` ); @@ -243,6 +246,10 @@ export function App() { channel.onmessage = () => { window.electron.deleteDownloadSource(downloadSource.id).then(() => { resolve(true); + logger.info( + "Deleted download source from SQLite", + downloadSource.url + ); }); indexRepacks(); diff --git a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx index 417c241a..086a8c94 100644 --- a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx +++ b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx @@ -1,5 +1,6 @@ import { gameBackupsTable } from "@renderer/dexie"; import { useToast } from "@renderer/hooks"; +import { logger } from "@renderer/logger"; import type { LudusaviBackup, GameArtifact, GameShop } from "@types"; import React, { createContext, @@ -92,11 +93,17 @@ export function CloudSyncContextProvider({ setArtifacts(results); }); - window.electron.getGameBackupPreview(objectId, shop).then((preview) => { - if (preview && Object.keys(preview.games).length) { - setBackupPreview(preview); - } - }); + window.electron + .getGameBackupPreview(objectId, shop) + .then((preview) => { + logger.info("Game backup preview", objectId, shop, preview); + if (preview && Object.keys(preview.games).length) { + setBackupPreview(preview); + } + }) + .catch((err) => { + logger.error("Failed to get game backup preview", objectId, shop, err); + }); }, [objectId, shop]); const uploadSaveGame = useCallback(async () => { @@ -146,9 +153,15 @@ export function CloudSyncContextProvider({ ); useEffect(() => { - window.electron.checkGameCloudSyncSupport(objectId, shop).then((result) => { - setSupportsCloudSync(result); - }); + window.electron + .checkGameCloudSyncSupport(objectId, shop) + .then((result) => { + logger.info("Cloud sync support", objectId, shop, result); + setSupportsCloudSync(result); + }) + .catch((err) => { + logger.error("Failed to check cloud sync support", err); + }); }, [objectId, shop, getGameBackupPreview]); useEffect(() => { diff --git a/src/renderer/src/logger.ts b/src/renderer/src/logger.ts new file mode 100644 index 00000000..052b4452 --- /dev/null +++ b/src/renderer/src/logger.ts @@ -0,0 +1,3 @@ +import log from "electron-log/renderer"; + +export const logger = log.scope("renderer"); diff --git a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx index 1bd849c3..ef57d1be 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx @@ -195,7 +195,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { }} >

    {t("backups")}

    - 2 / 2 + {artifacts.length} / 2
      From 790f7a2549acde4e4a350dc3db81ea5238d441f4 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 28 Sep 2024 00:23:47 +0100 Subject: [PATCH 09/22] feat: adding logger --- .../cloud-sync-files-modal/cloud-sync-files-modal.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx index aebabda3..a5081ee9 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx @@ -1,9 +1,8 @@ -import { Button, Modal, ModalProps, TextField } from "@renderer/components"; +import { Modal, ModalProps, TextField } from "@renderer/components"; import { useContext, useMemo } from "react"; import { cloudSyncContext } from "@renderer/context"; import { useTranslation } from "react-i18next"; -import { CheckCircleFillIcon } from "@primer/octicons-react"; export interface CloudSyncFilesModalProps extends Omit {} @@ -12,8 +11,6 @@ export function CloudSyncFilesModal({ visible, onClose, }: CloudSyncFilesModalProps) { - const { t } = useTranslation("game_details"); - const { backupPreview } = useContext(cloudSyncContext); const files = useMemo(() => { From 202751ddca3fd92ca311d0527ed267233d2e77ca Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 28 Sep 2024 00:24:18 +0100 Subject: [PATCH 10/22] feat: adding logger --- .../cloud-sync-files-modal/cloud-sync-files-modal.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx index a5081ee9..db36a285 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx @@ -2,8 +2,6 @@ import { Modal, ModalProps, TextField } from "@renderer/components"; import { useContext, useMemo } from "react"; import { cloudSyncContext } from "@renderer/context"; -import { useTranslation } from "react-i18next"; - export interface CloudSyncFilesModalProps extends Omit {} From 586df616e8d4a19148b74f71f673c2baaae1d2bb Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 28 Sep 2024 00:59:17 +0100 Subject: [PATCH 11/22] feat: removing session interception from auth --- src/main/index.ts | 42 +------------------------- src/main/services/window-manager.ts | 47 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index c9e36b2c..9127b881 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, net, protocol, session } from "electron"; +import { app, BrowserWindow, net, protocol } from "electron"; import { init } from "@sentry/electron/main"; import updater from "electron-updater"; import i18n from "i18next"; @@ -103,46 +103,6 @@ app.whenReady().then(async () => { WindowManager.createMainWindow(); WindowManager.createSystemTray(userPreferences?.language || "en"); - - session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { - callback({ - requestHeaders: { - ...details.requestHeaders, - "user-agent": - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", - }, - }); - }); - - session.defaultSession.webRequest.onHeadersReceived((details, callback) => { - const headers = { - "access-control-allow-origin": ["*"], - "access-control-allow-methods": ["GET, POST, PUT, DELETE, OPTIONS"], - "access-control-expose-headers": ["ETag"], - "access-control-allow-headers": [ - "Content-Type, Authorization, X-Requested-With, If-None-Match", - ], - "access-control-allow-credentials": ["true"], - }; - - if (details.method === "OPTIONS") { - callback({ - cancel: false, - responseHeaders: { - ...details.responseHeaders, - ...headers, - }, - statusLine: "HTTP/1.1 200 OK", - }); - } else { - callback({ - responseHeaders: { - ...details.responseHeaders, - ...headers, - }, - }); - } - }); }); app.on("browser-window-created", (_, window) => { diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index cf904fb4..4af15c82 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -61,6 +61,53 @@ export class WindowManager { show: false, }); + this.mainWindow.webContents.session.webRequest.onBeforeSendHeaders( + (details, callback) => { + callback({ + requestHeaders: { + ...details.requestHeaders, + "user-agent": + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + }, + }); + } + ); + + this.mainWindow.webContents.session.webRequest.onHeadersReceived( + (details, callback) => { + if (details.webContentsId !== this.mainWindow?.webContents.id) { + return callback(details); + } + + const headers = { + "access-control-allow-origin": ["*"], + "access-control-allow-methods": ["GET, POST, PUT, DELETE, OPTIONS"], + "access-control-expose-headers": ["ETag"], + "access-control-allow-headers": [ + "Content-Type, Authorization, X-Requested-With, If-None-Match", + ], + }; + + if (details.method === "OPTIONS") { + return callback({ + cancel: false, + responseHeaders: { + ...details.responseHeaders, + ...headers, + }, + statusLine: "HTTP/1.1 200 OK", + }); + } + + return callback({ + responseHeaders: { + ...details.responseHeaders, + ...headers, + }, + }); + } + ); + this.loadURL(); this.mainWindow.removeMenu(); From 81e2bda04914e1cbd432ca7d8c6bdcd699c21388 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:58:32 -0300 Subject: [PATCH 12/22] chore: add preview on version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fd4824cb..e7d6d052 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "2.1.7", + "version": "2.1.7-preview", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", From 035e424a76d380a7736fe1574bae2ad5b0a5d2f1 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 5 Oct 2024 02:21:41 +0100 Subject: [PATCH 13/22] feat: adding change hero --- package.json | 5 +- src/main/events/catalogue/get-catalogue.ts | 2 +- .../events/catalogue/get-game-shop-details.ts | 18 +- src/main/events/catalogue/get-games.ts | 31 +- .../events/catalogue/get-how-long-to-beat.ts | 8 +- .../events/cloud-save/delete-game-artifact.ts | 5 +- .../cloud-save/download-game-artifact.ts | 77 ++++- .../events/cloud-save/get-game-artifacts.ts | 4 +- .../events/cloud-save/upload-save-game.ts | 110 +++---- src/main/events/helpers/search-games.ts | 2 +- .../events/library/add-game-to-library.ts | 14 +- .../events/library/get-game-by-object-id.ts | 8 +- .../events/torrenting/start-game-download.ts | 12 +- src/main/services/process-watcher.ts | 2 +- src/main/services/steam-250.ts | 4 +- src/main/services/steam-grid.ts | 8 +- src/main/services/steam.ts | 6 +- src/main/workers/ludusavi.worker.ts | 14 +- src/preload/index.ts | 20 +- src/renderer/src/app.css.ts | 4 + .../src/components/game-card/game-card.tsx | 2 +- .../src/components/sidebar/sidebar.tsx | 5 +- .../game-details/game-details.context.tsx | 6 +- .../game-details.context.types.ts | 2 +- .../src/context/repacks/repacks.context.tsx | 1 + src/renderer/src/declaration.d.ts | 13 +- src/renderer/src/helpers.ts | 4 +- src/renderer/src/main.tsx | 2 +- .../src/pages/catalogue/catalogue.tsx | 17 +- .../src/pages/downloads/download-group.css.ts | 1 + .../src/pages/downloads/download-group.tsx | 9 +- .../cloud-sync-files-modal.tsx | 38 ++- .../cloud-sync-modal/cloud-sync-modal.tsx | 8 +- .../game-details/game-details-content.tsx | 10 +- .../src/pages/game-details/game-details.tsx | 10 +- .../game-details/hero/hero-panel-actions.tsx | 4 +- .../pages/game-details/sidebar/sidebar.tsx | 6 +- src/renderer/src/pages/home/home.tsx | 2 +- .../src/pages/home/search-results.tsx | 2 +- .../profile-content/profile-content.css.ts | 14 +- .../profile-content/profile-content.tsx | 79 ++++- .../profile-content/recent-games-box.tsx | 2 +- .../profile/profile-hero/profile-hero.css.ts | 2 +- .../profile/profile-hero/profile-hero.tsx | 84 ++++- src/renderer/src/workers/repacks.worker.ts | 8 +- src/shared/index.ts | 20 +- src/types/index.ts | 12 +- yarn.lock | 303 ++++-------------- 48 files changed, 520 insertions(+), 500 deletions(-) diff --git a/package.json b/package.json index 08f096f7..5b3088a1 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,6 @@ "@vanilla-extract/css": "^1.14.2", "@vanilla-extract/dynamic": "^2.1.1", "@vanilla-extract/recipes": "^0.5.2", - "adm-zip": "^0.5.16", - "archiver": "^7.0.1", "auto-launch": "^5.0.6", "axios": "^1.7.7", "better-sqlite3": "^11.2.1", @@ -74,6 +72,7 @@ "react-redux": "^9.1.1", "react-router-dom": "^6.22.3", "sudo-prompt": "^9.2.1", + "tar": "^7.4.3", "typeorm": "^0.3.20", "user-agents": "^1.1.193", "yaml": "^2.4.1", @@ -88,8 +87,6 @@ "@electron-toolkit/tsconfig": "^1.0.1", "@sentry/vite-plugin": "^2.20.1", "@swc/core": "^1.4.16", - "@types/adm-zip": "^0.5.5", - "@types/archiver": "^6.0.2", "@types/auto-launch": "^5.0.5", "@types/color": "^3.0.6", "@types/folder-hash": "^4.0.4", diff --git a/src/main/events/catalogue/get-catalogue.ts b/src/main/events/catalogue/get-catalogue.ts index 4fdb95bd..145f3166 100644 --- a/src/main/events/catalogue/get-catalogue.ts +++ b/src/main/events/catalogue/get-catalogue.ts @@ -30,7 +30,7 @@ const getCatalogue = async ( title: steamGame.name, shop: game.shop, cover: steamUrlBuilder.library(game.objectId), - objectID: game.objectId, + objectId: game.objectId, }; }) ); diff --git a/src/main/events/catalogue/get-game-shop-details.ts b/src/main/events/catalogue/get-game-shop-details.ts index 3a435013..08366abc 100644 --- a/src/main/events/catalogue/get-game-shop-details.ts +++ b/src/main/events/catalogue/get-game-shop-details.ts @@ -7,16 +7,16 @@ import { registerEvent } from "../register-event"; import { steamGamesWorker } from "@main/workers"; const getLocalizedSteamAppDetails = async ( - objectID: string, + objectId: string, language: string ): Promise => { if (language === "english") { - return getSteamAppDetails(objectID, language); + return getSteamAppDetails(objectId, language); } - return getSteamAppDetails(objectID, language).then( + return getSteamAppDetails(objectId, language).then( async (localizedAppDetails) => { - const steamGame = await steamGamesWorker.run(Number(objectID), { + const steamGame = await steamGamesWorker.run(Number(objectId), { name: "getById", }); @@ -34,21 +34,21 @@ const getLocalizedSteamAppDetails = async ( const getGameShopDetails = async ( _event: Electron.IpcMainInvokeEvent, - objectID: string, + objectId: string, shop: GameShop, language: string ): Promise => { if (shop === "steam") { const cachedData = await gameShopCacheRepository.findOne({ - where: { objectID, language }, + where: { objectID: objectId, language }, }); - const appDetails = getLocalizedSteamAppDetails(objectID, language).then( + const appDetails = getLocalizedSteamAppDetails(objectId, language).then( (result) => { if (result) { gameShopCacheRepository.upsert( { - objectID, + objectID: objectId, shop: "steam", language, serializedData: JSON.stringify(result), @@ -68,7 +68,7 @@ const getGameShopDetails = async ( if (cachedGame) { return { ...cachedGame, - objectID, + objectId, } as ShopDetails; } diff --git a/src/main/events/catalogue/get-games.ts b/src/main/events/catalogue/get-games.ts index 81717806..3eb1f135 100644 --- a/src/main/events/catalogue/get-games.ts +++ b/src/main/events/catalogue/get-games.ts @@ -1,28 +1,29 @@ import type { CatalogueEntry } from "@types"; import { registerEvent } from "../register-event"; -import { steamGamesWorker } from "@main/workers"; +import { HydraApi } from "@main/services"; import { steamUrlBuilder } from "@shared"; const getGames = async ( _event: Electron.IpcMainInvokeEvent, take = 12, - cursor = 0 -): Promise<{ results: CatalogueEntry[]; cursor: number }> => { - const steamGames = await steamGamesWorker.run( - { limit: take, offset: cursor }, - { name: "list" } + skip = 0 +): Promise => { + const searchParams = new URLSearchParams({ + take: take.toString(), + skip: skip.toString(), + }); + + const games = await HydraApi.get( + `/games/catalogue?${searchParams.toString()}`, + undefined, + { needsAuth: false } ); - return { - results: steamGames.map((steamGame) => ({ - title: steamGame.name, - shop: "steam", - cover: steamUrlBuilder.library(steamGame.id), - objectID: steamGame.id, - })), - cursor: cursor + steamGames.length, - }; + return games.map((game) => ({ + ...game, + cover: steamUrlBuilder.library(game.objectId), + })); }; registerEvent("getGames", getGames); diff --git a/src/main/events/catalogue/get-how-long-to-beat.ts b/src/main/events/catalogue/get-how-long-to-beat.ts index 642dd9a3..f489f804 100644 --- a/src/main/events/catalogue/get-how-long-to-beat.ts +++ b/src/main/events/catalogue/get-how-long-to-beat.ts @@ -6,14 +6,14 @@ import { gameShopCacheRepository } from "@main/repository"; const getHowLongToBeat = async ( _event: Electron.IpcMainInvokeEvent, - objectID: string, + objectId: string, shop: GameShop, title: string ): Promise => { const searchHowLongToBeatPromise = searchHowLongToBeat(title); const gameShopCache = await gameShopCacheRepository.findOne({ - where: { objectID, shop }, + where: { objectID: objectId, shop }, }); const howLongToBeatCachedData = gameShopCache?.howLongToBeatSerializedData @@ -23,7 +23,7 @@ const getHowLongToBeat = async ( return searchHowLongToBeatPromise.then(async (response) => { const game = response.data.find( - (game) => game.profile_steam === Number(objectID) + (game) => game.profile_steam === Number(objectId) ); if (!game) return null; @@ -31,7 +31,7 @@ const getHowLongToBeat = async ( gameShopCacheRepository.upsert( { - objectID, + objectID: objectId, shop, howLongToBeatSerializedData: JSON.stringify(howLongToBeat), }, diff --git a/src/main/events/cloud-save/delete-game-artifact.ts b/src/main/events/cloud-save/delete-game-artifact.ts index fa869896..e293bc56 100644 --- a/src/main/events/cloud-save/delete-game-artifact.ts +++ b/src/main/events/cloud-save/delete-game-artifact.ts @@ -4,6 +4,9 @@ import { registerEvent } from "../register-event"; const deleteGameArtifact = async ( _event: Electron.IpcMainInvokeEvent, gameArtifactId: string -) => HydraApi.delete<{ ok: boolean }>(`/games/artifacts/${gameArtifactId}`); +) => + HydraApi.delete<{ ok: boolean }>( + `/profile/games/artifacts/${gameArtifactId}` + ); registerEvent("deleteGameArtifact", deleteGameArtifact); diff --git a/src/main/events/cloud-save/download-game-artifact.ts b/src/main/events/cloud-save/download-game-artifact.ts index 94bc8edf..d587daaa 100644 --- a/src/main/events/cloud-save/download-game-artifact.ts +++ b/src/main/events/cloud-save/download-game-artifact.ts @@ -1,6 +1,6 @@ import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services"; import fs from "node:fs"; -import AdmZip from "adm-zip"; +import * as tar from "tar"; import { registerEvent } from "../register-event"; import axios from "axios"; import { app } from "electron"; @@ -8,16 +8,54 @@ import path from "node:path"; import { backupsPath } from "@main/constants"; import type { GameShop } from "@types"; +import YAML from "yaml"; + +export interface LudusaviBackup { + files: { + [key: string]: { + hash: string; + size: number; + }; + }; +} + +const replaceLudusaviBackupWithCurrentUser = ( + mappingPath: string, + backupHomeDir: string +) => { + const data = fs.readFileSync(mappingPath, "utf8"); + const manifest = YAML.parse(data); + + const currentHomeDir = app.getPath("home"); + + const backups = manifest.backups.map((backup: LudusaviBackup) => { + const files = Object.entries(backup.files).reduce((prev, [key, value]) => { + return { + ...prev, + [key.replace(backupHomeDir, currentHomeDir)]: value, + }; + }, {}); + + return { + ...backup, + files, + }; + }); + + fs.writeFileSync(mappingPath, YAML.stringify({ ...manifest, backups })); +}; + const downloadGameArtifact = async ( _event: Electron.IpcMainInvokeEvent, objectId: string, shop: GameShop, gameArtifactId: string ) => { - const { downloadUrl, objectKey } = await HydraApi.post<{ + const { downloadUrl, objectKey, homeDir } = await HydraApi.post<{ downloadUrl: string; objectKey: string; - }>(`/games/artifacts/${gameArtifactId}/download`); + homeDir: string; + }>(`/profile/games/artifacts/${gameArtifactId}/download`); const zipLocation = path.join(app.getPath("userData"), objectKey); const backupPath = path.join(backupsPath, `${shop}-${objectId}`); @@ -42,20 +80,31 @@ const downloadGameArtifact = async ( }); writer.on("close", () => { - const zip = new AdmZip(zipLocation); - zip.extractAllToAsync(backupPath, true, true, (err) => { - if (err) { - logger.error("Failed to extract zip", err); - throw err; - } + tar + .x({ + file: zipLocation, + cwd: backupPath, + }) + .then(async () => { + const [game] = await Ludusavi.findGames(shop, objectId); + if (!game) throw new Error("Game not found in Ludusavi manifest"); - Ludusavi.restoreBackup(backupPath).then(() => { - WindowManager.mainWindow?.webContents.send( - `on-backup-download-complete-${objectId}-${shop}`, - true + const mappingPath = path.join( + backupsPath, + `${shop}-${objectId}`, + game, + "mapping.yaml" ); + + replaceLudusaviBackupWithCurrentUser(mappingPath, homeDir); + + Ludusavi.restoreBackup(backupPath).then(() => { + WindowManager.mainWindow?.webContents.send( + `on-backup-download-complete-${objectId}-${shop}`, + true + ); + }); }); - }); }); }; diff --git a/src/main/events/cloud-save/get-game-artifacts.ts b/src/main/events/cloud-save/get-game-artifacts.ts index b32dfd79..fc47076a 100644 --- a/src/main/events/cloud-save/get-game-artifacts.ts +++ b/src/main/events/cloud-save/get-game-artifacts.ts @@ -12,7 +12,9 @@ const getGameArtifacts = async ( shop, }); - return HydraApi.get(`/games/artifacts?${params.toString()}`); + return HydraApi.get( + `/profile/games/artifacts?${params.toString()}` + ); }; registerEvent("getGameArtifacts", getGameArtifacts); diff --git a/src/main/events/cloud-save/upload-save-game.ts b/src/main/events/cloud-save/upload-save-game.ts index efa7cd22..9948da31 100644 --- a/src/main/events/cloud-save/upload-save-game.ts +++ b/src/main/events/cloud-save/upload-save-game.ts @@ -2,47 +2,31 @@ import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services"; import { registerEvent } from "../register-event"; import fs from "node:fs"; import path from "node:path"; -import archiver from "archiver"; +import * as tar from "tar"; import crypto from "node:crypto"; import { GameShop } from "@types"; import axios from "axios"; import os from "node:os"; -import { app } from "electron"; import { backupsPath } from "@main/constants"; +import { app } from "electron"; -const compressBackupToArtifact = async ( - shop: GameShop, - objectId: string, - cb: (zipLocation: string) => void -) => { +const bundleBackup = async (shop: GameShop, objectId: string) => { const backupPath = path.join(backupsPath, `${shop}-${objectId}`); await Ludusavi.backupGame(shop, objectId, backupPath); - const archive = archiver("zip", { - zlib: { level: 9 }, - }); + const tarLocation = path.join(backupsPath, `${crypto.randomUUID()}.zip`); - const zipLocation = path.join( - app.getPath("userData"), - `${crypto.randomUUID()}.zip` + await tar.create( + { + gzip: false, + file: tarLocation, + cwd: backupPath, + }, + ["."] ); - const output = fs.createWriteStream(zipLocation); - - output.on("close", () => { - cb(zipLocation); - }); - - output.on("error", (err) => { - logger.error("Failed to compress folder", err); - throw err; - }); - - archive.pipe(output); - - archive.directory(backupPath, false); - archive.finalize(); + return tarLocation; }; const uploadSaveGame = async ( @@ -50,49 +34,51 @@ const uploadSaveGame = async ( objectId: string, shop: GameShop ) => { - compressBackupToArtifact(shop, objectId, (zipLocation) => { - fs.stat(zipLocation, async (err, stat) => { + const bundleLocation = await bundleBackup(shop, objectId); + + fs.stat(bundleLocation, async (err, stat) => { + if (err) { + logger.error("Failed to get zip file stats", err); + throw err; + } + + const { uploadUrl } = await HydraApi.post<{ + id: string; + uploadUrl: string; + }>("/profile/games/artifacts", { + artifactLengthInBytes: stat.size, + shop, + objectId, + hostname: os.hostname(), + homeDir: app.getPath("home"), + platform: os.platform(), + }); + + fs.readFile(bundleLocation, async (err, fileBuffer) => { if (err) { - logger.error("Failed to get zip file stats", err); + logger.error("Failed to read zip file", err); throw err; } - const { uploadUrl } = await HydraApi.post<{ - id: string; - uploadUrl: string; - }>("/games/artifacts", { - artifactLengthInBytes: stat.size, - shop, - objectId, - hostname: os.hostname(), + await axios.put(uploadUrl, fileBuffer, { + headers: { + "Content-Type": "application/tar", + }, + onUploadProgress: (progressEvent) => { + console.log(progressEvent); + }, }); - fs.readFile(zipLocation, async (err, fileBuffer) => { + WindowManager.mainWindow?.webContents.send( + `on-upload-complete-${objectId}-${shop}`, + true + ); + + fs.rm(bundleLocation, (err) => { if (err) { - logger.error("Failed to read zip file", err); + logger.error("Failed to remove tar file", err); throw err; } - - await axios.put(uploadUrl, fileBuffer, { - headers: { - "Content-Type": "application/zip", - }, - onUploadProgress: (progressEvent) => { - console.log(progressEvent); - }, - }); - - WindowManager.mainWindow?.webContents.send( - `on-upload-complete-${objectId}-${shop}`, - true - ); - - fs.rm(zipLocation, (err) => { - if (err) { - logger.error("Failed to remove zip file", err); - throw err; - } - }); }); }); }); diff --git a/src/main/events/helpers/search-games.ts b/src/main/events/helpers/search-games.ts index 1f1fc756..74e0b6a8 100644 --- a/src/main/events/helpers/search-games.ts +++ b/src/main/events/helpers/search-games.ts @@ -12,7 +12,7 @@ export interface SearchGamesArgs { export const convertSteamGameToCatalogueEntry = ( game: SteamGame ): CatalogueEntry => ({ - objectID: String(game.id), + objectId: String(game.id), title: game.name, shop: "steam" as GameShop, cover: steamUrlBuilder.library(String(game.id)), diff --git a/src/main/events/library/add-game-to-library.ts b/src/main/events/library/add-game-to-library.ts index b5c9a5d0..789241f0 100644 --- a/src/main/events/library/add-game-to-library.ts +++ b/src/main/events/library/add-game-to-library.ts @@ -10,14 +10,14 @@ import { steamUrlBuilder } from "@shared"; const addGameToLibrary = async ( _event: Electron.IpcMainInvokeEvent, - objectID: string, + objectId: string, title: string, shop: GameShop ) => { return gameRepository .update( { - objectID, + objectID: objectId, }, { shop, @@ -27,23 +27,25 @@ const addGameToLibrary = async ( ) .then(async ({ affected }) => { if (!affected) { - const steamGame = await steamGamesWorker.run(Number(objectID), { + const steamGame = await steamGamesWorker.run(Number(objectId), { name: "getById", }); const iconUrl = steamGame?.clientIcon - ? steamUrlBuilder.icon(objectID, steamGame.clientIcon) + ? steamUrlBuilder.icon(objectId, steamGame.clientIcon) : null; await gameRepository.insert({ title, iconUrl, - objectID, + objectID: objectId, shop, }); } - const game = await gameRepository.findOne({ where: { objectID } }); + const game = await gameRepository.findOne({ + where: { objectID: objectId }, + }); createGame(game!).catch(() => {}); }); diff --git a/src/main/events/library/get-game-by-object-id.ts b/src/main/events/library/get-game-by-object-id.ts index 91cc1b5a..d68aac69 100644 --- a/src/main/events/library/get-game-by-object-id.ts +++ b/src/main/events/library/get-game-by-object-id.ts @@ -2,15 +2,15 @@ import { gameRepository } from "@main/repository"; import { registerEvent } from "../register-event"; -const getGameByObjectID = async ( +const getGameByObjectId = async ( _event: Electron.IpcMainInvokeEvent, - objectID: string + objectId: string ) => gameRepository.findOne({ where: { - objectID, + objectID: objectId, isDeleted: false, }, }); -registerEvent("getGameByObjectID", getGameByObjectID); +registerEvent("getGameByObjectId", getGameByObjectId); diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index a2c51a01..deac1d2c 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -14,7 +14,7 @@ const startGameDownload = async ( _event: Electron.IpcMainInvokeEvent, payload: StartGameDownloadPayload ) => { - const { objectID, title, shop, downloadPath, downloader, uri } = payload; + const { objectId, title, shop, downloadPath, downloader, uri } = payload; return dataSource.transaction(async (transactionalEntityManager) => { const gameRepository = transactionalEntityManager.getRepository(Game); @@ -23,7 +23,7 @@ const startGameDownload = async ( const game = await gameRepository.findOne({ where: { - objectID, + objectID: objectId, shop, }, }); @@ -51,18 +51,18 @@ const startGameDownload = async ( } ); } else { - const steamGame = await steamGamesWorker.run(Number(objectID), { + const steamGame = await steamGamesWorker.run(Number(objectId), { name: "getById", }); const iconUrl = steamGame?.clientIcon - ? steamUrlBuilder.icon(objectID, steamGame.clientIcon) + ? steamUrlBuilder.icon(objectId, steamGame.clientIcon) : null; await gameRepository.insert({ title, iconUrl, - objectID, + objectID: objectId, downloader, shop, status: "active", @@ -73,7 +73,7 @@ const startGameDownload = async ( const updatedGame = await gameRepository.findOne({ where: { - objectID, + objectID: objectId, }, }); diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index 2a194bf2..f4b550b3 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -2,7 +2,7 @@ import { IsNull, Not } from "typeorm"; import { gameRepository } from "@main/repository"; import { WindowManager } from "./window-manager"; import { createGame, updateGamePlaytime } from "./library-sync"; -import { GameRunning } from "@types"; +import type { GameRunning } from "@types"; import { PythonInstance } from "./download"; import { Game } from "@main/entity"; diff --git a/src/main/services/steam-250.ts b/src/main/services/steam-250.ts index 9833c278..0abc2f14 100644 --- a/src/main/services/steam-250.ts +++ b/src/main/services/steam-250.ts @@ -17,7 +17,7 @@ export const requestSteam250 = async (path: string) => { return { title: $title.textContent, - objectID: steamGameUrl.split("/").pop(), + objectId: steamGameUrl.split("/").pop(), } as Steam250Game; }) .filter((game) => game != null); @@ -38,7 +38,7 @@ export const getSteam250List = async () => { ).flat(); const gamesMap: Map = gamesList.reduce((map, item) => { - if (item) map.set(item.objectID, item); + if (item) map.set(item.objectId, item); return map; }, new Map()); diff --git a/src/main/services/steam-grid.ts b/src/main/services/steam-grid.ts index 2bdee28d..540e5857 100644 --- a/src/main/services/steam-grid.ts +++ b/src/main/services/steam-grid.ts @@ -21,7 +21,7 @@ export interface SteamGridGameResponse { } export const getSteamGridData = async ( - objectID: string, + objectId: string, path: string, shop: GameShop, params: Record = {} @@ -33,7 +33,7 @@ export const getSteamGridData = async ( } const response = await axios.get( - `https://www.steamgriddb.com/api/v2/${path}/${shop}/${objectID}?${searchParams.toString()}`, + `https://www.steamgriddb.com/api/v2/${path}/${shop}/${objectId}?${searchParams.toString()}`, { headers: { Authorization: `Bearer ${import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY}`, @@ -59,10 +59,10 @@ export const getSteamGridGameById = async ( return response.data; }; -export const getSteamGameClientIcon = async (objectID: string) => { +export const getSteamGameClientIcon = async (objectId: string) => { const { data: { id: steamGridGameId }, - } = await getSteamGridData(objectID, "games", "steam"); + } = await getSteamGridData(objectId, "games", "steam"); const steamGridGame = await getSteamGridGameById(steamGridGameId); return steamGridGame.data.platforms.steam.metadata.clienticon; diff --git a/src/main/services/steam.ts b/src/main/services/steam.ts index 53e243f3..1d8ed2d9 100644 --- a/src/main/services/steam.ts +++ b/src/main/services/steam.ts @@ -12,11 +12,11 @@ export interface SteamAppDetailsResponse { } export const getSteamAppDetails = async ( - objectID: string, + objectId: string, language: string ) => { const searchParams = new URLSearchParams({ - appids: objectID, + appids: objectId, l: language, }); @@ -25,7 +25,7 @@ export const getSteamAppDetails = async ( `http://store.steampowered.com/api/appdetails?${searchParams.toString()}` ) .then((response) => { - if (response.data[objectID].success) return response.data[objectID].data; + if (response.data[objectId].success) return response.data[objectId].data; return null; }) .catch((err) => { diff --git a/src/main/workers/ludusavi.worker.ts b/src/main/workers/ludusavi.worker.ts index 2a1d266c..e6ccdaad 100644 --- a/src/main/workers/ludusavi.worker.ts +++ b/src/main/workers/ludusavi.worker.ts @@ -28,20 +28,18 @@ export const backupGame = ({ title, backupPath, preview = false, + winePrefix, }: { title: string; backupPath: string; preview?: boolean; + winePrefix?: string; }) => { const args = ["backup", title, "--api", "--force"]; - if (preview) { - args.push("--preview"); - } - - if (backupPath) { - args.push("--path", backupPath); - } + if (preview) args.push("--preview"); + if (backupPath) args.push("--path", backupPath); + if (winePrefix) args.push("--wine-prefix", winePrefix); const result = cp.execFileSync(binaryPath, args); @@ -59,3 +57,5 @@ export const restoreBackup = (backupPath: string) => { return JSON.parse(result.toString("utf-8")) as LudusaviBackup; }; + +// --wine-prefix diff --git a/src/preload/index.ts b/src/preload/index.ts index f5dd0ba7..f2600fdc 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -38,13 +38,13 @@ contextBridge.exposeInMainWorld("electron", { searchGames: (query: string) => ipcRenderer.invoke("searchGames", query), getCatalogue: (category: CatalogueCategory) => ipcRenderer.invoke("getCatalogue", category), - getGameShopDetails: (objectID: string, shop: GameShop, language: string) => - ipcRenderer.invoke("getGameShopDetails", objectID, shop, language), + getGameShopDetails: (objectId: string, shop: GameShop, language: string) => + ipcRenderer.invoke("getGameShopDetails", objectId, shop, language), getRandomGame: () => ipcRenderer.invoke("getRandomGame"), - getHowLongToBeat: (objectID: string, shop: GameShop, title: string) => - ipcRenderer.invoke("getHowLongToBeat", objectID, shop, title), - getGames: (take?: number, prevCursor?: number) => - ipcRenderer.invoke("getGames", take, prevCursor), + getHowLongToBeat: (objectId: string, shop: GameShop, title: string) => + ipcRenderer.invoke("getHowLongToBeat", objectId, shop, title), + getGames: (take?: number, skip?: number) => + ipcRenderer.invoke("getGames", take, skip), searchGameRepacks: (query: string) => ipcRenderer.invoke("searchGameRepacks", query), getGameStats: (objectId: string, shop: GameShop) => @@ -65,8 +65,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("deleteDownloadSource", id), /* Library */ - addGameToLibrary: (objectID: string, title: string, shop: GameShop) => - ipcRenderer.invoke("addGameToLibrary", objectID, title, shop), + addGameToLibrary: (objectId: string, title: string, shop: GameShop) => + ipcRenderer.invoke("addGameToLibrary", objectId, title, shop), createGameShortcut: (id: number) => ipcRenderer.invoke("createGameShortcut", id), updateExecutablePath: (id: number, executablePath: string) => @@ -88,8 +88,8 @@ contextBridge.exposeInMainWorld("electron", { removeGame: (gameId: number) => ipcRenderer.invoke("removeGame", gameId), deleteGameFolder: (gameId: number) => ipcRenderer.invoke("deleteGameFolder", gameId), - getGameByObjectID: (objectID: string) => - ipcRenderer.invoke("getGameByObjectID", objectID), + getGameByObjectId: (objectId: string) => + ipcRenderer.invoke("getGameByObjectId", objectId), onGamesRunning: ( cb: ( gamesRunning: Pick[] diff --git a/src/renderer/src/app.css.ts b/src/renderer/src/app.css.ts index 4e0cf7a0..871f2220 100644 --- a/src/renderer/src/app.css.ts +++ b/src/renderer/src/app.css.ts @@ -26,6 +26,10 @@ globalStyle("::-webkit-scrollbar-thumb", { borderRadius: "24px", }); +globalStyle("::-webkit-scrollbar-thumb:hover", { + backgroundColor: "rgba(255, 255, 255, 0.16)", +}); + globalStyle("html, body, #root, main", { height: "100%", }); diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index 9d54bad8..c548be52 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -44,7 +44,7 @@ export function GameCard({ game, ...props }: GameCardProps) { const handleHover = useCallback(() => { if (!stats) { - window.electron.getGameStats(game.objectID, game.shop).then((stats) => { + window.electron.getGameStats(game.objectId, game.shop).then((stats) => { setStats(stats); }); } diff --git a/src/renderer/src/components/sidebar/sidebar.tsx b/src/renderer/src/components/sidebar/sidebar.tsx index 383d2197..c509e489 100644 --- a/src/renderer/src/components/sidebar/sidebar.tsx +++ b/src/renderer/src/components/sidebar/sidebar.tsx @@ -140,7 +140,10 @@ export function Sidebar() { event: React.MouseEvent, game: LibraryGame ) => { - const path = buildGameDetailsPath(game); + const path = buildGameDetailsPath({ + ...game, + objectId: game.objectID, + }); if (path !== location.pathname) { navigate(path); } diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 82984d9a..39771623 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -31,7 +31,7 @@ export const gameDetailsContext = createContext({ gameTitle: "", isGameRunning: false, isLoading: false, - objectID: undefined, + objectId: undefined, gameColor: "", showRepacksModal: false, showGameOptionsModal: false, @@ -97,7 +97,7 @@ export function GameDetailsContextProvider({ const updateGame = useCallback(async () => { return window.electron - .getGameByObjectID(objectId!) + .getGameByObjectId(objectId!) .then((result) => setGame(result)); }, [setGame, objectId]); @@ -199,7 +199,7 @@ export function GameDetailsContextProvider({ gameTitle, isGameRunning, isLoading, - objectID: objectId, + objectId, gameColor, showGameOptionsModal, showRepacksModal, diff --git a/src/renderer/src/context/game-details/game-details.context.types.ts b/src/renderer/src/context/game-details/game-details.context.types.ts index 7c3bd20b..9a09f74e 100644 --- a/src/renderer/src/context/game-details/game-details.context.types.ts +++ b/src/renderer/src/context/game-details/game-details.context.types.ts @@ -14,7 +14,7 @@ export interface GameDetailsContext { gameTitle: string; isGameRunning: boolean; isLoading: boolean; - objectID: string | undefined; + objectId: string | undefined; gameColor: string; showRepacksModal: boolean; showGameOptionsModal: boolean; diff --git a/src/renderer/src/context/repacks/repacks.context.tsx b/src/renderer/src/context/repacks/repacks.context.tsx index cddbb209..054df60b 100644 --- a/src/renderer/src/context/repacks/repacks.context.tsx +++ b/src/renderer/src/context/repacks/repacks.context.tsx @@ -50,6 +50,7 @@ export function RepacksContextProvider({ children }: RepacksContextProps) { }, []); useEffect(() => { + console.log("CALLED"); indexRepacks(); }, [indexRepacks]); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 870b592a..57a7db6d 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -51,27 +51,24 @@ declare global { searchGames: (query: string) => Promise; getCatalogue: (category: CatalogueCategory) => Promise; getGameShopDetails: ( - objectID: string, + objectId: string, shop: GameShop, language: string ) => Promise; getRandomGame: () => Promise; getHowLongToBeat: ( - objectID: string, + objectId: string, shop: GameShop, title: string ) => Promise; - getGames: ( - take?: number, - prevCursor?: number - ) => Promise<{ results: CatalogueEntry[]; cursor: number }>; + getGames: (take?: number, skip?: number) => Promise; searchGameRepacks: (query: string) => Promise; getGameStats: (objectId: string, shop: GameShop) => Promise; getTrendingGames: () => Promise; /* Library */ addGameToLibrary: ( - objectID: string, + objectId: string, title: string, shop: GameShop ) => Promise; @@ -87,7 +84,7 @@ declare global { removeGameFromLibrary: (gameId: number) => Promise; removeGame: (gameId: number) => Promise; deleteGameFolder: (gameId: number) => Promise; - getGameByObjectID: (objectID: string) => Promise; + getGameByObjectId: (objectId: string) => Promise; onGamesRunning: ( cb: ( gamesRunning: Pick[] diff --git a/src/renderer/src/helpers.ts b/src/renderer/src/helpers.ts index 3dea26d3..a9fc3cdd 100644 --- a/src/renderer/src/helpers.ts +++ b/src/renderer/src/helpers.ts @@ -27,11 +27,11 @@ export const getSteamLanguage = (language: string) => { }; export const buildGameDetailsPath = ( - game: { shop: GameShop; objectID: string; title: string }, + game: { shop: GameShop; objectId: string; title: string }, params: Record = {} ) => { const searchParams = new URLSearchParams({ title: game.title, ...params }); - return `/game/${game.shop}/${game.objectID}?${searchParams.toString()}`; + return `/game/${game.shop}/${game.objectId}?${searchParams.toString()}`; }; export const darkenColor = (color: string, amount: number, alpha: number = 1) => diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index d845e028..5b72264a 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -64,7 +64,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render( - + diff --git a/src/renderer/src/pages/catalogue/catalogue.tsx b/src/renderer/src/pages/catalogue/catalogue.tsx index ee1f5395..0ce77fa6 100644 --- a/src/renderer/src/pages/catalogue/catalogue.tsx +++ b/src/renderer/src/pages/catalogue/catalogue.tsx @@ -24,12 +24,10 @@ export function Catalogue() { const contentRef = useRef(null); - const cursorRef = useRef(0); - const navigate = useNavigate(); const [searchParams] = useSearchParams(); - const cursor = Number(searchParams.get("cursor") ?? 0); + const skip = Number(searchParams.get("skip") ?? 0); const handleGameClick = (game: CatalogueEntry) => { dispatch(clearSearch()); @@ -42,11 +40,10 @@ export function Catalogue() { setSearchResults([]); window.electron - .getGames(24, cursor) - .then(({ results, cursor }) => { + .getGames(24, skip) + .then((results) => { return new Promise((resolve) => { setTimeout(() => { - cursorRef.current = cursor; setSearchResults(results); resolve(null); }, 500); @@ -55,11 +52,11 @@ export function Catalogue() { .finally(() => { setIsLoading(false); }); - }, [dispatch, cursor, searchParams]); + }, [dispatch, skip, searchParams]); const handleNextPage = () => { const params = new URLSearchParams({ - cursor: cursorRef.current.toString(), + skip: String(skip + 24), }); navigate(`/catalogue?${params.toString()}`); @@ -80,7 +77,7 @@ export function Catalogue() { diff --git a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx index db36a285..f8a31223 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx @@ -1,4 +1,4 @@ -import { Modal, ModalProps, TextField } from "@renderer/components"; +import { Modal, ModalProps } from "@renderer/components"; import { useContext, useMemo } from "react"; import { cloudSyncContext } from "@renderer/context"; @@ -11,6 +11,8 @@ export function CloudSyncFilesModal({ }: CloudSyncFilesModalProps) { const { backupPreview } = useContext(cloudSyncContext); + console.log(backupPreview); + const files = useMemo(() => { if (!backupPreview) { return []; @@ -51,22 +53,24 @@ export function CloudSyncFilesModal({ ))} */} -
        - {files.map((file) => ( -
      • - -
      • - ))} -
      + + + + + + + + + + {files.map((file) => ( + + + + + + ))} + +
      ArquivoHashTamanho
      {file.path}{file.change}{file.path}
      ); } diff --git a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx index ef57d1be..27ac7d80 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx @@ -43,7 +43,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { setShowCloudSyncFilesModal, } = useContext(cloudSyncContext); - const { objectID, shop, gameTitle } = useContext(gameDetailsContext); + const { objectId, shop, gameTitle } = useContext(gameDetailsContext); const { showSuccessToast, showErrorToast } = useToast(); @@ -63,13 +63,13 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { useEffect(() => { gameBackupsTable - .where({ shop: shop, objectId: objectID }) + .where({ shop: shop, objectId }) .last() .then((lastBackup) => setLastBackup(lastBackup || null)); const removeBackupDownloadProgressListener = window.electron.onBackupDownloadProgress( - objectID!, + objectId!, shop, (progressEvent) => { setBackupDownloadProgress(progressEvent); @@ -79,7 +79,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { return () => { removeBackupDownloadProgressListener(); }; - }, [backupPreview, objectID, shop]); + }, [backupPreview, objectId, shop]); const handleBackupInstallClick = async (artifactId: string) => { setBackupDownloadProgress(null); diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 7350ccfd..fab6a49b 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -26,7 +26,7 @@ export function GameDetailsContent() { const { t } = useTranslation("game_details"); const { - objectID, + objectId, shopDetails, game, gameColor, @@ -42,7 +42,7 @@ export function GameDetailsContent() { const [backdropOpactiy, setBackdropOpacity] = useState(1); const handleHeroLoad = async () => { - const output = await average(steamUrlBuilder.libraryHero(objectID!), { + const output = await average(steamUrlBuilder.libraryHero(objectId!), { amount: 1, format: "hex", }); @@ -56,7 +56,7 @@ export function GameDetailsContent() { useEffect(() => { setBackdropOpacity(1); - }, [objectID]); + }, [objectId]); const onScroll: React.UIEventHandler = (event) => { const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT; @@ -90,7 +90,7 @@ export function GameDetailsContent() { return (
      {game?.title}
      {game?.title} diff --git a/src/renderer/src/pages/game-details/game-details.tsx b/src/renderer/src/pages/game-details/game-details.tsx index 89a34fff..f2b928b5 100644 --- a/src/renderer/src/pages/game-details/game-details.tsx +++ b/src/renderer/src/pages/game-details/game-details.tsx @@ -33,7 +33,7 @@ export function GameDetails() { const [randomGame, setRandomGame] = useState(null); const [randomizerLocked, setRandomizerLocked] = useState(false); - const { objectID, shop } = useParams(); + const { objectId, shop } = useParams(); const [searchParams] = useSearchParams(); const fromRandomizer = searchParams.get("fromRandomizer"); @@ -50,7 +50,7 @@ export function GameDetails() { window.electron.getRandomGame().then((randomGame) => { setRandomGame(randomGame); }); - }, [objectID]); + }, [objectId]); const handleRandomizerClick = () => { if (randomGame) { @@ -82,7 +82,7 @@ export function GameDetails() { {({ @@ -105,7 +105,7 @@ export function GameDetails() { ) => { await startDownload({ repackId: repack.id, - objectID: objectID!, + objectId: objectId!, title: gameTitle, downloader, shop: shop as GameShop, @@ -125,7 +125,7 @@ export function GameDetails() { return ( diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx index 323b3c8f..88eb7c63 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx @@ -18,7 +18,7 @@ export function HeroPanelActions() { game, repacks, isGameRunning, - objectID, + objectId, gameTitle, setShowGameOptionsModal, setShowRepacksModal, @@ -39,7 +39,7 @@ export function HeroPanelActions() { setToggleLibraryGameDisabled(true); try { - await window.electron.addGameToLibrary(objectID!, gameTitle, "steam"); + await window.electron.addGameToLibrary(objectId!, gameTitle, "steam"); updateLibrary(); updateGame(); diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index e56f0764..5d7e2d56 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -24,11 +24,11 @@ export function Sidebar() { const { numberFormatter } = useFormat(); // useEffect(() => { - // if (objectID) { + // if (objectId) { // setHowLongToBeat({ isLoading: true, data: null }); // window.electron - // .getHowLongToBeat(objectID, "steam", gameTitle) + // .getHowLongToBeat(objectId, "steam", gameTitle) // .then((howLongToBeat) => { // setHowLongToBeat({ isLoading: false, data: howLongToBeat }); // }) @@ -36,7 +36,7 @@ export function Sidebar() { // setHowLongToBeat({ isLoading: false, data: null }); // }); // } - // }, [objectID, gameTitle]); + // }, [objectId, gameTitle]); return (
      { uris: string; } +const state = { + repacks: [] as SerializedGameRepack[], +}; + self.onmessage = async ( event: MessageEvent<[string, string] | "INDEX_REPACKS"> ) => { diff --git a/src/shared/index.ts b/src/shared/index.ts index 556000f2..ec3fe9d9 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -91,14 +91,14 @@ export const getDownloadersForUris = (uris: string[]) => { }; export const steamUrlBuilder = { - library: (objectID: string) => - `https://steamcdn-a.akamaihd.net/steam/apps/${objectID}/header.jpg`, - libraryHero: (objectID: string) => - `https://steamcdn-a.akamaihd.net/steam/apps/${objectID}/library_hero.jpg`, - logo: (objectID: string) => - `https://cdn.cloudflare.steamstatic.com/steam/apps/${objectID}/logo.png`, - cover: (objectID: string) => - `https://cdn.cloudflare.steamstatic.com/steam/apps/${objectID}/library_600x900.jpg`, - icon: (objectID: string, clientIcon: string) => - `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${objectID}/${clientIcon}.ico`, + library: (objectId: string) => + `https://steamcdn-a.akamaihd.net/steam/apps/${objectId}/header.jpg`, + libraryHero: (objectId: string) => + `https://steamcdn-a.akamaihd.net/steam/apps/${objectId}/library_hero.jpg`, + logo: (objectId: string) => + `https://cdn.cloudflare.steamstatic.com/steam/apps/${objectId}/logo.png`, + cover: (objectId: string) => + `https://cdn.cloudflare.steamstatic.com/steam/apps/${objectId}/library_600x900.jpg`, + icon: (objectId: string, clientIcon: string) => + `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${objectId}/${clientIcon}.ico`, }; diff --git a/src/types/index.ts b/src/types/index.ts index cd06cfcc..0ab1c8b9 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -29,7 +29,7 @@ export interface GameRepack { } export type ShopDetails = SteamAppDetails & { - objectID: string; + objectId: string; }; export interface TorrentFile { @@ -39,7 +39,7 @@ export interface TorrentFile { /* Used by the catalogue */ export interface CatalogueEntry { - objectID: string; + objectId: string; shop: GameShop; title: string; /* Epic Games covers cannot be guessed with objectID */ @@ -54,6 +54,8 @@ export interface UserGame { cover: string; playTimeInSeconds: number; lastTimePlayed: Date | null; + unlockedAchievementCount: number; + achievementCount: number; } export interface DownloadQueue { @@ -126,7 +128,7 @@ export interface HowLongToBeatCategory { export interface Steam250Game { title: string; - objectID: string; + objectId: string; } export interface SteamGame { @@ -142,7 +144,7 @@ export type AppUpdaterEvent = /* Events */ export interface StartGameDownloadPayload { repackId: number; - objectID: string; + objectId: string; title: string; shop: GameShop; uri: string; @@ -187,7 +189,7 @@ export interface UserRelation { updatedAt: string; } -export interface UserProfileCurrentGame extends Omit { +export interface UserProfileCurrentGame extends Omit { objectId: string; sessionDurationInSeconds: number; } diff --git a/yarn.lock b/yarn.lock index 75b9a7d8..8af7c20f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -991,6 +991,13 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + "@jimp/bmp@^0.22.10": version "0.22.12" resolved "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz" @@ -1923,20 +1930,6 @@ dependencies: "@types/node" "*" -"@types/adm-zip@^0.5.5": - version "0.5.5" - resolved "https://registry.yarnpkg.com/@types/adm-zip/-/adm-zip-0.5.5.tgz#4588042726aa5f351d7ea88232e4a952f60e7c1a" - integrity sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw== - dependencies: - "@types/node" "*" - -"@types/archiver@^6.0.2": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@types/archiver/-/archiver-6.0.2.tgz#0daf8c83359cbde69de1e4b33dcade6a48a929e2" - integrity sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw== - dependencies: - "@types/readdir-glob" "*" - "@types/auto-launch@^5.0.5": version "5.0.5" resolved "https://registry.npmjs.org/@types/auto-launch/-/auto-launch-5.0.5.tgz" @@ -2311,13 +2304,6 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@types/readdir-glob@*": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@types/readdir-glob/-/readdir-glob-1.1.5.tgz#21a4a98898fc606cb568ad815f2a0eedc24d412a" - integrity sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg== - dependencies: - "@types/node" "*" - "@types/responselike@^1.0.0": version "1.0.3" resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz" @@ -2609,11 +2595,6 @@ acorn@^8.8.1, acorn@^8.8.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== -adm-zip@^0.5.16: - version "0.5.16" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909" - integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== - agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" @@ -2743,32 +2724,6 @@ applescript@^1.0.0: resolved "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz" integrity sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ== -archiver-utils@^5.0.0, archiver-utils@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-5.0.2.tgz#63bc719d951803efc72cf961a56ef810760dd14d" - integrity sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA== - dependencies: - glob "^10.0.0" - graceful-fs "^4.2.0" - is-stream "^2.0.1" - lazystream "^1.0.0" - lodash "^4.17.15" - normalize-path "^3.0.0" - readable-stream "^4.0.0" - -archiver@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-7.0.1.tgz#c9d91c350362040b8927379c7aa69c0655122f61" - integrity sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ== - dependencies: - archiver-utils "^5.0.2" - async "^3.2.4" - buffer-crc32 "^1.0.0" - readable-stream "^4.0.0" - readdir-glob "^1.1.2" - tar-stream "^3.0.0" - zip-stream "^6.0.1" - arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -2908,11 +2863,6 @@ async@^3.2.3: resolved "https://registry.npmjs.org/async/-/async-3.2.5.tgz" integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== -async@^3.2.4: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" @@ -2962,21 +2912,11 @@ axobject-query@^3.2.1: dependencies: dequal "^2.0.3" -b4a@^1.6.4: - version "1.6.6" - resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.6.tgz#a4cc349a3851987c3c4ac2d7785c18744f6da9ba" - integrity sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -bare-events@^2.2.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.0.tgz#305b511e262ffd8b9d5616b056464f8e1b3329cc" - integrity sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A== - base64-arraybuffer@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz" @@ -3089,11 +3029,6 @@ browserslist@^4.22.2: node-releases "^2.0.14" update-browserslist-db "^1.0.13" -buffer-crc32@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405" - integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w== - buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" @@ -3269,6 +3204,11 @@ chownr@^2.0.0: resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + chromium-pickle-js@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz" @@ -3414,17 +3354,6 @@ compare-version@^0.1.2: resolved "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz" integrity sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A== -compress-commons@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-6.0.2.tgz#26d31251a66b9d6ba23a84064ecd3a6a71d2609e" - integrity sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg== - dependencies: - crc-32 "^1.2.0" - crc32-stream "^6.0.0" - is-stream "^2.0.1" - normalize-path "^3.0.0" - readable-stream "^4.0.0" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -3477,11 +3406,6 @@ core-util-is@1.0.2: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - cosmiconfig-typescript-loader@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz" @@ -3509,19 +3433,6 @@ cosmiconfig@^9.0.0: js-yaml "^4.1.0" parse-json "^5.2.0" -crc-32@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" - integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== - -crc32-stream@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-6.0.0.tgz#8529a3868f8b27abb915f6c3617c0fadedbf9430" - integrity sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g== - dependencies: - crc-32 "^1.2.0" - readable-stream "^4.0.0" - crc@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" @@ -4345,11 +4256,6 @@ event-target-shim@^5.0.0: resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -events@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - execa@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz" @@ -4396,11 +4302,6 @@ fast-diff@^1.1.2: resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-fifo@^1.2.0, fast-fifo@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" - integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== - fast-glob@^3.2.9: version "3.3.2" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" @@ -4721,18 +4622,6 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.0.0: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - glob@^10.3.10: version "10.3.15" resolved "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz" @@ -4744,6 +4633,18 @@ glob@^10.3.10: minipass "^7.0.4" path-scurry "^1.11.0" +glob@^10.3.7: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^7.1.3, glob@^7.1.6: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" @@ -5101,7 +5002,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -5298,11 +5199,6 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: dependencies: call-bind "^1.0.7" -is-stream@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - is-stream@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz" @@ -5361,11 +5257,6 @@ isarray@^2.0.5: resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - isbinaryfile@^4.0.8: version "4.0.10" resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz" @@ -5622,13 +5513,6 @@ lazy-val@^1.0.4, lazy-val@^1.0.5: resolved "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz" integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== -lazystream@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" - integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== - dependencies: - readable-stream "^2.0.5" - levn@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" @@ -5917,7 +5801,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1, minimatch@^5.1.0, minimatch@^5.1.1: +minimatch@^5.0.1, minimatch@^5.1.1: version "5.1.6" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== @@ -5985,6 +5869,14 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" +minizlib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" + integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== + dependencies: + minipass "^7.0.4" + rimraf "^5.0.5" + mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" @@ -6007,6 +5899,11 @@ mkdirp@^2.1.3: resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz" integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + mlly@^1.4.2, mlly@^1.7.0: version "1.7.0" resolved "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz" @@ -6580,16 +6477,6 @@ prettier@^3.2.4: resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz" integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - progress@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" @@ -6650,11 +6537,6 @@ queue-microtask@^1.2.2, queue-microtask@^1.2.3: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -queue-tick@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142" - integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag== - quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz" @@ -6748,19 +6630,6 @@ read-config-file@6.3.2: json5 "^2.2.0" lazy-val "^1.0.4" -readable-stream@^2.0.5: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" @@ -6770,17 +6639,6 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^4.0.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" - integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== - dependencies: - abort-controller "^3.0.0" - buffer "^6.0.3" - events "^3.3.0" - process "^0.11.10" - string_decoder "^1.3.0" - readable-web-to-node-stream@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz" @@ -6788,13 +6646,6 @@ readable-web-to-node-stream@^3.0.2: dependencies: readable-stream "^3.6.0" -readdir-glob@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" - integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== - dependencies: - minimatch "^5.1.0" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -6948,6 +6799,13 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^5.0.5: + version "5.0.10" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" + integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== + dependencies: + glob "^10.3.7" + roarr@^2.15.3: version "2.15.4" resolved "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz" @@ -7012,11 +6870,6 @@ safe-buffer@^5.0.1, safe-buffer@~5.2.0: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz" @@ -7234,17 +7087,6 @@ stat-mode@^1.0.0: resolved "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz" integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg== -streamx@^2.15.0: - version "2.20.1" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.20.1.tgz#471c4f8b860f7b696feb83d5b125caab2fdbb93c" - integrity sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA== - dependencies: - fast-fifo "^1.3.2" - queue-tick "^1.0.1" - text-decoder "^1.1.0" - optionalDependencies: - bare-events "^2.2.0" - "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" @@ -7318,20 +7160,13 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.1.1, string_decoder@^1.3.0: +string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - "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" @@ -7446,15 +7281,6 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar-stream@^3.0.0: - version "3.1.7" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" - integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== - dependencies: - b4a "^1.6.4" - fast-fifo "^1.2.0" - streamx "^2.15.0" - tar@^6.1.12: version "6.2.1" resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz" @@ -7467,6 +7293,18 @@ tar@^6.1.12: mkdirp "^1.0.3" yallist "^4.0.0" +tar@^7.4.3: + version "7.4.3" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" + integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.0.1" + mkdirp "^3.0.1" + yallist "^5.0.0" + tarn@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" @@ -7480,13 +7318,6 @@ temp-file@^3.4.0: async-exit-hook "^2.0.1" fs-extra "^10.0.0" -text-decoder@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.0.tgz#85f19d4d5088e0b45cd841bdfaeac458dbffeefc" - integrity sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg== - dependencies: - b4a "^1.6.4" - text-extensions@^2.0.0: version "2.4.0" resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz" @@ -7837,7 +7668,7 @@ utf8-byte-length@^1.0.1: resolved "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz" integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA== -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -8096,6 +7927,11 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== + yaml@^2.4.1: version "2.4.2" resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz" @@ -8170,15 +8006,6 @@ yup@^1.4.0: toposort "^2.0.2" type-fest "^2.19.0" -zip-stream@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-6.0.1.tgz#e141b930ed60ccaf5d7fa9c8260e0d1748a2bbfb" - integrity sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA== - dependencies: - archiver-utils "^5.0.0" - compress-commons "^6.0.2" - readable-stream "^4.0.0" - zod@^3.23.8: version "3.23.8" resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz" From 4222fcec52d66b56f55307a9cc26240464bc98e4 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 5 Oct 2024 02:22:43 +0100 Subject: [PATCH 14/22] feat: adding change hero --- .../src/pages/profile/profile-hero/profile-hero.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx b/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx index a4d702f6..f81761ea 100644 --- a/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx +++ b/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx @@ -274,15 +274,7 @@ export function ProfileHero() { if (filePaths && filePaths.length > 0) { const path = filePaths[0]; - const { imagePath } = await window.electron - .processProfileImage(path) - .catch(() => { - showErrorToast(t("image_process_failure")); - return { imagePath: null }; - }); - - console.log("imagePath", imagePath); - setHero(imagePath); + setHero(path); // onChange(imagePath); } @@ -292,7 +284,7 @@ export function ProfileHero() { if (hero) return `local:${hero}`; // if (userDetails?.profileImageUrl) return userDetails.profileImageUrl; - return null; + return ""; }; // const imageUrl = getImageUrl(); From 0873c8e244074ca7a8d3845ddb144aee59edacad Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 5 Oct 2024 03:27:40 +0100 Subject: [PATCH 15/22] fix: fixing windows path replacement --- .../cloud-save/download-game-artifact.ts | 35 +++++++++++++------ .../events/cloud-save/upload-save-game.ts | 2 +- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main/events/cloud-save/download-game-artifact.ts b/src/main/events/cloud-save/download-game-artifact.ts index d587daaa..289e5dec 100644 --- a/src/main/events/cloud-save/download-game-artifact.ts +++ b/src/main/events/cloud-save/download-game-artifact.ts @@ -8,6 +8,8 @@ import path from "node:path"; import { backupsPath } from "@main/constants"; import type { GameShop } from "@types"; +import os from "node:os"; + import YAML from "yaml"; export interface LudusaviBackup { @@ -20,14 +22,27 @@ export interface LudusaviBackup { } const replaceLudusaviBackupWithCurrentUser = ( - mappingPath: string, + gameBackupPath: string, backupHomeDir: string ) => { - const data = fs.readFileSync(mappingPath, "utf8"); - const manifest = YAML.parse(data); + const mappingYamlPath = path.join(gameBackupPath, "mapping.yaml"); + + const data = fs.readFileSync(mappingYamlPath, "utf8"); + const manifest = YAML.parse(data) as { + backups: LudusaviBackup[]; + drives: Record; + }; const currentHomeDir = app.getPath("home"); + // TODO: Only works on Windows + const usersDirPath = path.join(gameBackupPath, "drive-C", "Users"); + + fs.renameSync( + path.join(usersDirPath, path.basename(backupHomeDir)), + path.join(usersDirPath, os.userInfo().username) + ); + const backups = manifest.backups.map((backup: LudusaviBackup) => { const files = Object.entries(backup.files).reduce((prev, [key, value]) => { return { @@ -42,7 +57,9 @@ const replaceLudusaviBackupWithCurrentUser = ( }; }); - fs.writeFileSync(mappingPath, YAML.stringify({ ...manifest, backups })); + console.log(backups); + + fs.writeFileSync(mappingYamlPath, YAML.stringify({ ...manifest, backups })); }; const downloadGameArtifact = async ( @@ -89,15 +106,11 @@ const downloadGameArtifact = async ( const [game] = await Ludusavi.findGames(shop, objectId); if (!game) throw new Error("Game not found in Ludusavi manifest"); - const mappingPath = path.join( - backupsPath, - `${shop}-${objectId}`, - game, - "mapping.yaml" + replaceLudusaviBackupWithCurrentUser( + path.join(backupPath, game), + path.normalize(homeDir).replace(/\\/g, "/") ); - replaceLudusaviBackupWithCurrentUser(mappingPath, homeDir); - Ludusavi.restoreBackup(backupPath).then(() => { WindowManager.mainWindow?.webContents.send( `on-backup-download-complete-${objectId}-${shop}`, diff --git a/src/main/events/cloud-save/upload-save-game.ts b/src/main/events/cloud-save/upload-save-game.ts index 9948da31..919cfd9e 100644 --- a/src/main/events/cloud-save/upload-save-game.ts +++ b/src/main/events/cloud-save/upload-save-game.ts @@ -50,7 +50,7 @@ const uploadSaveGame = async ( shop, objectId, hostname: os.hostname(), - homeDir: app.getPath("home"), + homeDir: path.normalize(app.getPath("home")).replace(/\\/g, "/"), platform: os.platform(), }); From bcbe6c96192e7246f1ba08664df7dae182c12663 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 5 Oct 2024 03:53:51 +0100 Subject: [PATCH 16/22] updating current home dir --- src/main/events/cloud-save/download-game-artifact.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/events/cloud-save/download-game-artifact.ts b/src/main/events/cloud-save/download-game-artifact.ts index 289e5dec..adef7ce4 100644 --- a/src/main/events/cloud-save/download-game-artifact.ts +++ b/src/main/events/cloud-save/download-game-artifact.ts @@ -8,8 +8,6 @@ import path from "node:path"; import { backupsPath } from "@main/constants"; import type { GameShop } from "@types"; -import os from "node:os"; - import YAML from "yaml"; export interface LudusaviBackup { @@ -40,7 +38,7 @@ const replaceLudusaviBackupWithCurrentUser = ( fs.renameSync( path.join(usersDirPath, path.basename(backupHomeDir)), - path.join(usersDirPath, os.userInfo().username) + path.join(usersDirPath, path.basename(currentHomeDir)) ); const backups = manifest.backups.map((backup: LudusaviBackup) => { From b99dbe83e2ca72a4de6c00cb3de7fba1fd29f051 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 5 Oct 2024 03:55:48 +0100 Subject: [PATCH 17/22] adding mkdir for backup path --- src/main/events/cloud-save/download-game-artifact.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/events/cloud-save/download-game-artifact.ts b/src/main/events/cloud-save/download-game-artifact.ts index adef7ce4..f6899e6f 100644 --- a/src/main/events/cloud-save/download-game-artifact.ts +++ b/src/main/events/cloud-save/download-game-artifact.ts @@ -94,6 +94,8 @@ const downloadGameArtifact = async ( throw err; }); + fs.mkdirSync(backupPath, { recursive: true }); + writer.on("close", () => { tar .x({ From 9391b7e6c93a9ba84e8ecba6f7f8cc5fbfae0c37 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 5 Oct 2024 04:29:29 +0100 Subject: [PATCH 18/22] feat: removing directory sync --- src/main/events/cloud-save/download-game-artifact.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/events/cloud-save/download-game-artifact.ts b/src/main/events/cloud-save/download-game-artifact.ts index f6899e6f..5c0b2c32 100644 --- a/src/main/events/cloud-save/download-game-artifact.ts +++ b/src/main/events/cloud-save/download-game-artifact.ts @@ -36,6 +36,11 @@ const replaceLudusaviBackupWithCurrentUser = ( // TODO: Only works on Windows const usersDirPath = path.join(gameBackupPath, "drive-C", "Users"); + fs.rmSync(gameBackupPath, { + recursive: true, + force: true, + }); + fs.renameSync( path.join(usersDirPath, path.basename(backupHomeDir)), path.join(usersDirPath, path.basename(currentHomeDir)) @@ -55,8 +60,6 @@ const replaceLudusaviBackupWithCurrentUser = ( }; }); - console.log(backups); - fs.writeFileSync(mappingYamlPath, YAML.stringify({ ...manifest, backups })); }; From f6acfa4aeeccac3313160d3bff8a464a49b9540b Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 5 Oct 2024 04:53:15 +0100 Subject: [PATCH 19/22] feat: removing existing directory --- src/main/events/cloud-save/download-game-artifact.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/events/cloud-save/download-game-artifact.ts b/src/main/events/cloud-save/download-game-artifact.ts index 5c0b2c32..7a92dc85 100644 --- a/src/main/events/cloud-save/download-game-artifact.ts +++ b/src/main/events/cloud-save/download-game-artifact.ts @@ -36,11 +36,6 @@ const replaceLudusaviBackupWithCurrentUser = ( // TODO: Only works on Windows const usersDirPath = path.join(gameBackupPath, "drive-C", "Users"); - fs.rmSync(gameBackupPath, { - recursive: true, - force: true, - }); - fs.renameSync( path.join(usersDirPath, path.basename(backupHomeDir)), path.join(usersDirPath, path.basename(currentHomeDir)) @@ -78,6 +73,13 @@ const downloadGameArtifact = async ( const zipLocation = path.join(app.getPath("userData"), objectKey); const backupPath = path.join(backupsPath, `${shop}-${objectId}`); + if (fs.existsSync(backupPath)) { + fs.rmSync(backupPath, { + recursive: true, + force: true, + }); + } + const response = await axios.get(downloadUrl, { responseType: "stream", onDownloadProgress: (progressEvent) => { From 02221212888ca7584b36f2ee15e907df0ba5af15 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 5 Oct 2024 07:12:47 +0100 Subject: [PATCH 20/22] fix: fixing multiple folders --- .../cloud-save/download-game-artifact.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/events/cloud-save/download-game-artifact.ts b/src/main/events/cloud-save/download-game-artifact.ts index 7a92dc85..d0e8d845 100644 --- a/src/main/events/cloud-save/download-game-artifact.ts +++ b/src/main/events/cloud-save/download-game-artifact.ts @@ -36,10 +36,20 @@ const replaceLudusaviBackupWithCurrentUser = ( // TODO: Only works on Windows const usersDirPath = path.join(gameBackupPath, "drive-C", "Users"); - fs.renameSync( - path.join(usersDirPath, path.basename(backupHomeDir)), - path.join(usersDirPath, path.basename(currentHomeDir)) - ); + const oldPath = path.join(usersDirPath, path.basename(backupHomeDir)); + const newPath = path.join(usersDirPath, path.basename(currentHomeDir)); + + // Directories are different, rename + if (backupHomeDir !== currentHomeDir) { + if (fs.existsSync(newPath)) { + fs.rmSync(newPath, { + recursive: true, + force: true, + }); + } + + fs.renameSync(oldPath, newPath); + } const backups = manifest.backups.map((backup: LudusaviBackup) => { const files = Object.entries(backup.files).reduce((prev, [key, value]) => { From baafc6c7d113ec5581dfa95fdc3c5c74a3088634 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 7 Oct 2024 20:56:53 +0100 Subject: [PATCH 21/22] feat: adding hltb key extraction --- src/main/entity/game-shop-cache.entity.ts | 3 + .../events/catalogue/get-how-long-to-beat.ts | 38 +- src/main/events/profile/update-profile.ts | 4 +- src/main/services/how-long-to-beat.ts | 57 ++- src/main/services/window-manager.ts | 53 +- src/preload/index.ts | 4 +- .../src/context/repacks/repacks.context.tsx | 2 + src/renderer/src/declaration.d.ts | 2 - src/renderer/src/dexie.ts | 17 +- .../game-details/game-details-skeleton.tsx | 17 - .../pages/game-details/hero/hero-panel.tsx | 1 + .../sidebar-section/sidebar-section.css.ts | 37 ++ .../sidebar-section/sidebar-section.tsx | 38 ++ .../sidebar/how-long-to-beat-section.tsx | 66 +-- .../pages/game-details/sidebar/sidebar.css.ts | 18 +- .../pages/game-details/sidebar/sidebar.tsx | 137 +++--- .../edit-profile-modal/edit-profile-modal.tsx | 4 + src/renderer/src/workers/repacks.worker.ts | 9 +- src/shared/char-map.ts | 461 ++++++++++++++++++ src/shared/index.ts | 7 + src/types/howlongtobeat.types.ts | 14 + src/types/index.ts | 7 +- 22 files changed, 791 insertions(+), 205 deletions(-) create mode 100644 src/renderer/src/pages/game-details/sidebar-section/sidebar-section.css.ts create mode 100644 src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx create mode 100644 src/shared/char-map.ts create mode 100644 src/types/howlongtobeat.types.ts diff --git a/src/main/entity/game-shop-cache.entity.ts b/src/main/entity/game-shop-cache.entity.ts index f83e1b0c..3382da1c 100644 --- a/src/main/entity/game-shop-cache.entity.ts +++ b/src/main/entity/game-shop-cache.entity.ts @@ -18,6 +18,9 @@ export class GameShopCache { @Column("text", { nullable: true }) serializedData: string; + /** + * @deprecated Use IndexedDB's `howLongToBeatEntries` instead + */ @Column("text", { nullable: true }) howLongToBeatSerializedData: string; diff --git a/src/main/events/catalogue/get-how-long-to-beat.ts b/src/main/events/catalogue/get-how-long-to-beat.ts index f489f804..01966afc 100644 --- a/src/main/events/catalogue/get-how-long-to-beat.ts +++ b/src/main/events/catalogue/get-how-long-to-beat.ts @@ -1,45 +1,23 @@ -import type { GameShop, HowLongToBeatCategory } from "@types"; +import type { HowLongToBeatCategory } from "@types"; import { getHowLongToBeatGame, searchHowLongToBeat } from "@main/services"; import { registerEvent } from "../register-event"; -import { gameShopCacheRepository } from "@main/repository"; +import { formatName } from "@shared"; const getHowLongToBeat = async ( _event: Electron.IpcMainInvokeEvent, - objectId: string, - shop: GameShop, title: string ): Promise => { - const searchHowLongToBeatPromise = searchHowLongToBeat(title); + const response = await searchHowLongToBeat(title); - const gameShopCache = await gameShopCacheRepository.findOne({ - where: { objectID: objectId, shop }, + const game = response.data.find((game) => { + return formatName(game.game_name) === formatName(title); }); - const howLongToBeatCachedData = gameShopCache?.howLongToBeatSerializedData - ? JSON.parse(gameShopCache?.howLongToBeatSerializedData) - : null; - if (howLongToBeatCachedData) return howLongToBeatCachedData; + if (!game) return null; + const howLongToBeat = await getHowLongToBeatGame(String(game.game_id)); - return searchHowLongToBeatPromise.then(async (response) => { - const game = response.data.find( - (game) => game.profile_steam === Number(objectId) - ); - - if (!game) return null; - const howLongToBeat = await getHowLongToBeatGame(String(game.game_id)); - - gameShopCacheRepository.upsert( - { - objectID: objectId, - shop, - howLongToBeatSerializedData: JSON.stringify(howLongToBeat), - }, - ["objectID"] - ); - - return howLongToBeat; - }); + return howLongToBeat; }; registerEvent("getHowLongToBeat", getHowLongToBeat); diff --git a/src/main/events/profile/update-profile.ts b/src/main/events/profile/update-profile.ts index eb80bc47..b1244684 100644 --- a/src/main/events/profile/update-profile.ts +++ b/src/main/events/profile/update-profile.ts @@ -48,7 +48,9 @@ const updateProfile = async ( const profileImageUrl = await getNewProfileImageUrl( updateProfile.profileImageUrl - ).catch(() => undefined); + ).catch((err) => { + console.log(err); + }); return patchUserProfile({ ...updateProfile, profileImageUrl }); }; diff --git a/src/main/services/how-long-to-beat.ts b/src/main/services/how-long-to-beat.ts index c7164d09..5a82d8e7 100644 --- a/src/main/services/how-long-to-beat.ts +++ b/src/main/services/how-long-to-beat.ts @@ -1,32 +1,65 @@ import axios from "axios"; import { requestWebPage } from "@main/helpers"; -import { HowLongToBeatCategory } from "@types"; +import type { + HowLongToBeatCategory, + HowLongToBeatSearchResponse, +} from "@types"; import { formatName } from "@shared"; import { logger } from "./logger"; +import UserAgent from "user-agents"; -export interface HowLongToBeatResult { - game_id: number; - profile_steam: number; -} +const state = { + apiKey: null as string | null, +}; -export interface HowLongToBeatSearchResponse { - data: HowLongToBeatResult[]; -} +const getHowLongToBeatSearchApiKey = async () => { + const userAgent = new UserAgent(); + + const document = await requestWebPage("https://howlongtobeat.com/"); + const scripts = Array.from(document.querySelectorAll("script")); + + const appScript = scripts.find((script) => + script.src.startsWith("/_next/static/chunks/pages/_app") + ); + + if (!appScript) return null; + + const response = await axios.get( + `https://howlongtobeat.com${appScript.src}`, + { + headers: { + "User-Agent": userAgent.toString(), + }, + } + ); + + const results = /fetch\("\/api\/search\/"\.concat\("(.*?)"\)/gm.exec( + response.data + ); + + if (!results) return null; + + return results[1]; +}; export const searchHowLongToBeat = async (gameName: string) => { + state.apiKey = state.apiKey ?? (await getHowLongToBeatSearchApiKey()); + if (!state.apiKey) return { data: [] }; + + const userAgent = new UserAgent(); + const response = await axios .post( - "https://howlongtobeat.com/api/search", + "https://howlongtobeat.com/api/search/8fbd64723a8204dd", { searchType: "games", searchTerms: formatName(gameName).split(" "), searchPage: 1, - size: 100, + size: 20, }, { headers: { - "User-Agent": - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + "User-Agent": userAgent.toString(), Referer: "https://howlongtobeat.com/", }, } diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 9b9d09b2..164ccf9c 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -16,6 +16,7 @@ import trayIcon from "@resources/tray-icon.png?asset"; import { gameRepository, userPreferencesRepository } from "@main/repository"; import { IsNull, Not } from "typeorm"; import { HydraApi } from "./hydra-api"; +import UserAgent from "user-agents"; export class WindowManager { public static mainWindow: Electron.BrowserWindow | null = null; @@ -79,11 +80,12 @@ export class WindowManager { this.mainWindow.webContents.session.webRequest.onBeforeSendHeaders( (details, callback) => { + const userAgent = new UserAgent(); + callback({ requestHeaders: { ...details.requestHeaders, - "user-agent": - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + "user-agent": userAgent.toString(), }, }); } @@ -146,30 +148,29 @@ export class WindowManager { } public static createNotificationWindow() { - this.notificationWindow = new BrowserWindow({ - transparent: true, - maximizable: false, - autoHideMenuBar: true, - minimizable: false, - focusable: false, - skipTaskbar: true, - frame: false, - width: 350, - height: 104, - x: 0, - y: 0, - webPreferences: { - preload: path.join(__dirname, "../preload/index.mjs"), - sandbox: false, - }, - }); - - this.notificationWindow.setIgnoreMouseEvents(true); - this.notificationWindow.setVisibleOnAllWorkspaces(true, { - visibleOnFullScreen: true, - }); - this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); - this.loadNotificationWindowURL(); + // this.notificationWindow = new BrowserWindow({ + // transparent: true, + // maximizable: false, + // autoHideMenuBar: true, + // minimizable: false, + // focusable: false, + // skipTaskbar: true, + // frame: false, + // width: 350, + // height: 104, + // x: 0, + // y: 0, + // webPreferences: { + // preload: path.join(__dirname, "../preload/index.mjs"), + // sandbox: false, + // }, + // }); + // this.notificationWindow.setIgnoreMouseEvents(true); + // this.notificationWindow.setVisibleOnAllWorkspaces(true, { + // visibleOnFullScreen: true, + // }); + // this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); + // this.loadNotificationWindowURL(); } public static openAuthWindow() { diff --git a/src/preload/index.ts b/src/preload/index.ts index 761696f9..decd407c 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -41,8 +41,8 @@ contextBridge.exposeInMainWorld("electron", { getGameShopDetails: (objectId: string, shop: GameShop, language: string) => ipcRenderer.invoke("getGameShopDetails", objectId, shop, language), getRandomGame: () => ipcRenderer.invoke("getRandomGame"), - getHowLongToBeat: (objectId: string, shop: GameShop, title: string) => - ipcRenderer.invoke("getHowLongToBeat", objectId, shop, title), + getHowLongToBeat: (title: string) => + ipcRenderer.invoke("getHowLongToBeat", title), getGames: (take?: number, skip?: number) => ipcRenderer.invoke("getGames", take, skip), searchGameRepacks: (query: string) => diff --git a/src/renderer/src/context/repacks/repacks.context.tsx b/src/renderer/src/context/repacks/repacks.context.tsx index 054df60b..b688793c 100644 --- a/src/renderer/src/context/repacks/repacks.context.tsx +++ b/src/renderer/src/context/repacks/repacks.context.tsx @@ -41,10 +41,12 @@ export function RepacksContextProvider({ children }: RepacksContextProps) { }, []); const indexRepacks = useCallback(() => { + console.log("INDEXING"); setIsIndexingRepacks(true); repacksWorker.postMessage("INDEX_REPACKS"); repacksWorker.onmessage = () => { + console.log("INDEXING COMPLETE"); setIsIndexingRepacks(false); }; }, []); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index fe4d3112..2896abf3 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -58,8 +58,6 @@ declare global { ) => Promise; getRandomGame: () => Promise; getHowLongToBeat: ( - objectId: string, - shop: GameShop, title: string ) => Promise; getGames: (take?: number, skip?: number) => Promise; diff --git a/src/renderer/src/dexie.ts b/src/renderer/src/dexie.ts index 75dc6079..e0e86a7f 100644 --- a/src/renderer/src/dexie.ts +++ b/src/renderer/src/dexie.ts @@ -1,4 +1,4 @@ -import { GameShop } from "@types"; +import type { GameShop, HowLongToBeatCategory } from "@types"; import { Dexie } from "dexie"; export interface GameBackup { @@ -8,16 +8,29 @@ export interface GameBackup { createdAt: Date; } +export interface HowLongToBeatEntry { + id?: number; + objectId: string; + categories: HowLongToBeatCategory[]; + shop: GameShop; + createdAt: Date; + updatedAt: Date; +} + export const db = new Dexie("Hydra"); -db.version(3).stores({ +db.version(4).stores({ repacks: `++id, title, uris, fileSize, uploadDate, downloadSourceId, repacker, createdAt, updatedAt`, downloadSources: `++id, url, name, etag, downloadCount, status, createdAt, updatedAt`, gameBackups: `++id, [shop+objectId], createdAt`, + howLongToBeatEntries: `++id, categories, [shop+objectId], createdAt, updatedAt`, }); export const downloadSourcesTable = db.table("downloadSources"); export const repacksTable = db.table("repacks"); export const gameBackupsTable = db.table("gameBackups"); +export const howLongToBeatEntriesTable = db.table( + "howLongToBeatEntries" +); db.open(); diff --git a/src/renderer/src/pages/game-details/game-details-skeleton.tsx b/src/renderer/src/pages/game-details/game-details-skeleton.tsx index 23f0c6f1..24cfe2cb 100644 --- a/src/renderer/src/pages/game-details/game-details-skeleton.tsx +++ b/src/renderer/src/pages/game-details/game-details-skeleton.tsx @@ -43,23 +43,6 @@ export function GameDetailsSkeleton() {
      - {/*
      -

      HowLongToBeat

      -
      -
        - {Array.from({ length: 3 }).map((_, index) => ( - - ))} -
      */} -
      -

      {t("requirements")}

      -
      + +
      + {children} +
      +
      + ); +} diff --git a/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx b/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx index ffd148e5..d63879f5 100644 --- a/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx +++ b/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx @@ -4,6 +4,7 @@ import type { HowLongToBeatCategory } from "@types"; import { vars } from "@renderer/theme.css"; import * as styles from "./sidebar.css"; +import { SidebarSection } from "../sidebar-section/sidebar-section"; const durationTranslation: Record = { Hours: "hours", @@ -30,41 +31,42 @@ export function HowLongToBeatSection({ return ( -
      -

      HowLongToBeat

      -
      - -
        - {howLongToBeatData - ? howLongToBeatData.map((category) => ( -
      • -

        +

          + {howLongToBeatData + ? howLongToBeatData.map((category) => ( +
        • - {category.title} -

          +

          + {category.title} +

          -

          - {getDuration(category.duration)} -

          +

          + {getDuration(category.duration)} +

          - {category.accuracy !== "00" && ( - - {t("accuracy", { accuracy: category.accuracy })} - - )} -
        • - )) - : Array.from({ length: 4 }).map((_, index) => ( - - ))} -
        + {category.accuracy !== "00" && ( + + {t("accuracy", { accuracy: category.accuracy })} + + )} +
      • + )) + : Array.from({ length: 4 }).map((_, index) => ( + + ))} +
      +
      ); } diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts b/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts index 783e4ffa..c909fba2 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts @@ -3,7 +3,8 @@ import { globalStyle, style } from "@vanilla-extract/css"; import { SPACING_UNIT, vars } from "../../../theme.css"; export const contentSidebar = style({ - borderLeft: `solid 1px ${vars.color.border};`, + borderLeft: `solid 1px ${vars.color.border}`, + backgroundColor: vars.color.darkBackground, width: "100%", height: "100%", "@media": { @@ -18,14 +19,6 @@ export const contentSidebar = style({ }, }); -export const contentSidebarTitle = style({ - height: "72px", - padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`, - display: "flex", - alignItems: "center", - backgroundColor: vars.color.background, -}); - export const requirementButtonContainer = style({ width: "100%", display: "flex", @@ -55,7 +48,7 @@ export const requirementsDetailsSkeleton = style({ export const howLongToBeatCategoriesList = style({ margin: "0", - padding: "16px", + padding: `${SPACING_UNIT * 2}px`, display: "flex", flexDirection: "column", gap: "16px", @@ -65,7 +58,8 @@ export const howLongToBeatCategory = style({ display: "flex", flexDirection: "column", gap: "4px", - backgroundColor: vars.color.background, + background: + "linear-gradient(90deg, transparent 20%, rgb(255 255 255 / 2%) 100%)", borderRadius: "4px", padding: `8px 16px`, border: `solid 1px ${vars.color.border}`, @@ -86,6 +80,8 @@ export const statsSection = style({ gap: `${SPACING_UNIT * 2}px`, padding: `${SPACING_UNIT * 2}px`, justifyContent: "space-between", + transition: "max-height ease 0.5s", + overflow: "hidden", "@media": { "(min-width: 1024px)": { flexDirection: "column", diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index 7b748a1a..5c3cbbc0 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -1,4 +1,4 @@ -import { useContext, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import type { HowLongToBeatCategory, SteamAppDetails } from "@types"; import { useTranslation } from "react-i18next"; import { Button } from "@renderer/components"; @@ -8,9 +8,12 @@ import { gameDetailsContext } from "@renderer/context"; import { useDate, useFormat } from "@renderer/hooks"; import { DownloadIcon, PeopleIcon } from "@primer/octicons-react"; import { SPACING_UNIT } from "@renderer/theme.css"; +import { HowLongToBeatSection } from "./how-long-to-beat-section"; +import { howLongToBeatEntriesTable } from "@renderer/dexie"; +import { SidebarSection } from "../sidebar-section/sidebar-section"; export function Sidebar() { - const [_howLongToBeat, _setHowLongToBeat] = useState<{ + const [howLongToBeat, setHowLongToBeat] = useState<{ isLoading: boolean; data: HowLongToBeatCategory[] | null; }>({ isLoading: true, data: null }); @@ -18,7 +21,7 @@ export function Sidebar() { const [activeRequirement, setActiveRequirement] = useState("minimum"); - const { gameTitle, shopDetails, stats, achievements } = + const { gameTitle, shopDetails, objectId, shop, stats, achievements } = useContext(gameDetailsContext); const { t } = useTranslation("game_details"); @@ -26,28 +29,45 @@ export function Sidebar() { const { numberFormatter } = useFormat(); - // useEffect(() => { - // if (objectId) { - // setHowLongToBeat({ isLoading: true, data: null }); + useEffect(() => { + if (objectId) { + setHowLongToBeat({ isLoading: true, data: null }); - // window.electron - // .getHowLongToBeat(objectId, "steam", gameTitle) - // .then((howLongToBeat) => { - // setHowLongToBeat({ isLoading: false, data: howLongToBeat }); - // }) - // .catch(() => { - // setHowLongToBeat({ isLoading: false, data: null }); - // }); - // } - // }, [objectId, gameTitle]); + howLongToBeatEntriesTable + .where({ shop, objectId }) + .first() + .then(async (cachedHowLongToBeat) => { + if (cachedHowLongToBeat) { + setHowLongToBeat({ + isLoading: false, + data: cachedHowLongToBeat.categories, + }); + } else { + try { + const howLongToBeat = + await window.electron.getHowLongToBeat(gameTitle); + + if (howLongToBeat) { + howLongToBeatEntriesTable.add({ + objectId, + shop: "steam", + createdAt: new Date(), + updatedAt: new Date(), + categories: howLongToBeat, + }); + } + + setHowLongToBeat({ isLoading: false, data: howLongToBeat }); + } catch (err) { + setHowLongToBeat({ isLoading: false, data: null }); + } + } + }); + } + }, [objectId, shop, gameTitle]); return ( ); } diff --git a/src/renderer/src/pages/profile/edit-profile-modal/edit-profile-modal.tsx b/src/renderer/src/pages/profile/edit-profile-modal/edit-profile-modal.tsx index cd43641a..0d86bddc 100644 --- a/src/renderer/src/pages/profile/edit-profile-modal/edit-profile-modal.tsx +++ b/src/renderer/src/pages/profile/edit-profile-modal/edit-profile-modal.tsx @@ -64,6 +64,8 @@ export function EditProfileModal( const { showSuccessToast, showErrorToast } = useToast(); const onSubmit = async (values: FormValues) => { + console.log(values); + return patchUser(values) .then(async () => { await Promise.allSettled([fetchUserDetails(), getUserProfile()]); @@ -118,6 +120,8 @@ export function EditProfileModal( return { imagePath: null }; }); + console.log(imagePath); + onChange(imagePath); } }; diff --git a/src/renderer/src/workers/repacks.worker.ts b/src/renderer/src/workers/repacks.worker.ts index c4660074..6c3aca73 100644 --- a/src/renderer/src/workers/repacks.worker.ts +++ b/src/renderer/src/workers/repacks.worker.ts @@ -3,20 +3,21 @@ import { formatName } from "@shared"; import { GameRepack } from "@types"; import flexSearch from "flexsearch"; -const index = new flexSearch.Index(); - interface SerializedGameRepack extends Omit { uris: string; } const state = { repacks: [] as SerializedGameRepack[], + index: null as flexSearch.Index | null, }; self.onmessage = async ( event: MessageEvent<[string, string] | "INDEX_REPACKS"> ) => { if (event.data === "INDEX_REPACKS") { + state.index = new flexSearch.Index(); + repacksTable .toCollection() .sortBy("uploadDate") @@ -26,7 +27,7 @@ self.onmessage = async ( for (let i = 0; i < state.repacks.length; i++) { const repack = state.repacks[i]; const formattedTitle = formatName(repack.title); - index.add(i, formattedTitle); + state.index!.add(i, formattedTitle); } self.postMessage("INDEXING_COMPLETE"); @@ -34,7 +35,7 @@ self.onmessage = async ( } else { const [requestId, query] = event.data; - const results = index.search(formatName(query)).map((index) => { + const results = state.index!.search(formatName(query)).map((index) => { const repack = state.repacks.at(index as number) as SerializedGameRepack; return { diff --git a/src/shared/char-map.ts b/src/shared/char-map.ts new file mode 100644 index 00000000..7f29509e --- /dev/null +++ b/src/shared/char-map.ts @@ -0,0 +1,461 @@ +export const charMap = { + À: "A", + Á: "A", + Â: "A", + Ã: "A", + Ä: "A", + Å: "A", + Ấ: "A", + Ắ: "A", + Ẳ: "A", + Ẵ: "A", + Ặ: "A", + Æ: "AE", + Ầ: "A", + Ằ: "A", + Ȃ: "A", + Ả: "A", + Ạ: "A", + Ẩ: "A", + Ẫ: "A", + Ậ: "A", + Ç: "C", + Ḉ: "C", + È: "E", + É: "E", + Ê: "E", + Ë: "E", + Ế: "E", + Ḗ: "E", + Ề: "E", + Ḕ: "E", + Ḝ: "E", + Ȇ: "E", + Ẻ: "E", + Ẽ: "E", + Ẹ: "E", + Ể: "E", + Ễ: "E", + Ệ: "E", + Ì: "I", + Í: "I", + Î: "I", + Ï: "I", + Ḯ: "I", + Ȋ: "I", + Ỉ: "I", + Ị: "I", + Ð: "D", + Ñ: "N", + Ò: "O", + Ó: "O", + Ô: "O", + Õ: "O", + Ö: "O", + Ø: "O", + Ố: "O", + Ṍ: "O", + Ṓ: "O", + Ȏ: "O", + Ỏ: "O", + Ọ: "O", + Ổ: "O", + Ỗ: "O", + Ộ: "O", + Ờ: "O", + Ở: "O", + Ỡ: "O", + Ớ: "O", + Ợ: "O", + Ù: "U", + Ú: "U", + Û: "U", + Ü: "U", + Ủ: "U", + Ụ: "U", + Ử: "U", + Ữ: "U", + Ự: "U", + Ý: "Y", + à: "a", + á: "a", + â: "a", + ã: "a", + ä: "a", + å: "a", + ấ: "a", + ắ: "a", + ẳ: "a", + ẵ: "a", + ặ: "a", + æ: "ae", + ầ: "a", + ằ: "a", + ȃ: "a", + ả: "a", + ạ: "a", + ẩ: "a", + ẫ: "a", + ậ: "a", + ç: "c", + ḉ: "c", + è: "e", + é: "e", + ê: "e", + ë: "e", + ế: "e", + ḗ: "e", + ề: "e", + ḕ: "e", + ḝ: "e", + ȇ: "e", + ẻ: "e", + ẽ: "e", + ẹ: "e", + ể: "e", + ễ: "e", + ệ: "e", + ì: "i", + í: "i", + î: "i", + ï: "i", + ḯ: "i", + ȋ: "i", + ỉ: "i", + ị: "i", + ð: "d", + ñ: "n", + ò: "o", + ó: "o", + ô: "o", + õ: "o", + ö: "o", + ø: "o", + ố: "o", + ṍ: "o", + ṓ: "o", + ȏ: "o", + ỏ: "o", + ọ: "o", + ổ: "o", + ỗ: "o", + ộ: "o", + ờ: "o", + ở: "o", + ỡ: "o", + ớ: "o", + ợ: "o", + ù: "u", + ú: "u", + û: "u", + ü: "u", + ủ: "u", + ụ: "u", + ử: "u", + ữ: "u", + ự: "u", + ý: "y", + ÿ: "y", + Ā: "A", + ā: "a", + Ă: "A", + ă: "a", + Ą: "A", + ą: "a", + Ć: "C", + ć: "c", + Ĉ: "C", + ĉ: "c", + Ċ: "C", + ċ: "c", + Č: "C", + č: "c", + C̆: "C", + c̆: "c", + Ď: "D", + ď: "d", + Đ: "D", + đ: "d", + Ē: "E", + ē: "e", + Ĕ: "E", + ĕ: "e", + Ė: "E", + ė: "e", + Ę: "E", + ę: "e", + Ě: "E", + ě: "e", + Ĝ: "G", + Ǵ: "G", + ĝ: "g", + ǵ: "g", + Ğ: "G", + ğ: "g", + Ġ: "G", + ġ: "g", + Ģ: "G", + ģ: "g", + Ĥ: "H", + ĥ: "h", + Ħ: "H", + ħ: "h", + Ḫ: "H", + ḫ: "h", + Ĩ: "I", + ĩ: "i", + Ī: "I", + ī: "i", + Ĭ: "I", + ĭ: "i", + Į: "I", + į: "i", + İ: "I", + ı: "i", + IJ: "IJ", + ij: "ij", + Ĵ: "J", + ĵ: "j", + Ķ: "K", + ķ: "k", + Ḱ: "K", + ḱ: "k", + K̆: "K", + k̆: "k", + Ĺ: "L", + ĺ: "l", + Ļ: "L", + ļ: "l", + Ľ: "L", + ľ: "l", + Ŀ: "L", + ŀ: "l", + Ł: "l", + ł: "l", + Ḿ: "M", + ḿ: "m", + M̆: "M", + m̆: "m", + Ń: "N", + ń: "n", + Ņ: "N", + ņ: "n", + Ň: "N", + ň: "n", + ʼn: "n", + N̆: "N", + n̆: "n", + Ō: "O", + ō: "o", + Ŏ: "O", + ŏ: "o", + Ő: "O", + ő: "o", + Œ: "OE", + œ: "oe", + P̆: "P", + p̆: "p", + Ŕ: "R", + ŕ: "r", + Ŗ: "R", + ŗ: "r", + Ř: "R", + ř: "r", + R̆: "R", + r̆: "r", + Ȓ: "R", + ȓ: "r", + Ś: "S", + ś: "s", + Ŝ: "S", + ŝ: "s", + Ş: "S", + Ș: "S", + ș: "s", + ş: "s", + Š: "S", + š: "s", + Ţ: "T", + ţ: "t", + ț: "t", + Ț: "T", + Ť: "T", + ť: "t", + Ŧ: "T", + ŧ: "t", + T̆: "T", + t̆: "t", + Ũ: "U", + ũ: "u", + Ū: "U", + ū: "u", + Ŭ: "U", + ŭ: "u", + Ů: "U", + ů: "u", + Ű: "U", + ű: "u", + Ų: "U", + ų: "u", + Ȗ: "U", + ȗ: "u", + V̆: "V", + v̆: "v", + Ŵ: "W", + ŵ: "w", + Ẃ: "W", + ẃ: "w", + X̆: "X", + x̆: "x", + Ŷ: "Y", + ŷ: "y", + Ÿ: "Y", + Y̆: "Y", + y̆: "y", + Ź: "Z", + ź: "z", + Ż: "Z", + ż: "z", + Ž: "Z", + ž: "z", + ſ: "s", + ƒ: "f", + Ơ: "O", + ơ: "o", + Ư: "U", + ư: "u", + Ǎ: "A", + ǎ: "a", + Ǐ: "I", + ǐ: "i", + Ǒ: "O", + ǒ: "o", + Ǔ: "U", + ǔ: "u", + Ǖ: "U", + ǖ: "u", + Ǘ: "U", + ǘ: "u", + Ǚ: "U", + ǚ: "u", + Ǜ: "U", + ǜ: "u", + Ứ: "U", + ứ: "u", + Ṹ: "U", + ṹ: "u", + Ǻ: "A", + ǻ: "a", + Ǽ: "AE", + ǽ: "ae", + Ǿ: "O", + ǿ: "o", + Þ: "TH", + þ: "th", + Ṕ: "P", + ṕ: "p", + Ṥ: "S", + ṥ: "s", + X́: "X", + x́: "x", + Ѓ: "Г", + ѓ: "г", + Ќ: "К", + ќ: "к", + A̋: "A", + a̋: "a", + E̋: "E", + e̋: "e", + I̋: "I", + i̋: "i", + Ǹ: "N", + ǹ: "n", + Ồ: "O", + ồ: "o", + Ṑ: "O", + ṑ: "o", + Ừ: "U", + ừ: "u", + Ẁ: "W", + ẁ: "w", + Ỳ: "Y", + ỳ: "y", + Ȁ: "A", + ȁ: "a", + Ȅ: "E", + ȅ: "e", + Ȉ: "I", + ȉ: "i", + Ȍ: "O", + ȍ: "o", + Ȑ: "R", + ȑ: "r", + Ȕ: "U", + ȕ: "u", + B̌: "B", + b̌: "b", + Č̣: "C", + č̣: "c", + Ê̌: "E", + ê̌: "e", + F̌: "F", + f̌: "f", + Ǧ: "G", + ǧ: "g", + Ȟ: "H", + ȟ: "h", + J̌: "J", + ǰ: "j", + Ǩ: "K", + ǩ: "k", + M̌: "M", + m̌: "m", + P̌: "P", + p̌: "p", + Q̌: "Q", + q̌: "q", + Ř̩: "R", + ř̩: "r", + Ṧ: "S", + ṧ: "s", + V̌: "V", + v̌: "v", + W̌: "W", + w̌: "w", + X̌: "X", + x̌: "x", + Y̌: "Y", + y̌: "y", + A̧: "A", + a̧: "a", + B̧: "B", + b̧: "b", + Ḑ: "D", + ḑ: "d", + Ȩ: "E", + ȩ: "e", + Ɛ̧: "E", + ɛ̧: "e", + Ḩ: "H", + ḩ: "h", + I̧: "I", + i̧: "i", + Ɨ̧: "I", + ɨ̧: "i", + M̧: "M", + m̧: "m", + O̧: "O", + o̧: "o", + Q̧: "Q", + q̧: "q", + U̧: "U", + u̧: "u", + X̧: "X", + x̧: "x", + Z̧: "Z", + z̧: "z", + й: "и", + Й: "И", + ё: "е", + Ё: "Е", +}; diff --git a/src/shared/index.ts b/src/shared/index.ts index ec3fe9d9..5d216183 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -1,3 +1,4 @@ +import { charMap } from "./char-map"; import { Downloader } from "./constants"; export * from "./constants"; @@ -51,6 +52,12 @@ export const replaceUnderscoreWithSpace = (name: string) => name.replace(/_/g, " "); export const formatName = pipe( + (str) => + str.replace( + new RegExp(Object.keys(charMap).join("|"), "g"), + (match) => charMap[match] + ), + (str) => str.toLowerCase(), removeReleaseYearFromName, removeSpecialEditionFromName, replaceUnderscoreWithSpace, diff --git a/src/types/howlongtobeat.types.ts b/src/types/howlongtobeat.types.ts new file mode 100644 index 00000000..1ab7ee34 --- /dev/null +++ b/src/types/howlongtobeat.types.ts @@ -0,0 +1,14 @@ +export interface HowLongToBeatCategory { + title: string; + duration: string; + accuracy: string; +} + +export interface HowLongToBeatResult { + game_id: number; + game_name: string; +} + +export interface HowLongToBeatSearchResponse { + data: HowLongToBeatResult[]; +} diff --git a/src/types/index.ts b/src/types/index.ts index 7af2aeca..303d47ae 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -130,12 +130,6 @@ export interface UserPreferences { runAtStartup: boolean; } -export interface HowLongToBeatCategory { - title: string; - duration: string; - accuracy: string; -} - export interface Steam250Game { title: string; objectId: string; @@ -304,3 +298,4 @@ export interface GameArtifact { export * from "./steam.types"; export * from "./real-debrid.types"; export * from "./ludusavi.types"; +export * from "./howlongtobeat.types"; From 75be6e5b473d295cf7788d04a45c240a102bfee8 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Mon, 7 Oct 2024 21:02:15 +0100 Subject: [PATCH 22/22] feat: adding hltb key extraction --- src/main/events/profile/update-profile.ts | 4 +-- src/main/services/window-manager.ts | 40 +++++++++++------------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/main/events/profile/update-profile.ts b/src/main/events/profile/update-profile.ts index b1244684..eb80bc47 100644 --- a/src/main/events/profile/update-profile.ts +++ b/src/main/events/profile/update-profile.ts @@ -48,9 +48,7 @@ const updateProfile = async ( const profileImageUrl = await getNewProfileImageUrl( updateProfile.profileImageUrl - ).catch((err) => { - console.log(err); - }); + ).catch(() => undefined); return patchUserProfile({ ...updateProfile, profileImageUrl }); }; diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 164ccf9c..eaa05f7a 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -148,29 +148,29 @@ export class WindowManager { } public static createNotificationWindow() { - // this.notificationWindow = new BrowserWindow({ - // transparent: true, - // maximizable: false, - // autoHideMenuBar: true, - // minimizable: false, - // focusable: false, - // skipTaskbar: true, - // frame: false, - // width: 350, - // height: 104, - // x: 0, - // y: 0, - // webPreferences: { - // preload: path.join(__dirname, "../preload/index.mjs"), - // sandbox: false, - // }, - // }); - // this.notificationWindow.setIgnoreMouseEvents(true); + this.notificationWindow = new BrowserWindow({ + transparent: true, + maximizable: false, + autoHideMenuBar: true, + minimizable: false, + focusable: false, + skipTaskbar: true, + frame: false, + width: 350, + height: 104, + x: 0, + y: 0, + webPreferences: { + preload: path.join(__dirname, "../preload/index.mjs"), + sandbox: false, + }, + }); + this.notificationWindow.setIgnoreMouseEvents(true); // this.notificationWindow.setVisibleOnAllWorkspaces(true, { // visibleOnFullScreen: true, // }); - // this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); - // this.loadNotificationWindowURL(); + this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); + this.loadNotificationWindowURL(); } public static openAuthWindow() {