mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-18 08:43:57 +00:00
chore: merging with main
This commit is contained in:
@@ -97,6 +97,14 @@ export function App() {
|
||||
};
|
||||
}, [clearDownload, setLastPacket, updateLibrary]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electron.onHardDelete(() => {
|
||||
updateLibrary();
|
||||
});
|
||||
|
||||
return () => unsubscribe();
|
||||
}, [updateLibrary]);
|
||||
|
||||
useEffect(() => {
|
||||
const cachedUserDetails = window.localStorage.getItem("userDetails");
|
||||
|
||||
|
||||
68
src/renderer/src/components/dropdown-menu/dropdown-menu.scss
Normal file
68
src/renderer/src/components/dropdown-menu/dropdown-menu.scss
Normal file
@@ -0,0 +1,68 @@
|
||||
@use "../../scss/globals.scss";
|
||||
|
||||
.dropdown-menu {
|
||||
&__content {
|
||||
background-color: globals.$dark-background-color;
|
||||
border: 1px solid globals.$border-color;
|
||||
border-radius: 6px;
|
||||
min-width: 200px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__group {
|
||||
width: 100%;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
&__title-bar {
|
||||
width: 100%;
|
||||
padding: 4px 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: globals.$muted-color;
|
||||
}
|
||||
|
||||
&__separator {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: globals.$border-color;
|
||||
}
|
||||
|
||||
&__item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
border-radius: 4px;
|
||||
padding: 5px 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__item--disabled {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:not(&__item--disabled) &__item:hover {
|
||||
background-color: globals.$background-color;
|
||||
color: globals.$muted-color;
|
||||
}
|
||||
|
||||
&__item:focus {
|
||||
background-color: globals.$background-color;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&__item-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
81
src/renderer/src/components/dropdown-menu/dropdown-menu.tsx
Normal file
81
src/renderer/src/components/dropdown-menu/dropdown-menu.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||
import "./dropdown-menu.scss";
|
||||
|
||||
export interface DropdownMenuItem {
|
||||
icon?: React.ReactNode;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
show?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
interface DropdownMenuProps {
|
||||
children: React.ReactNode;
|
||||
title?: string;
|
||||
loop?: boolean;
|
||||
items: DropdownMenuItem[];
|
||||
sideOffset?: number;
|
||||
side?: "top" | "bottom" | "left" | "right";
|
||||
align?: "start" | "center" | "end";
|
||||
alignOffset?: number;
|
||||
}
|
||||
|
||||
export function DropdownMenu({
|
||||
children,
|
||||
title,
|
||||
items,
|
||||
sideOffset = 5,
|
||||
side = "bottom",
|
||||
loop = true,
|
||||
align = "center",
|
||||
alignOffset = 0,
|
||||
}: DropdownMenuProps) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Root>
|
||||
<DropdownMenuPrimitive.Trigger asChild>
|
||||
<div aria-label={title}>{children}</div>
|
||||
</DropdownMenuPrimitive.Trigger>
|
||||
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
sideOffset={sideOffset}
|
||||
side={side}
|
||||
loop={loop}
|
||||
align={align}
|
||||
alignOffset={alignOffset}
|
||||
className="dropdown-menu__content"
|
||||
>
|
||||
{title && (
|
||||
<DropdownMenuPrimitive.Group className="dropdown-menu__group">
|
||||
<div className="dropdown-menu__title-bar">{title}</div>
|
||||
</DropdownMenuPrimitive.Group>
|
||||
)}
|
||||
|
||||
<DropdownMenuPrimitive.Separator className="dropdown-menu__separator" />
|
||||
|
||||
<DropdownMenuPrimitive.Group className="dropdown-menu__group">
|
||||
{items.map(
|
||||
(item) =>
|
||||
item.show !== false && (
|
||||
<DropdownMenuPrimitive.Item
|
||||
key={item.label}
|
||||
aria-label={item.label}
|
||||
onSelect={item.onClick}
|
||||
className={`dropdown-menu__item ${item.disabled ? "dropdown-menu__item--disabled" : ""}`}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.icon && (
|
||||
<div className="dropdown-menu__item-icon">
|
||||
{item.icon}
|
||||
</div>
|
||||
)}
|
||||
{item.label}
|
||||
</DropdownMenuPrimitive.Item>
|
||||
)
|
||||
)}
|
||||
</DropdownMenuPrimitive.Group>
|
||||
</DropdownMenuPrimitive.Content>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
</DropdownMenuPrimitive.Root>
|
||||
);
|
||||
}
|
||||
7
src/renderer/src/declaration.d.ts
vendored
7
src/renderer/src/declaration.d.ts
vendored
@@ -8,6 +8,7 @@ import type {
|
||||
ShopDetails,
|
||||
Steam250Game,
|
||||
DownloadProgress,
|
||||
SeedingStatus,
|
||||
UserPreferences,
|
||||
StartGameDownloadPayload,
|
||||
RealDebridUser,
|
||||
@@ -44,9 +45,15 @@ declare global {
|
||||
cancelGameDownload: (gameId: number) => Promise<void>;
|
||||
pauseGameDownload: (gameId: number) => Promise<void>;
|
||||
resumeGameDownload: (gameId: number) => Promise<void>;
|
||||
pauseGameSeed: (gameId: number) => Promise<void>;
|
||||
resumeGameSeed: (gameId: number) => Promise<void>;
|
||||
onDownloadProgress: (
|
||||
cb: (value: DownloadProgress) => void
|
||||
) => () => Electron.IpcRenderer;
|
||||
onSeedingStatus: (
|
||||
cb: (value: SeedingStatus[]) => void
|
||||
) => () => Electron.IpcRenderer;
|
||||
onHardDelete: (cb: () => void) => () => Electron.IpcRenderer;
|
||||
|
||||
/* Catalogue */
|
||||
searchGames: (
|
||||
|
||||
@@ -66,6 +66,16 @@ export function useDownload() {
|
||||
updateLibrary();
|
||||
});
|
||||
|
||||
const pauseSeeding = async (gameId: number) => {
|
||||
await window.electron.pauseGameSeed(gameId);
|
||||
await updateLibrary();
|
||||
};
|
||||
|
||||
const resumeSeeding = async (gameId: number) => {
|
||||
await window.electron.resumeGameSeed(gameId);
|
||||
await updateLibrary();
|
||||
};
|
||||
|
||||
const calculateETA = () => {
|
||||
if (!lastPacket || lastPacket.timeRemaining < 0) return "";
|
||||
|
||||
@@ -96,6 +106,8 @@ export function useDownload() {
|
||||
removeGameFromLibrary,
|
||||
removeGameInstaller,
|
||||
isGameDeleting,
|
||||
pauseSeeding,
|
||||
resumeSeeding,
|
||||
clearDownload: () => dispatch(clearDownload()),
|
||||
setLastPacket: (packet: DownloadProgress) =>
|
||||
dispatch(setLastPacket(packet)),
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function Catalogue() {
|
||||
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
|
||||
.map(([key, value]) => ({
|
||||
label: key,
|
||||
value: value.toString(),
|
||||
value: value,
|
||||
checked: filters.tags.includes(value),
|
||||
}));
|
||||
}, [steamUserTags, filters.tags, language]);
|
||||
|
||||
@@ -8,10 +8,10 @@ export interface FilterSectionProps {
|
||||
title: string;
|
||||
items: {
|
||||
label: string;
|
||||
value: string;
|
||||
value: string | number;
|
||||
checked: boolean;
|
||||
}[];
|
||||
onSelect: (value: string) => void;
|
||||
onSelect: (value: string | number) => void;
|
||||
color: string;
|
||||
onClear: () => void;
|
||||
}
|
||||
|
||||
@@ -38,22 +38,24 @@ export function Pagination({
|
||||
<Button
|
||||
theme="outline"
|
||||
onClick={() => onPageChange(page - 1)}
|
||||
style={{ width: 40 }}
|
||||
style={{ width: 40, maxWidth: 40, maxHeight: 40 }}
|
||||
disabled={page === 1}
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
</Button>
|
||||
|
||||
<div
|
||||
style={{
|
||||
width: 40,
|
||||
justifyContent: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 16 }}>...</span>
|
||||
</div>
|
||||
{page > 2 && (
|
||||
<div
|
||||
style={{
|
||||
width: 40,
|
||||
justifyContent: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 16 }}>...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Page Buttons */}
|
||||
{Array.from(
|
||||
@@ -63,17 +65,31 @@ export function Pagination({
|
||||
<Button
|
||||
theme={page === pageNumber ? "primary" : "outline"}
|
||||
key={pageNumber}
|
||||
style={{ width: 40, maxWidth: 40, maxHeight: 40 }}
|
||||
onClick={() => onPageChange(pageNumber)}
|
||||
>
|
||||
{pageNumber}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
{page < totalPages - 1 && (
|
||||
<div
|
||||
style={{
|
||||
width: 40,
|
||||
justifyContent: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 16 }}>...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Next Button */}
|
||||
<Button
|
||||
theme="outline"
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
style={{ width: 40 }}
|
||||
style={{ width: 40, maxWidth: 40, maxHeight: 40 }}
|
||||
disabled={page === totalPages}
|
||||
>
|
||||
<ChevronRightIcon />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import type { LibraryGame } from "@types";
|
||||
import type { LibraryGame, SeedingStatus } from "@types";
|
||||
|
||||
import { Badge, Button } from "@renderer/components";
|
||||
import {
|
||||
@@ -15,12 +15,28 @@ import { useAppSelector, useDownload } from "@renderer/hooks";
|
||||
import * as styles from "./download-group.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuItem,
|
||||
} from "@renderer/components/dropdown-menu/dropdown-menu";
|
||||
import {
|
||||
ColumnsIcon,
|
||||
DownloadIcon,
|
||||
LinkIcon,
|
||||
PlayIcon,
|
||||
ThreeBarsIcon,
|
||||
TrashIcon,
|
||||
UnlinkIcon,
|
||||
XCircleIcon,
|
||||
} from "@primer/octicons-react";
|
||||
|
||||
export interface DownloadGroupProps {
|
||||
library: LibraryGame[];
|
||||
title: string;
|
||||
openDeleteGameModal: (gameId: number) => void;
|
||||
openGameInstaller: (gameId: number) => void;
|
||||
seedingStatus: SeedingStatus[];
|
||||
}
|
||||
|
||||
export function DownloadGroup({
|
||||
@@ -28,6 +44,7 @@ export function DownloadGroup({
|
||||
title,
|
||||
openDeleteGameModal,
|
||||
openGameInstaller,
|
||||
seedingStatus,
|
||||
}: DownloadGroupProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -44,6 +61,8 @@ export function DownloadGroup({
|
||||
resumeDownload,
|
||||
cancelDownload,
|
||||
isGameDeleting,
|
||||
pauseSeeding,
|
||||
resumeSeeding,
|
||||
} = useDownload();
|
||||
|
||||
const getFinalDownloadSize = (game: LibraryGame) => {
|
||||
@@ -57,9 +76,20 @@ export function DownloadGroup({
|
||||
return "N/A";
|
||||
};
|
||||
|
||||
const seedingMap = useMemo(() => {
|
||||
const map = new Map<number, SeedingStatus>();
|
||||
|
||||
seedingStatus.forEach((seed) => {
|
||||
map.set(seed.gameId, seed);
|
||||
});
|
||||
|
||||
return map;
|
||||
}, [seedingStatus]);
|
||||
|
||||
const getGameInfo = (game: LibraryGame) => {
|
||||
const isGameDownloading = lastPacket?.game.id === game.id;
|
||||
const finalDownloadSize = getFinalDownloadSize(game);
|
||||
const seedingStatus = seedingMap.get(game.id);
|
||||
|
||||
if (isGameDeleting(game.id)) {
|
||||
return <p>{t("deleting")}</p>;
|
||||
@@ -98,7 +128,17 @@ export function DownloadGroup({
|
||||
}
|
||||
|
||||
if (game.progress === 1) {
|
||||
return <p>{t("completed")}</p>;
|
||||
const uploadSpeed = formatBytes(seedingStatus?.uploadSpeed ?? 0);
|
||||
|
||||
return game.status === "seeding" &&
|
||||
game.downloader === Downloader.Torrent ? (
|
||||
<>
|
||||
<p>{t("seeding")}</p>
|
||||
{uploadSpeed && <p>{uploadSpeed}/s</p>}
|
||||
</>
|
||||
) : (
|
||||
<p>{t("completed")}</p>
|
||||
);
|
||||
}
|
||||
|
||||
if (game.status === "paused") {
|
||||
@@ -125,59 +165,74 @@ export function DownloadGroup({
|
||||
return <p>{t(game.status as string)}</p>;
|
||||
};
|
||||
|
||||
const getGameActions = (game: LibraryGame) => {
|
||||
const getGameActions = (game: LibraryGame): DropdownMenuItem[] => {
|
||||
const isGameDownloading = lastPacket?.game.id === game.id;
|
||||
|
||||
const deleting = isGameDeleting(game.id);
|
||||
|
||||
if (game.progress === 1) {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => openGameInstaller(game.id)}
|
||||
theme="outline"
|
||||
disabled={deleting}
|
||||
>
|
||||
{t("install")}
|
||||
</Button>
|
||||
|
||||
<Button onClick={() => openDeleteGameModal(game.id)} theme="outline">
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
return [
|
||||
{
|
||||
label: t("install"),
|
||||
disabled: deleting,
|
||||
onClick: () => openGameInstaller(game.id),
|
||||
icon: <DownloadIcon />,
|
||||
},
|
||||
{
|
||||
label: t("stop_seeding"),
|
||||
disabled: deleting,
|
||||
icon: <UnlinkIcon />,
|
||||
show:
|
||||
game.status === "seeding" && game.downloader === Downloader.Torrent,
|
||||
onClick: () => pauseSeeding(game.id),
|
||||
},
|
||||
{
|
||||
label: t("resume_seeding"),
|
||||
disabled: deleting,
|
||||
icon: <LinkIcon />,
|
||||
show:
|
||||
game.status !== "seeding" && game.downloader === Downloader.Torrent,
|
||||
onClick: () => resumeSeeding(game.id),
|
||||
},
|
||||
{
|
||||
label: t("delete"),
|
||||
disabled: deleting,
|
||||
icon: <TrashIcon />,
|
||||
onClick: () => openDeleteGameModal(game.id),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (isGameDownloading || game.status === "active") {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => pauseDownload(game.id)} theme="outline">
|
||||
{t("pause")}
|
||||
</Button>
|
||||
<Button onClick={() => cancelDownload(game.id)} theme="outline">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
return [
|
||||
{
|
||||
label: t("pause"),
|
||||
onClick: () => pauseDownload(game.id),
|
||||
icon: <ColumnsIcon />,
|
||||
},
|
||||
{
|
||||
label: t("cancel"),
|
||||
onClick: () => cancelDownload(game.id),
|
||||
icon: <XCircleIcon />,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => resumeDownload(game.id)}
|
||||
theme="outline"
|
||||
disabled={
|
||||
game.downloader === Downloader.RealDebrid &&
|
||||
!userPreferences?.realDebridApiToken
|
||||
}
|
||||
>
|
||||
{t("resume")}
|
||||
</Button>
|
||||
<Button onClick={() => cancelDownload(game.id)} theme="outline">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
return [
|
||||
{
|
||||
label: t("resume"),
|
||||
disabled:
|
||||
game.downloader === Downloader.RealDebrid &&
|
||||
!userPreferences?.realDebridApiToken,
|
||||
onClick: () => resumeDownload(game.id),
|
||||
icon: <PlayIcon />,
|
||||
},
|
||||
{
|
||||
label: t("cancel"),
|
||||
onClick: () => cancelDownload(game.id),
|
||||
icon: <XCircleIcon />,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
if (!library.length) return null;
|
||||
@@ -207,7 +262,11 @@ export function DownloadGroup({
|
||||
<ul className={styles.downloads}>
|
||||
{library.map((game) => {
|
||||
return (
|
||||
<li key={game.id} className={styles.download}>
|
||||
<li
|
||||
key={game.id}
|
||||
className={styles.download}
|
||||
style={{ position: "relative" }}
|
||||
>
|
||||
<div className={styles.downloadCover}>
|
||||
<div className={styles.downloadCoverBackdrop}>
|
||||
<img
|
||||
@@ -243,9 +302,28 @@ export function DownloadGroup({
|
||||
{getGameInfo(game)}
|
||||
</div>
|
||||
|
||||
<div className={styles.downloadActions}>
|
||||
{getGameActions(game)}
|
||||
</div>
|
||||
{getGameActions(game) !== null && (
|
||||
<DropdownMenu
|
||||
align="end"
|
||||
items={getGameActions(game)}
|
||||
sideOffset={-75}
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "12px",
|
||||
right: "12px",
|
||||
borderRadius: "50%",
|
||||
border: "none",
|
||||
padding: "8px",
|
||||
minHeight: "unset",
|
||||
}}
|
||||
theme="outline"
|
||||
>
|
||||
<ThreeBarsIcon />
|
||||
</Button>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -2,12 +2,12 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useDownload, useLibrary } from "@renderer/hooks";
|
||||
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal";
|
||||
import * as styles from "./downloads.css";
|
||||
import { DeleteGameModal } from "./delete-game-modal";
|
||||
import { DownloadGroup } from "./download-group";
|
||||
import type { LibraryGame } from "@types";
|
||||
import type { LibraryGame, SeedingStatus } from "@types";
|
||||
import { orderBy } from "lodash-es";
|
||||
import { ArrowDownIcon } from "@primer/octicons-react";
|
||||
|
||||
@@ -21,15 +21,23 @@ export default function Downloads() {
|
||||
const [showBinaryNotFoundModal, setShowBinaryNotFoundModal] = useState(false);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
|
||||
const { removeGameInstaller } = useDownload();
|
||||
const { removeGameInstaller, pauseSeeding } = useDownload();
|
||||
|
||||
const handleDeleteGame = async () => {
|
||||
if (gameToBeDeleted.current)
|
||||
if (gameToBeDeleted.current) {
|
||||
await pauseSeeding(gameToBeDeleted.current);
|
||||
await removeGameInstaller(gameToBeDeleted.current);
|
||||
}
|
||||
};
|
||||
|
||||
const { lastPacket } = useDownload();
|
||||
|
||||
const [seedingStatus, setSeedingStatus] = useState<SeedingStatus[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
window.electron.onSeedingStatus((value) => setSeedingStatus(value));
|
||||
}, []);
|
||||
|
||||
const handleOpenGameInstaller = (gameId: number) =>
|
||||
window.electron.openGameInstaller(gameId).then((isBinaryInPath) => {
|
||||
if (!isBinaryInPath) setShowBinaryNotFoundModal(true);
|
||||
@@ -119,9 +127,10 @@ export default function Downloads() {
|
||||
<DownloadGroup
|
||||
key={group.title}
|
||||
title={group.title}
|
||||
library={group.library}
|
||||
library={orderBy(group.library, ["updatedAt"], ["desc"])}
|
||||
openDeleteGameModal={handleOpenDeleteGameModal}
|
||||
openGameInstaller={handleOpenGameInstaller}
|
||||
seedingStatus={seedingStatus}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -179,7 +179,7 @@ export function ProfileContent() {
|
||||
game.achievementCount > 0 && (
|
||||
<div
|
||||
style={{
|
||||
color: "white",
|
||||
color: "#fff",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
@@ -235,6 +235,8 @@ export function ProfileContent() {
|
||||
borderRadius: 4,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
minWidth: "100%",
|
||||
minHeight: "100%",
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -19,6 +19,7 @@ export function SettingsBehavior() {
|
||||
runAtStartup: false,
|
||||
startMinimized: false,
|
||||
disableNsfwAlert: false,
|
||||
seedAfterDownloadComplete: false,
|
||||
showHiddenAchievementsDescription: false,
|
||||
});
|
||||
|
||||
@@ -31,6 +32,7 @@ export function SettingsBehavior() {
|
||||
runAtStartup: userPreferences.runAtStartup,
|
||||
startMinimized: userPreferences.startMinimized,
|
||||
disableNsfwAlert: userPreferences.disableNsfwAlert,
|
||||
seedAfterDownloadComplete: userPreferences.seedAfterDownloadComplete,
|
||||
showHiddenAchievementsDescription:
|
||||
userPreferences.showHiddenAchievementsDescription,
|
||||
});
|
||||
@@ -100,6 +102,16 @@ export function SettingsBehavior() {
|
||||
}
|
||||
/>
|
||||
|
||||
<CheckboxField
|
||||
label={t("seed_after_download_complete")}
|
||||
checked={form.seedAfterDownloadComplete}
|
||||
onChange={() =>
|
||||
handleChange({
|
||||
seedAfterDownloadComplete: !form.seedAfterDownloadComplete,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<CheckboxField
|
||||
label={t("show_hidden_achievement_description")}
|
||||
checked={form.showHiddenAchievementsDescription}
|
||||
|
||||
Reference in New Issue
Block a user