feat: correcting date in process-watcher

This commit is contained in:
Chubby Granny Chaser 2024-05-29 17:20:47 +01:00
commit 85516c1744
No known key found for this signature in database
47 changed files with 334 additions and 489 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

View file

@ -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",
},
});

View file

@ -1,12 +1,18 @@
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 { AppUpdaterEvents } from "@types";
export interface HeaderProps {
onSearch: (query: string) => void;
@ -34,6 +40,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 +58,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 +96,80 @@ 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={12} />
<small>{t("version_available", { version: newVersion })}</small>
</button>
</header>
)}
</>
);
}

View file

@ -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 = 55;
const TOAST_HEIGHT = 80;
export const slideIn = keyframes({
"0%": { transform: `translateY(${TOAST_HEIGHT + SPACING_UNIT * 2}px)` },
@ -19,12 +19,12 @@ export const toast = recipe({
base: {
animationDuration: "0.2s",
animationTimingFunction: "ease-in-out",
height: TOAST_HEIGHT,
maxHeight: TOAST_HEIGHT,
position: "fixed",
backgroundColor: vars.color.background,
borderRadius: "4px",
border: `solid 1px ${vars.color.border}`,
left: "50%",
right: `${SPACING_UNIT * 2}px`,
/* Bottom panel height + 16px */
bottom: `${26 + SPACING_UNIT * 2}px`,
overflow: "hidden",
@ -32,6 +32,7 @@ export const toast = recipe({
flexDirection: "column",
justifyContent: "space-between",
zIndex: "0",
maxWidth: "500px",
},
variants: {
closing: {

View file

@ -82,6 +82,7 @@ export function Toast({ visible, message, type, onClose }: ToastProps) {
{type === "success" && (
<CheckCircleFillIcon className={styles.successIcon} />
)}
{type === "error" && <XCircleFillIcon className={styles.errorIcon} />}
<span style={{ fontWeight: "bold" }}>{message}</span>
</div>

View file

@ -11,6 +11,7 @@ import type {
DownloadProgress,
UserPreferences,
StartGameDownloadPayload,
RealDebridUser,
} from "@types";
import type { DiskSpace } from "check-disk-space";
@ -88,13 +89,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 {

View file

@ -20,6 +20,7 @@ export const toastSlice = createSlice({
reducers: {
showToast: (state, action: PayloadAction<Omit<ToastState, "visible">>) => {
state.message = action.payload.message;
state.type = action.payload.type;
state.visible = true;
},
closeToast: (state) => {

View file

@ -0,0 +1,3 @@
import log from "electron-log/renderer";
export const logger = log.scope("renderer");

View file

@ -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} />

View file

@ -8,6 +8,7 @@ import { CheckCircleFillIcon, DownloadIcon } from "@primer/octicons-react";
import { Downloader, formatBytes } from "@shared";
import type { GameRepack, UserPreferences } from "@types";
import { SPACING_UNIT } from "@renderer/theme.css";
export interface SelectFolderModalProps {
visible: boolean;
@ -95,7 +96,14 @@ export function SelectFolderModal({
>
<div className={styles.container}>
<div>
<label style={{ marginBottom: 0, padding: 0 }}>Method</label>
<span
style={{
marginBottom: `${SPACING_UNIT}px`,
display: "block",
}}
>
Method
</span>
<div className={styles.downloaders}>
<Button
@ -130,25 +138,27 @@ export function SelectFolderModal({
</div>
</div>
<div className={styles.downloadsPathField}>
<TextField value={selectedPath} readOnly disabled label="Path" />
<div>
<div className={styles.downloadsPathField}>
<TextField value={selectedPath} readOnly disabled label="Path" />
<Button
style={{ alignSelf: "flex-end" }}
theme="outline"
onClick={handleChooseDownloadsPath}
disabled={downloadStarting}
>
{t("change")}
</Button>
<Button
style={{ alignSelf: "flex-end" }}
theme="outline"
onClick={handleChooseDownloadsPath}
disabled={downloadStarting}
>
{t("change")}
</Button>
</div>
<p className={styles.hintText}>
<Trans i18nKey="select_folder_hint" ns="game_details">
<Link to="/settings" />
</Trans>
</p>
</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")}

View file

@ -10,5 +10,5 @@ export const form = style({
export const description = style({
fontFamily: "'Fira Sans', sans-serif",
marginBottom: `${SPACING_UNIT}px`,
marginBottom: `${SPACING_UNIT * 2}px`,
});

View file

@ -41,12 +41,7 @@ export function SettingsRealDebrid({
event
) => {
event.preventDefault();
dispatch(
showToast({
message: t("real_debrid_authenticated"),
type: "success",
})
);
if (form.useRealDebrid) {
const user = await window.electron.authenticateRealDebrid(
form.realDebridApiToken!
@ -55,18 +50,25 @@ export function SettingsRealDebrid({
if (user.type === "premium") {
dispatch(
showToast({
message: t("real_debrid_authenticated"),
type: "success",
message: t("real_debrid_free_account", { username: user.username }),
type: "error",
})
);
updateUserPreferences({
realDebridApiToken: form.useRealDebrid
? form.realDebridApiToken
: null,
});
return;
}
}
// dispatch(
// showToast({
// message: t("real_debrid_free_account", { username: "doctorp" }),
// type: "error",
// })
// );
updateUserPreferences({
realDebridApiToken: form.useRealDebrid ? form.realDebridApiToken : null,
});
};
const isButtonDisabled = form.useRealDebrid && !form.realDebridApiToken;
@ -106,7 +108,7 @@ export function SettingsRealDebrid({
<Button
type="submit"
style={{ alignSelf: "flex-end" }}
style={{ alignSelf: "flex-end", marginTop: `${SPACING_UNIT * 2}px` }}
disabled={isButtonDisabled}
>
{t("save_changes")}

View file

@ -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,
});

View file

@ -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>
);
}