feat: adding toast component

This commit is contained in:
Chubby Granny Chaser 2024-05-22 00:12:57 +01:00
parent da607fe741
commit 0162ebd133
No known key found for this signature in database
13 changed files with 136 additions and 82 deletions

View file

@ -120,7 +120,7 @@
"verifying": "Verifying…", "verifying": "Verifying…",
"completed_at": "Completed in {{date}}", "completed_at": "Completed in {{date}}",
"completed": "Completed", "completed": "Completed",
"cancelled": "Cancelled", "removed": "Removed",
"download_again": "Download again", "download_again": "Download again",
"cancel": "Cancel", "cancel": "Cancel",
"filter": "Filter downloaded games", "filter": "Filter downloaded games",

View file

@ -116,7 +116,7 @@
"verifying": "Verificando…", "verifying": "Verificando…",
"completed_at": "Concluído em {{date}}", "completed_at": "Concluído em {{date}}",
"completed": "Concluído", "completed": "Concluído",
"cancelled": "Cancelado", "removed": "Removido",
"download_again": "Baixar novamente", "download_again": "Baixar novamente",
"cancel": "Cancelar", "cancel": "Cancelar",
"filter": "Filtrar jogos baixados", "filter": "Filtrar jogos baixados",

View file

@ -1,7 +1,11 @@
import { Not } from "typeorm";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gameRepository } from "../../repository"; import { gameRepository } from "../../repository";
import { DownloadManager } from "@main/services"; import { DownloadManager } from "@main/services";
import { dataSource } from "@main/data-source";
import { Game } from "@main/entity";
const resumeGameDownload = async ( const resumeGameDownload = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
@ -18,11 +22,19 @@ const resumeGameDownload = async (
if (!game) return; if (!game) return;
if (game.status === "paused") { if (game.status === "paused") {
await DownloadManager.pauseDownload(); await dataSource.transaction(async (transactionalEntityManager) => {
await DownloadManager.pauseDownload();
await gameRepository.update({ id: gameId }, { status: "active" }); await transactionalEntityManager
.getRepository(Game)
.update({ status: "active", progress: Not(1) }, { status: "paused" });
await DownloadManager.resumeDownload(gameId); await DownloadManager.resumeDownload(gameId);
await transactionalEntityManager
.getRepository(Game)
.update({ id: gameId }, { status: "active" });
});
} }
}; };

View file

@ -44,6 +44,8 @@ const startGameDownload = async (
if (!repack || game?.status === "active") return; if (!repack || game?.status === "active") return;
await DownloadManager.pauseDownload();
await gameRepository.update( await gameRepository.update(
{ status: "active", progress: Not(1) }, { status: "active", progress: Not(1) },
{ status: "paused" } { status: "paused" }
@ -65,9 +67,7 @@ const startGameDownload = async (
await DownloadManager.startDownload(game.id); await DownloadManager.startDownload(game.id);
game.status = "active"; return { ...game, stauts: "active" };
return game;
} else { } else {
const steamGame = stateManager const steamGame = stateManager
.getValue("steamGames") .getValue("steamGames")
@ -98,11 +98,9 @@ const startGameDownload = async (
return result; return result;
}); });
DownloadManager.startDownload(createdGame.id); await DownloadManager.startDownload(createdGame.id);
const { repack: _, ...rest } = createdGame; return createdGame;
return rest;
} }
}; };

View file

@ -30,7 +30,16 @@ export class DownloadManager {
"aria2c" "aria2c"
); );
spawn(binary, ["--enable-rpc", "--rpc-listen-all"], { stdio: "inherit" }); spawn(
binary,
[
"--enable-rpc",
"--rpc-listen-all",
"--file-allocation=none",
"--allow-overwrite=true",
],
{ stdio: "inherit" }
);
await this.aria2.open(); await this.aria2.open();
this.attachListener(); this.attachListener();
@ -76,6 +85,7 @@ export class DownloadManager {
} }
private static async attachListener() { private static async attachListener() {
// eslint-disable-next-line no-constant-condition
while (true) { while (true) {
try { try {
if (!this.gid || !this.gameId) { if (!this.gid || !this.gameId) {
@ -205,8 +215,6 @@ export class DownloadManager {
} }
static async resumeDownload(gameId: number) { static async resumeDownload(gameId: number) {
await this.aria2.call("forcePauseAll");
if (this.downloads.has(gameId)) { if (this.downloads.has(gameId)) {
const gid = this.downloads.get(gameId)!; const gid = this.downloads.get(gameId)!;
await this.aria2.call("unpause", gid); await this.aria2.call("unpause", gid);
@ -219,8 +227,6 @@ export class DownloadManager {
} }
static async startDownload(gameId: number) { static async startDownload(gameId: number) {
await this.aria2.call("forcePauseAll");
const game = await this.getGame(gameId)!; const game = await this.getGame(gameId)!;
if (game) { if (game) {

View file

@ -79,6 +79,10 @@ globalStyle("img", {
WebkitUserDrag: "none", WebkitUserDrag: "none",
} as Record<string, string>); } as Record<string, string>);
globalStyle("progress[value]", {
WebkitAppearance: "none",
});
export const container = style({ export const container = style({
width: "100%", width: "100%",
height: "100%", height: "100%",

View file

@ -2,14 +2,14 @@ import { keyframes, style } from "@vanilla-extract/css";
import { recipe } from "@vanilla-extract/recipes"; import { recipe } from "@vanilla-extract/recipes";
import { SPACING_UNIT, vars } from "../../theme.css"; import { SPACING_UNIT, vars } from "../../theme.css";
export const modalSlideIn = keyframes({ export const fadeIn = keyframes({
"0%": { opacity: 0 }, "0%": { opacity: 0 },
"100%": { "100%": {
opacity: 1, opacity: 1,
}, },
}); });
export const modalSlideOut = keyframes({ export const fadeOut = keyframes({
"0%": { opacity: 1 }, "0%": { opacity: 1 },
"100%": { "100%": {
opacity: 0, opacity: 0,
@ -18,7 +18,7 @@ export const modalSlideOut = keyframes({
export const modal = recipe({ export const modal = recipe({
base: { base: {
animationName: modalSlideIn, animationName: fadeIn,
animationDuration: "0.3s", animationDuration: "0.3s",
backgroundColor: vars.color.background, backgroundColor: vars.color.background,
borderRadius: "5px", borderRadius: "5px",
@ -33,7 +33,7 @@ export const modal = recipe({
variants: { variants: {
closing: { closing: {
true: { true: {
animationName: modalSlideOut, animationName: fadeOut,
opacity: 0, opacity: 0,
}, },
}, },

View file

@ -27,6 +27,7 @@ export const content = recipe({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
padding: `${SPACING_UNIT * 2}px`, padding: `${SPACING_UNIT * 2}px`,
gap: `${SPACING_UNIT * 2}px`,
paddingBottom: "0", paddingBottom: "0",
width: "100%", width: "100%",
overflow: "auto", overflow: "auto",
@ -118,7 +119,6 @@ export const sectionTitle = style({
}); });
export const section = style({ export const section = style({
padding: `${SPACING_UNIT * 2}px 0`,
gap: `${SPACING_UNIT * 2}px`, gap: `${SPACING_UNIT * 2}px`,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",

View file

@ -29,7 +29,6 @@ export const downloaderName = style({
borderRadius: "4px", borderRadius: "4px",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
alignSelf: "flex-start",
}); });
export const downloads = style({ export const downloads = style({
@ -46,9 +45,34 @@ export const downloadCover = style({
width: "280px", width: "280px",
minWidth: "280px", minWidth: "280px",
height: "auto", height: "auto",
objectFit: "cover",
objectPosition: "center",
borderRight: `solid 1px ${vars.color.border}`, borderRight: `solid 1px ${vars.color.border}`,
position: "relative",
zIndex: "1",
});
export const downloadCoverContent = style({
width: "100%",
height: "100%",
padding: `${SPACING_UNIT}px`,
display: "flex",
alignItems: "flex-end",
justifyContent: "flex-end",
});
export const downloadCoverBackdrop = style({
width: "100%",
height: "100%",
background: "linear-gradient(0deg, rgba(0, 0, 0, 0.8) 5%, transparent 100%)",
display: "flex",
overflow: "hidden",
zIndex: "1",
});
export const downloadCoverImage = style({
width: "100%",
height: "100%",
position: "absolute",
zIndex: "-1",
}); });
export const download = recipe({ export const download = recipe({

View file

@ -2,7 +2,11 @@ import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Button, TextField } from "@renderer/components"; import { Button, TextField } from "@renderer/components";
import { formatDownloadProgress, steamUrlBuilder } from "@renderer/helpers"; import {
buildGameDetailsPath,
formatDownloadProgress,
steamUrlBuilder,
} from "@renderer/helpers";
import { useDownload, useLibrary } from "@renderer/hooks"; import { useDownload, useLibrary } from "@renderer/hooks";
import type { Game } from "@types"; import type { Game } from "@types";
@ -86,9 +90,9 @@ export function Downloads() {
</p> </p>
{game.downloader === Downloader.Torrent && ( {game.downloader === Downloader.Torrent && (
<p> <small>
{lastPacket?.numPeers} peers / {lastPacket?.numSeeds} seeds {lastPacket?.numPeers} peers / {lastPacket?.numSeeds} seeds
</p> </small>
)} )}
</> </>
); );
@ -102,7 +106,6 @@ export function Downloads() {
</> </>
); );
} }
if (game?.status === "removed") return <p>{t("cancelled")}</p>;
if (game?.status === "paused") { if (game?.status === "paused") {
return ( return (
@ -113,7 +116,7 @@ export function Downloads() {
); );
} }
return null; return <p>{t(game?.status)}</p>;
}; };
const openDeleteModal = (gameId: number) => { const openDeleteModal = (gameId: number) => {
@ -173,7 +176,7 @@ export function Downloads() {
return ( return (
<> <>
<Button <Button
onClick={() => navigate(`/game/${game.shop}/${game.objectID}`)} onClick={() => navigate(buildGameDetailsPath(game))}
theme="outline" theme="outline"
disabled={deleting} disabled={deleting}
> >
@ -230,11 +233,21 @@ export function Downloads() {
cancelled: game.status === "removed", cancelled: game.status === "removed",
})} })}
> >
<img <div className={styles.downloadCover}>
src={steamUrlBuilder.library(game.objectID)} <div className={styles.downloadCoverBackdrop}>
className={styles.downloadCover} <img
alt={game.title} src={steamUrlBuilder.library(game.objectID)}
/> className={styles.downloadCoverImage}
alt={game.title}
/>
<div className={styles.downloadCoverContent}>
<small className={styles.downloaderName}>
{downloaderName[game?.downloader]}
</small>
</div>
</div>
</div>
<div className={styles.downloadRightContent}> <div className={styles.downloadRightContent}>
<div className={styles.downloadDetails}> <div className={styles.downloadDetails}>
<div className={styles.downloadTitleWrapper}> <div className={styles.downloadTitleWrapper}>
@ -249,9 +262,6 @@ export function Downloads() {
</button> </button>
</div> </div>
<small className={styles.downloaderName}>
{downloaderName[game?.downloader]}
</small>
{getGameInfo(game)} {getGameInfo(game)}
</div> </div>

View file

@ -2,7 +2,7 @@ import { globalStyle, keyframes, style } from "@vanilla-extract/css";
import { SPACING_UNIT, vars } from "../../theme.css"; import { SPACING_UNIT, vars } from "../../theme.css";
export const slideIn = keyframes({ export const slideIn = keyframes({
"0%": { transform: `translateY(${40 + 16}px)` }, "0%": { transform: `translateY(${40 + SPACING_UNIT * 2}px)` },
"100%": { transform: "translateY(0)" }, "100%": { transform: "translateY(0)" },
}); });

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Button } from "@renderer/components"; import { Button } from "@renderer/components";
import * as styles from "./settings.css"; import * as styles from "./settings.css";
@ -7,6 +7,7 @@ import { UserPreferences } from "@types";
import { SettingsRealDebrid } from "./settings-real-debrid"; import { SettingsRealDebrid } from "./settings-real-debrid";
import { SettingsGeneral } from "./settings-general"; import { SettingsGeneral } from "./settings-general";
import { SettingsBehavior } from "./settings-behavior"; import { SettingsBehavior } from "./settings-behavior";
import { Toast } from "@renderer/components/toast/toast";
const categories = ["general", "behavior", "real_debrid"]; const categories = ["general", "behavior", "real_debrid"];
@ -14,6 +15,7 @@ export function Settings() {
const [currentCategory, setCurrentCategory] = useState(categories.at(0)!); const [currentCategory, setCurrentCategory] = useState(categories.at(0)!);
const [userPreferences, setUserPreferences] = const [userPreferences, setUserPreferences] =
useState<UserPreferences | null>(null); useState<UserPreferences | null>(null);
const [isToastVisible, setIsToastVisible] = useState(false);
const { t } = useTranslation("settings"); const { t } = useTranslation("settings");
@ -23,8 +25,14 @@ export function Settings() {
}); });
}, []); }, []);
const handleUpdateUserPreferences = (values: Partial<UserPreferences>) => { const handleUpdateUserPreferences = async (
window.electron.updateUserPreferences(values); values: Partial<UserPreferences>
) => {
await window.electron.updateUserPreferences(values);
window.electron.getUserPreferences().then((userPreferences) => {
setUserPreferences(userPreferences);
setIsToastVisible(true);
});
}; };
const renderCategory = () => { const renderCategory = () => {
@ -54,24 +62,37 @@ export function Settings() {
); );
}; };
return ( const handleToastClose = useCallback(() => {
<section className={styles.container}> setIsToastVisible(false);
<div className={styles.content}> }, []);
<section className={styles.settingsCategories}>
{categories.map((category) => (
<Button
key={category}
theme={currentCategory === category ? "primary" : "outline"}
onClick={() => setCurrentCategory(category)}
>
{t(category)}
</Button>
))}
</section>
<h2>{t(currentCategory)}</h2> return (
{renderCategory()} <>
</div> <section className={styles.container}>
</section> <div className={styles.content}>
<section className={styles.settingsCategories}>
{categories.map((category) => (
<Button
key={category}
theme={currentCategory === category ? "primary" : "outline"}
onClick={() => setCurrentCategory(category)}
>
{t(category)}
</Button>
))}
</section>
<h2>{t(currentCategory)}</h2>
{renderCategory()}
</div>
</section>
<Toast
message="Settings have been saved"
visible={isToastVisible}
onClose={handleToastClose}
type="success"
/>
</>
); );
} }

View file

@ -1,14 +1,3 @@
export enum GameStatus {
Seeding = "seeding",
Downloading = "downloading",
Paused = "paused",
CheckingFiles = "checking_files",
DownloadingMetadata = "downloading_metadata",
Cancelled = "cancelled",
Decompressing = "decompressing",
Finished = "finished",
}
export enum Downloader { export enum Downloader {
RealDebrid, RealDebrid,
Torrent, Torrent,
@ -29,13 +18,3 @@ export const formatBytes = (bytes: number): string => {
return `${Math.trunc(formatedByte * 10) / 10} ${FORMAT[base]}`; return `${Math.trunc(formatedByte * 10) / 10} ${FORMAT[base]}`;
}; };
export class GameStatusHelper {
public static isDownloading(status: string | null) {
return status === "active";
}
public static isReady(status: string | null) {
return status === "complete";
}
}