mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
fix: fixing errors with electron dl manager
This commit is contained in:
parent
11f1785432
commit
74a99f5bc8
51 changed files with 718 additions and 766 deletions
|
@ -20,6 +20,7 @@ import {
|
|||
setRepackersFriendlyNames,
|
||||
toggleDraggingDisabled,
|
||||
} from "@renderer/features";
|
||||
import { GameStatusHelper } from "@shared";
|
||||
|
||||
document.body.classList.add(themeClass);
|
||||
|
||||
|
@ -57,7 +58,7 @@ export function App({ children }: AppProps) {
|
|||
useEffect(() => {
|
||||
const unsubscribe = window.electron.onDownloadProgress(
|
||||
(downloadProgress) => {
|
||||
if (downloadProgress.game.progress === 1) {
|
||||
if (GameStatusHelper.isReady(downloadProgress.game.status)) {
|
||||
clearDownload();
|
||||
updateLibrary();
|
||||
return;
|
||||
|
|
|
@ -7,14 +7,17 @@ import { vars } from "../../theme.css";
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { VERSION_CODENAME } from "@renderer/constants";
|
||||
import { GameStatus } from "@globals";
|
||||
import { GameStatus, GameStatusHelper } from "@shared";
|
||||
|
||||
export function BottomPanel() {
|
||||
const { t } = useTranslation("bottom_panel");
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { game, progress, downloadSpeed, eta, isDownloading } = useDownload();
|
||||
const { game, progress, downloadSpeed, eta } = useDownload();
|
||||
|
||||
const isGameDownloading =
|
||||
game && GameStatusHelper.isDownloading(game.status ?? null);
|
||||
|
||||
const [version, setVersion] = useState("");
|
||||
|
||||
|
@ -23,7 +26,7 @@ export function BottomPanel() {
|
|||
}, []);
|
||||
|
||||
const status = useMemo(() => {
|
||||
if (isDownloading && game) {
|
||||
if (isGameDownloading) {
|
||||
if (game.status === GameStatus.DownloadingMetadata)
|
||||
return t("downloading_metadata", { title: game.title });
|
||||
|
||||
|
@ -42,13 +45,13 @@ export function BottomPanel() {
|
|||
}
|
||||
|
||||
return t("no_downloads_in_progress");
|
||||
}, [t, game, progress, eta, isDownloading, downloadSpeed]);
|
||||
}, [t, isGameDownloading, game, progress, eta, downloadSpeed]);
|
||||
|
||||
return (
|
||||
<footer
|
||||
className={styles.bottomPanel}
|
||||
style={{
|
||||
background: isDownloading
|
||||
background: isGameDownloading
|
||||
? `linear-gradient(90deg, ${vars.color.background} ${progress}, ${vars.color.darkBackground} ${progress})`
|
||||
: vars.color.darkBackground,
|
||||
}}
|
||||
|
|
|
@ -24,7 +24,7 @@ export function CheckboxField({ label, ...props }: CheckboxFieldProps) {
|
|||
/>
|
||||
{props.checked && <CheckIcon />}
|
||||
</div>
|
||||
<label htmlFor={id} className={styles.checkboxLabel}>
|
||||
<label htmlFor={id} className={styles.checkboxLabel} tabIndex={0}>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,7 @@ import DiscordLogo from "@renderer/assets/discord-icon.svg?react";
|
|||
import XLogo from "@renderer/assets/x-icon.svg?react";
|
||||
|
||||
import * as styles from "./sidebar.css";
|
||||
import { GameStatus } from "@globals";
|
||||
import { GameStatus, GameStatusHelper } from "@shared";
|
||||
|
||||
const SIDEBAR_MIN_WIDTH = 200;
|
||||
const SIDEBAR_INITIAL_WIDTH = 250;
|
||||
|
@ -61,7 +61,7 @@ export function Sidebar() {
|
|||
}, [gameDownloading?.id, updateLibrary]);
|
||||
|
||||
const isDownloading = library.some((game) =>
|
||||
GameStatus.isDownloading(game.status)
|
||||
GameStatusHelper.isDownloading(game.status)
|
||||
);
|
||||
|
||||
const sidebarRef = useRef<HTMLElement>(null);
|
||||
|
@ -124,7 +124,7 @@ export function Sidebar() {
|
|||
return t("paused", { title: game.title });
|
||||
|
||||
if (gameDownloading?.id === game.id) {
|
||||
const isVerifying = GameStatus.isVerifying(gameDownloading.status);
|
||||
const isVerifying = GameStatusHelper.isVerifying(gameDownloading.status);
|
||||
|
||||
if (isVerifying)
|
||||
return t(gameDownloading.status!, {
|
||||
|
|
|
@ -27,7 +27,7 @@ export function TextField({
|
|||
return (
|
||||
<div style={{ flex: 1 }}>
|
||||
{label && (
|
||||
<label htmlFor={id} className={styles.label}>
|
||||
<label htmlFor={id} className={styles.label} tabIndex={0}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./use-download";
|
||||
export * from "./use-library";
|
||||
export * from "./use-date";
|
||||
export * from "./redux";
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
import type { GameShop, TorrentProgress } from "@types";
|
||||
import { useDate } from "./use-date";
|
||||
import { formatBytes } from "@renderer/utils";
|
||||
import { GameStatus } from "@globals";
|
||||
import { GameStatus, GameStatusHelper } from "@shared";
|
||||
|
||||
export function useDownload() {
|
||||
const { updateLibrary } = useLibrary();
|
||||
|
@ -64,7 +64,9 @@ export function useDownload() {
|
|||
updateLibrary();
|
||||
});
|
||||
|
||||
const isVerifying = GameStatus.isVerifying(lastPacket?.game.status);
|
||||
const isVerifying = GameStatusHelper.isVerifying(
|
||||
lastPacket?.game.status ?? null
|
||||
);
|
||||
|
||||
const getETA = () => {
|
||||
if (isVerifying || !isFinite(lastPacket?.timeRemaining ?? 0)) {
|
||||
|
@ -85,8 +87,6 @@ export function useDownload() {
|
|||
const getProgress = () => {
|
||||
if (lastPacket?.game.status === GameStatus.CheckingFiles) {
|
||||
return formatDownloadProgress(lastPacket?.game.fileVerificationProgress);
|
||||
} else if (lastPacket?.game.status === GameStatus.Decompressing) {
|
||||
return formatDownloadProgress(lastPacket?.game.decompressionProgress);
|
||||
}
|
||||
|
||||
return formatDownloadProgress(lastPacket?.game.progress);
|
||||
|
@ -116,7 +116,6 @@ export function useDownload() {
|
|||
isVerifying,
|
||||
gameId: lastPacket?.game.id,
|
||||
downloadSpeed: `${formatBytes(lastPacket?.downloadSpeed ?? 0)}/s`,
|
||||
isDownloading: Boolean(lastPacket),
|
||||
progress: getProgress(),
|
||||
numPeers: lastPacket?.numPeers,
|
||||
numSeeds: lastPacket?.numSeeds,
|
||||
|
|
|
@ -11,7 +11,7 @@ import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal";
|
|||
import * as styles from "./downloads.css";
|
||||
import { DeleteModal } from "./delete-modal";
|
||||
import { formatBytes } from "@renderer/utils";
|
||||
import { GameStatus } from "@globals";
|
||||
import { Downloader, GameStatus, GameStatusHelper } from "@shared";
|
||||
|
||||
export function Downloads() {
|
||||
const { library, updateLibrary } = useLibrary();
|
||||
|
@ -29,7 +29,6 @@ export function Downloads() {
|
|||
const {
|
||||
game: gameDownloading,
|
||||
progress,
|
||||
isDownloading,
|
||||
numPeers,
|
||||
numSeeds,
|
||||
pauseDownload,
|
||||
|
@ -55,7 +54,7 @@ export function Downloads() {
|
|||
});
|
||||
|
||||
const getFinalDownloadSize = (game: Game) => {
|
||||
const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;
|
||||
const isGameDownloading = gameDownloading?.id === game?.id;
|
||||
|
||||
if (!game) return "N/A";
|
||||
if (game.fileSize) return formatBytes(game.fileSize);
|
||||
|
@ -67,7 +66,7 @@ export function Downloads() {
|
|||
};
|
||||
|
||||
const getGameInfo = (game: Game) => {
|
||||
const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;
|
||||
const isGameDownloading = gameDownloading?.id === game?.id;
|
||||
const finalDownloadSize = getFinalDownloadSize(game);
|
||||
|
||||
if (isGameDeleting(game?.id)) {
|
||||
|
@ -79,7 +78,8 @@ export function Downloads() {
|
|||
<>
|
||||
<p>{progress}</p>
|
||||
|
||||
{gameDownloading?.status !== GameStatus.Downloading ? (
|
||||
{gameDownloading?.status &&
|
||||
gameDownloading?.status !== GameStatus.Downloading ? (
|
||||
<p>{t(gameDownloading?.status)}</p>
|
||||
) : (
|
||||
<>
|
||||
|
@ -87,16 +87,18 @@ export function Downloads() {
|
|||
{formatBytes(gameDownloading?.bytesDownloaded)} /{" "}
|
||||
{finalDownloadSize}
|
||||
</p>
|
||||
<p>
|
||||
{numPeers} peers / {numSeeds} seeds
|
||||
</p>
|
||||
{game.downloader === Downloader.Torrent && (
|
||||
<p>
|
||||
{numPeers} peers / {numSeeds} seeds
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (GameStatus.isReady(game?.status)) {
|
||||
if (GameStatusHelper.isReady(game?.status)) {
|
||||
return (
|
||||
<>
|
||||
<p>{game?.repack.title}</p>
|
||||
|
@ -126,7 +128,7 @@ export function Downloads() {
|
|||
};
|
||||
|
||||
const getGameActions = (game: Game) => {
|
||||
const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;
|
||||
const isGameDownloading = gameDownloading?.id === game?.id;
|
||||
|
||||
const deleting = isGameDeleting(game.id);
|
||||
|
||||
|
@ -156,7 +158,7 @@ export function Downloads() {
|
|||
);
|
||||
}
|
||||
|
||||
if (GameStatus.isReady(game?.status)) {
|
||||
if (GameStatusHelper.isReady(game?.status)) {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
|
|
|
@ -67,7 +67,7 @@ export function GameDetails() {
|
|||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { game: gameDownloading, startDownload, isDownloading } = useDownload();
|
||||
const { game: gameDownloading, startDownload } = useDownload();
|
||||
|
||||
const heroImage = steamUrlBuilder.libraryHero(objectID!);
|
||||
|
||||
|
@ -122,7 +122,7 @@ export function GameDetails() {
|
|||
setHowLongToBeat({ isLoading: true, data: null });
|
||||
}, [getGame, dispatch, navigate, objectID, i18n.language]);
|
||||
|
||||
const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;
|
||||
const isGameDownloading = gameDownloading?.id === game?.id;
|
||||
|
||||
useEffect(() => {
|
||||
if (isGameDownloading)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { GameStatus } from "@globals";
|
||||
import { GameStatus, GameStatusHelper } from "@shared";
|
||||
import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react";
|
||||
|
||||
import { Button } from "@renderer/components";
|
||||
|
@ -174,10 +174,13 @@ export function HeroPanelActions({
|
|||
);
|
||||
}
|
||||
|
||||
if (GameStatus.isReady(game?.status) || (game && !game.status)) {
|
||||
if (
|
||||
GameStatusHelper.isReady(game?.status ?? null) ||
|
||||
(game && !game.status)
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
{GameStatus.isReady(game?.status) ? (
|
||||
{GameStatusHelper.isReady(game?.status ?? null) ? (
|
||||
<Button
|
||||
onClick={openGameInstaller}
|
||||
theme="outline"
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { Game } from "@types";
|
||||
import { useDate } from "@renderer/hooks";
|
||||
|
||||
const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
|
||||
|
||||
export interface HeroPanelPlaytimeProps {
|
||||
game: Game;
|
||||
isGamePlaying: boolean;
|
||||
}
|
||||
|
||||
export function HeroPanelPlaytime({
|
||||
game,
|
||||
isGamePlaying,
|
||||
}: HeroPanelPlaytimeProps) {
|
||||
const [lastTimePlayed, setLastTimePlayed] = useState("");
|
||||
|
||||
const { i18n, t } = useTranslation("game_details");
|
||||
|
||||
const { formatDistance } = useDate();
|
||||
|
||||
useEffect(() => {
|
||||
if (game?.lastTimePlayed) {
|
||||
setLastTimePlayed(
|
||||
formatDistance(game.lastTimePlayed, new Date(), {
|
||||
addSuffix: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [game?.lastTimePlayed, formatDistance]);
|
||||
|
||||
const numberFormatter = useMemo(() => {
|
||||
return new Intl.NumberFormat(i18n.language, {
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
}, [i18n.language]);
|
||||
|
||||
const formatPlayTime = () => {
|
||||
const milliseconds = game?.playTimeInMilliseconds || 0;
|
||||
const seconds = milliseconds / 1000;
|
||||
const minutes = seconds / 60;
|
||||
|
||||
if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) {
|
||||
return t("amount_minutes", {
|
||||
amount: minutes.toFixed(0),
|
||||
});
|
||||
}
|
||||
|
||||
const hours = minutes / 60;
|
||||
return t("amount_hours", { amount: numberFormatter.format(hours) });
|
||||
};
|
||||
|
||||
if (!game.lastTimePlayed) {
|
||||
return <p>{t("not_played_yet", { title: game.title })}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
{t("play_time", {
|
||||
amount: formatPlayTime(),
|
||||
})}
|
||||
</p>
|
||||
|
||||
{isGamePlaying ? (
|
||||
<p>{t("playing_now")}</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("last_time_played", {
|
||||
period: lastTimePlayed,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
import { format } from "date-fns";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useDownload } from "@renderer/hooks";
|
||||
import type { Game, ShopDetails } from "@types";
|
||||
|
||||
import { formatDownloadProgress } from "@renderer/helpers";
|
||||
import { useDate } from "@renderer/hooks/use-date";
|
||||
import { formatBytes } from "@renderer/utils";
|
||||
import { HeroPanelActions } from "./hero-panel-actions";
|
||||
import { GameStatus } from "@globals";
|
||||
import { Downloader, GameStatus, GameStatusHelper } from "@shared";
|
||||
|
||||
import { BinaryNotFoundModal } from "../../shared-modals/binary-not-found-modal";
|
||||
import * as styles from "./hero-panel.css";
|
||||
import { HeroPanelPlaytime } from "./hero-panel-playtime";
|
||||
|
||||
export interface HeroPanelProps {
|
||||
game: Game | null;
|
||||
|
@ -23,8 +23,6 @@ export interface HeroPanelProps {
|
|||
getGame: () => void;
|
||||
}
|
||||
|
||||
const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
|
||||
|
||||
export function HeroPanel({
|
||||
game,
|
||||
gameDetails,
|
||||
|
@ -33,54 +31,22 @@ export function HeroPanel({
|
|||
getGame,
|
||||
isGamePlaying,
|
||||
}: HeroPanelProps) {
|
||||
const { t, i18n } = useTranslation("game_details");
|
||||
const { t } = useTranslation("game_details");
|
||||
|
||||
const [showBinaryNotFoundModal, setShowBinaryNotFoundModal] = useState(false);
|
||||
const [lastTimePlayed, setLastTimePlayed] = useState("");
|
||||
|
||||
const { formatDistance } = useDate();
|
||||
|
||||
const {
|
||||
game: gameDownloading,
|
||||
isDownloading,
|
||||
progress,
|
||||
eta,
|
||||
numPeers,
|
||||
numSeeds,
|
||||
isGameDeleting,
|
||||
} = useDownload();
|
||||
const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;
|
||||
|
||||
useEffect(() => {
|
||||
if (game?.lastTimePlayed) {
|
||||
setLastTimePlayed(
|
||||
formatDistance(game.lastTimePlayed, new Date(), {
|
||||
addSuffix: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [game?.lastTimePlayed, formatDistance]);
|
||||
|
||||
const numberFormatter = useMemo(() => {
|
||||
return new Intl.NumberFormat(i18n.language, {
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
}, [i18n]);
|
||||
|
||||
const formatPlayTime = () => {
|
||||
const milliseconds = game?.playTimeInMilliseconds || 0;
|
||||
const seconds = milliseconds / 1000;
|
||||
const minutes = seconds / 60;
|
||||
|
||||
if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) {
|
||||
return t("amount_minutes", {
|
||||
amount: minutes.toFixed(0),
|
||||
});
|
||||
}
|
||||
|
||||
const hours = minutes / 60;
|
||||
return t("amount_hours", { amount: numberFormatter.format(hours) });
|
||||
};
|
||||
const isGameDownloading =
|
||||
gameDownloading?.id === game?.id &&
|
||||
GameStatusHelper.isDownloading(game?.status ?? null);
|
||||
|
||||
const finalDownloadSize = useMemo(() => {
|
||||
if (!game) return "N/A";
|
||||
|
@ -117,7 +83,8 @@ export function HeroPanel({
|
|||
{formatBytes(gameDownloading.bytesDownloaded)} /{" "}
|
||||
{finalDownloadSize}
|
||||
<small>
|
||||
{numPeers} peers / {numSeeds} seeds
|
||||
{game?.downloader === Downloader.Torrent &&
|
||||
`${numPeers} peers / ${numSeeds} seeds`}
|
||||
</small>
|
||||
</p>
|
||||
)}
|
||||
|
@ -140,30 +107,8 @@ export function HeroPanel({
|
|||
);
|
||||
}
|
||||
|
||||
if (GameStatus.isReady(game?.status) || (game && !game.status)) {
|
||||
if (!game.lastTimePlayed) {
|
||||
return <p>{t("not_played_yet", { title: game.title })}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
{t("play_time", {
|
||||
amount: formatPlayTime(),
|
||||
})}
|
||||
</p>
|
||||
|
||||
{isGamePlaying ? (
|
||||
<p>{t("playing_now")}</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("last_time_played", {
|
||||
period: lastTimePlayed,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
if (game && GameStatusHelper.isReady(game?.status ?? null)) {
|
||||
return <HeroPanelPlaytime game={game} isGamePlaying={isGamePlaying} />;
|
||||
}
|
||||
|
||||
const [latestRepack] = gameDetails.repacks;
|
||||
|
|
|
@ -129,6 +129,7 @@ export function Settings() {
|
|||
<TextField
|
||||
label={t("real_debrid_api_token_description")}
|
||||
value={form.realDebridApiToken ?? ""}
|
||||
type="password"
|
||||
onChange={(event) => {
|
||||
updateUserPreferences("realDebridApiToken", event.target.value);
|
||||
}}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue