mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
feat: remove splash and add new subheader to request user to restart and install update
This commit is contained in:
parent
21305d3e39
commit
cdd18a8ce2
14 changed files with 131 additions and 280 deletions
|
@ -145,3 +145,21 @@ export const title = recipe({
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const subheader = style({
|
||||
borderBottom: `solid 1px ${vars.color.border}`,
|
||||
padding: `${SPACING_UNIT / 2}px ${SPACING_UNIT * 3}px`,
|
||||
});
|
||||
|
||||
export const newVersionButton = style({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
color: vars.color.bodyText,
|
||||
borderBottom: "1px solid transparent",
|
||||
":hover": {
|
||||
borderBottom: `1px solid ${vars.color.bodyText}`,
|
||||
cursor: "pointer",
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { ArrowLeftIcon, SearchIcon, XIcon } from "@primer/octicons-react";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
SearchIcon,
|
||||
SyncIcon,
|
||||
XIcon,
|
||||
} from "@primer/octicons-react";
|
||||
|
||||
import { useAppDispatch, useAppSelector } from "@renderer/hooks";
|
||||
|
||||
import * as styles from "./header.css";
|
||||
import { clearSearch } from "@renderer/features";
|
||||
import { Button } from "../button/button";
|
||||
import { AppUpdaterEvents } from "@types";
|
||||
|
||||
export interface HeaderProps {
|
||||
onSearch: (query: string) => void;
|
||||
|
@ -34,6 +41,9 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
|||
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const [showUpdateSubheader, setShowUpdateSubheader] = useState(false);
|
||||
const [newVersion, setNewVersion] = useState("");
|
||||
|
||||
const { t } = useTranslation("header");
|
||||
|
||||
const title = useMemo(() => {
|
||||
|
@ -49,6 +59,30 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
|||
}
|
||||
}, [location.pathname, search, dispatch]);
|
||||
|
||||
const handleClickRestartAndUpdate = () => {
|
||||
window.electron.restartAndInstallUpdate();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electron.onAutoUpdaterEvent(
|
||||
(event: AppUpdaterEvents) => {
|
||||
if (event.type == "update-available") {
|
||||
setNewVersion(event.info.version || "");
|
||||
}
|
||||
|
||||
if (event.type == "update-downloaded") {
|
||||
setShowUpdateSubheader(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
window.electron.checkForUpdates();
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
});
|
||||
|
||||
const focusInput = () => {
|
||||
setIsFocused(true);
|
||||
inputRef.current?.focus();
|
||||
|
@ -63,64 +97,83 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
|||
};
|
||||
|
||||
return (
|
||||
<header
|
||||
className={styles.header({
|
||||
draggingDisabled,
|
||||
isWindows: window.electron.platform === "win32",
|
||||
})}
|
||||
>
|
||||
<div className={styles.section}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.backButton({ enabled: location.key !== "default" })}
|
||||
onClick={handleBackButtonClick}
|
||||
disabled={location.key === "default"}
|
||||
>
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
|
||||
<h3
|
||||
className={styles.title({
|
||||
hasBackButton: location.key !== "default",
|
||||
})}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<section className={styles.section}>
|
||||
<div className={styles.search({ focused: isFocused })}>
|
||||
<>
|
||||
<header
|
||||
className={styles.header({
|
||||
draggingDisabled,
|
||||
isWindows: window.electron.platform === "win32",
|
||||
})}
|
||||
>
|
||||
<div className={styles.section}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.actionButton}
|
||||
onClick={focusInput}
|
||||
className={styles.backButton({
|
||||
enabled: location.key !== "default",
|
||||
})}
|
||||
onClick={handleBackButtonClick}
|
||||
disabled={location.key === "default"}
|
||||
>
|
||||
<SearchIcon />
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
name="search"
|
||||
placeholder={t("search")}
|
||||
value={search}
|
||||
className={styles.searchInput}
|
||||
onChange={(event) => onSearch(event.target.value)}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
<h3
|
||||
className={styles.title({
|
||||
hasBackButton: location.key !== "default",
|
||||
})}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{search && (
|
||||
<section className={styles.section}>
|
||||
<div className={styles.search({ focused: isFocused })}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClear}
|
||||
className={styles.actionButton}
|
||||
onClick={focusInput}
|
||||
>
|
||||
<XIcon />
|
||||
<SearchIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
name="search"
|
||||
placeholder={t("search")}
|
||||
value={search}
|
||||
className={styles.searchInput}
|
||||
onChange={(event) => onSearch(event.target.value)}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
|
||||
{search && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClear}
|
||||
className={styles.actionButton}
|
||||
>
|
||||
<XIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</header>
|
||||
{showUpdateSubheader && (
|
||||
<header className={styles.subheader}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.newVersionButton}
|
||||
onClick={handleClickRestartAndUpdate}
|
||||
>
|
||||
<SyncIcon size={14} />
|
||||
<small>
|
||||
New version {newVersion} available. Click here to restart and
|
||||
install
|
||||
</small>
|
||||
</button>
|
||||
</header>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
3
src/renderer/src/declaration.d.ts
vendored
3
src/renderer/src/declaration.d.ts
vendored
|
@ -92,13 +92,12 @@ declare global {
|
|||
) => Promise<Electron.OpenDialogReturnValue>;
|
||||
platform: NodeJS.Platform;
|
||||
|
||||
/* Splash */
|
||||
/* Auto update */
|
||||
onAutoUpdaterEvent: (
|
||||
cb: (event: AppUpdaterEvents) => void
|
||||
) => () => Electron.IpcRenderer;
|
||||
checkForUpdates: () => Promise<void>;
|
||||
restartAndInstallUpdate: () => Promise<void>;
|
||||
continueToMainWindow: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
|
|
|
@ -27,7 +27,6 @@ import {
|
|||
import { store } from "./store";
|
||||
|
||||
import * as resources from "@locales";
|
||||
import Splash from "./pages/splash/splash";
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
|
@ -48,7 +47,6 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|||
<Provider store={store}>
|
||||
<HashRouter>
|
||||
<Routes>
|
||||
<Route path="/splash" Component={Splash} />
|
||||
<Route element={<App />}>
|
||||
<Route path="/" Component={Home} />
|
||||
<Route path="/catalogue" Component={Catalogue} />
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
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,
|
||||
});
|
|
@ -1,82 +0,0 @@
|
|||
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