mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
feat: adding download method toggle
This commit is contained in:
commit
a240c3ae24
33 changed files with 564 additions and 45 deletions
|
@ -12,7 +12,7 @@ import {
|
|||
import * as styles from "./app.css";
|
||||
import { themeClass } from "./theme.css";
|
||||
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
setSearch,
|
||||
clearSearch,
|
||||
|
@ -26,7 +26,7 @@ export interface AppProps {
|
|||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function App({ children }: AppProps) {
|
||||
export function App() {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const { updateLibrary } = useLibrary();
|
||||
|
||||
|
@ -127,7 +127,7 @@ export function App({ children }: AppProps) {
|
|||
/>
|
||||
|
||||
<section ref={contentRef} className={styles.content}>
|
||||
{children}
|
||||
<Outlet />
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
|
|
BIN
src/renderer/src/assets/icon.png
Normal file
BIN
src/renderer/src/assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
|
@ -3,7 +3,7 @@ import { keyframes, style } from "@vanilla-extract/css";
|
|||
import { SPACING_UNIT, vars } from "../../theme.css";
|
||||
import { recipe } from "@vanilla-extract/recipes";
|
||||
|
||||
const TOAST_HEIGHT = 60;
|
||||
const TOAST_HEIGHT = 55;
|
||||
|
||||
export const slideIn = keyframes({
|
||||
"0%": { transform: `translateY(${TOAST_HEIGHT + SPACING_UNIT * 2}px)` },
|
||||
|
|
|
@ -85,6 +85,7 @@ export function Toast({ visible, message, type, onClose }: ToastProps) {
|
|||
type="button"
|
||||
className={styles.closeButton}
|
||||
onClick={startAnimateClosing}
|
||||
aria-label="Close toast"
|
||||
>
|
||||
<XCircleIcon />
|
||||
</button>
|
||||
|
|
9
src/renderer/src/declaration.d.ts
vendored
9
src/renderer/src/declaration.d.ts
vendored
|
@ -1,4 +1,5 @@
|
|||
import type {
|
||||
AppUpdaterEvents,
|
||||
CatalogueCategory,
|
||||
CatalogueEntry,
|
||||
Game,
|
||||
|
@ -90,6 +91,14 @@ declare global {
|
|||
options: Electron.OpenDialogOptions
|
||||
) => Promise<Electron.OpenDialogReturnValue>;
|
||||
platform: NodeJS.Platform;
|
||||
|
||||
/* Splash */
|
||||
onAutoUpdaterEvent: (
|
||||
cb: (event: AppUpdaterEvents) => void
|
||||
) => () => Electron.IpcRenderer;
|
||||
checkForUpdates: () => Promise<void>;
|
||||
restartAndInstallUpdate: () => Promise<void>;
|
||||
continueToMainWindow: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
import { store } from "./store";
|
||||
|
||||
import * as resources from "@locales";
|
||||
import Splash from "./pages/splash/splash";
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
|
@ -46,16 +47,17 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<HashRouter>
|
||||
<App>
|
||||
<Routes>
|
||||
<Routes>
|
||||
<Route path="/splash" Component={Splash} />
|
||||
<Route element={<App />}>
|
||||
<Route path="/" Component={Home} />
|
||||
<Route path="/catalogue" Component={Catalogue} />
|
||||
<Route path="/downloads" Component={Downloads} />
|
||||
<Route path="/game/:shop/:objectID" Component={GameDetails} />
|
||||
<Route path="/search" Component={SearchResults} />
|
||||
<Route path="/settings" Component={Settings} />
|
||||
</Routes>
|
||||
</App>
|
||||
</Route>
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
|
|
|
@ -57,9 +57,7 @@ export function GallerySlider() {
|
|||
if (hasMovies && mediaContainerRef.current) {
|
||||
mediaContainerRef.current.childNodes.forEach((node, index) => {
|
||||
if (node instanceof HTMLVideoElement) {
|
||||
if (index == mediaIndex) {
|
||||
node.play();
|
||||
} else {
|
||||
if (index !== mediaIndex) {
|
||||
node.pause();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,3 +17,12 @@ export const hintText = style({
|
|||
fontSize: "12px",
|
||||
color: vars.color.bodyText,
|
||||
});
|
||||
|
||||
export const downloaders = style({
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
||||
export const downloaderOption = style({
|
||||
flex: "1",
|
||||
});
|
||||
|
|
|
@ -80,8 +80,24 @@ export function SelectFolderModal({
|
|||
onClose={onClose}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<div>
|
||||
<label style={{ marginBottom: 0, padding: 0 }}>Download method</label>
|
||||
|
||||
<div className={styles.downloaders}>
|
||||
<Button className={styles.downloaderOption} theme="outline">
|
||||
Torrent
|
||||
</Button>
|
||||
<Button className={styles.downloaderOption}>Real Debrid</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.downloadsPathField}>
|
||||
<TextField value={selectedPath} readOnly disabled />
|
||||
<TextField
|
||||
value={selectedPath}
|
||||
readOnly
|
||||
disabled
|
||||
label="Download path"
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
|
@ -92,11 +108,13 @@ export function SelectFolderModal({
|
|||
{t("change")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className={styles.hintText}>
|
||||
<Trans i18nKey="select_folder_hint" ns="game_details">
|
||||
<Link to="/settings" />
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Button onClick={handleStartClick} disabled={downloadStarting}>
|
||||
<DownloadIcon />
|
||||
{t("download_now")}
|
||||
|
|
|
@ -28,6 +28,8 @@ export function Settings() {
|
|||
const handleUpdateUserPreferences = async (
|
||||
values: Partial<UserPreferences>
|
||||
) => {
|
||||
setIsToastVisible(false);
|
||||
|
||||
await window.electron.updateUserPreferences(values);
|
||||
window.electron.getUserPreferences().then((userPreferences) => {
|
||||
setUserPreferences(userPreferences);
|
||||
|
|
49
src/renderer/src/pages/splash/splash.css.ts
Normal file
49
src/renderer/src/pages/splash/splash.css.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { style } from "@vanilla-extract/css";
|
||||
import { SPACING_UNIT, vars } from "../../theme.css";
|
||||
|
||||
export const main = style({
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: `${SPACING_UNIT * 3}px`,
|
||||
flex: "1",
|
||||
overflowY: "auto",
|
||||
alignItems: "center",
|
||||
});
|
||||
|
||||
export const splashIcon = style({
|
||||
width: "75%",
|
||||
});
|
||||
|
||||
export const updateInfoSection = style({
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
flex: "1",
|
||||
overflowY: "auto",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
});
|
||||
|
||||
export const progressBar = style({
|
||||
WebkitAppearance: "none",
|
||||
appearance: "none",
|
||||
borderRadius: "4px",
|
||||
width: "100%",
|
||||
border: `solid 1px ${vars.color.border}`,
|
||||
overflow: "hidden",
|
||||
height: "18px",
|
||||
"::-webkit-progress-value": {
|
||||
backgroundColor: vars.color.muted,
|
||||
transition: "width 0.2s",
|
||||
},
|
||||
"::-webkit-progress-bar": {
|
||||
backgroundColor: vars.color.darkBackground,
|
||||
},
|
||||
});
|
||||
|
||||
export const progressBarText = style({
|
||||
zIndex: 2,
|
||||
});
|
82
src/renderer/src/pages/splash/splash.tsx
Normal file
82
src/renderer/src/pages/splash/splash.tsx
Normal file
|
@ -0,0 +1,82 @@
|
|||
import icon from "@renderer/assets/icon.png";
|
||||
import * as styles from "./splash.css";
|
||||
import { themeClass } from "../../theme.css";
|
||||
|
||||
import "../../app.css";
|
||||
import { useEffect, useState } from "react";
|
||||
import { AppUpdaterEvents } from "@types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
document.body.classList.add(themeClass);
|
||||
|
||||
export default function Splash() {
|
||||
const [status, setStatus] = useState<AppUpdaterEvents | null>(null);
|
||||
const [newVersion, setNewVersion] = useState("");
|
||||
|
||||
const { t } = useTranslation("splash");
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electron.onAutoUpdaterEvent(
|
||||
(event: AppUpdaterEvents) => {
|
||||
setStatus(event);
|
||||
|
||||
switch (event.type) {
|
||||
case "error":
|
||||
window.electron.continueToMainWindow();
|
||||
break;
|
||||
case "update-available":
|
||||
setNewVersion(event.info.version);
|
||||
break;
|
||||
case "update-cancelled":
|
||||
window.electron.continueToMainWindow();
|
||||
break;
|
||||
case "update-downloaded":
|
||||
window.electron.restartAndInstallUpdate();
|
||||
break;
|
||||
case "update-not-available":
|
||||
window.electron.continueToMainWindow();
|
||||
break;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
window.electron.checkForUpdates();
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderUpdateInfo = () => {
|
||||
switch (status?.type) {
|
||||
case "download-progress":
|
||||
return (
|
||||
<>
|
||||
<p>{t("downloading_version", { version: newVersion })}</p>
|
||||
<progress
|
||||
className={styles.progressBar}
|
||||
max="100"
|
||||
value={status.info.percent}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
case "checking-for-updates":
|
||||
return <p>{t("searching_updates")}</p>;
|
||||
case "update-available":
|
||||
return <p>{t("update_found", { version: newVersion })}</p>;
|
||||
case "update-downloaded":
|
||||
return <p>{t("restarting_and_applying")}</p>;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<img src={icon} className={styles.splashIcon} alt="Hydra Launcher Logo" />
|
||||
<section className={styles.updateInfoSection}>
|
||||
{renderUpdateInfo()}
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue