Migrate Tooltips and OptimalValuesDialog to new system

This commit is contained in:
Mathias Wagner
2026-01-20 20:45:43 +01:00
parent 55cacb68ec
commit 431b1ca81d
3 changed files with 121 additions and 122 deletions

View File

@@ -10,7 +10,7 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { useContext, useEffect, useState } from "react";
import DropdownComponent from "../Dropdown/DropdownComponent";
import { InputDialogContext } from "@/common/contexts/InputDialog";
import { useAlert } from "@/common/contexts/Alert";
import { StatusContext } from "@/common/contexts/Status";
import { SpeedtestContext } from "@/common/contexts/Speedtests";
import { jsonRequest, postRequest } from "@/common/utils/RequestUtil";
@@ -23,6 +23,7 @@ import { Trans } from "react-i18next";
import { useLocation, useNavigate } from "react-router-dom";
import Pagination from "./components/Pagination";
import AboutDialog from "@/common/components/AboutDialog";
import Tooltip from "@/common/components/Tooltip";
const HeaderComponent = () => {
const findNode = useContext(NodeContext)[4];
@@ -30,7 +31,7 @@ const HeaderComponent = () => {
const navigate = useNavigate();
const location = useLocation();
const [setDialog] = useContext(InputDialogContext);
const alert = useAlert();
const [icon, setIcon] = useState(faGear);
const [status, updateStatus, setRunning] = useContext(StatusContext);
const {updateTests} = useContext(SpeedtestContext);
@@ -44,35 +45,38 @@ const HeaderComponent = () => {
setIcon(isDropdownOpen ? faGear : faClose);
}
const showDemoDialog = () => setDialog({
title: t("preview.title"),
description: <Trans components={{ Link: <a href={WEB_URL + "/install"} target="_blank" /> }}>preview.description</Trans>,
buttonText: t("dialog.okay")
});
const showDemoDialog = () => alert.openAlert(
t("preview.title"),
<Trans components={{ Link: <a href={WEB_URL + "/install"} target="_blank" /> }}>preview.description</Trans>,
{ buttonText: t("dialog.okay") }
);
const showPasswordDialog = () => setDialog({
title: t("header.admin_login"),
placeholder: t("dialog.password.placeholder"),
description: localStorage.getItem("password") ? <span className="icon-red">{t("dialog.password.wrong")}</span> : "",
type: "password",
buttonText: t("dialog.login"),
onSuccess: (value) => {
localStorage.setItem("password", value);
const showPasswordDialog = async () => {
const result = await alert.openInput(t("header.admin_login"), {
placeholder: t("dialog.password.placeholder"),
description: localStorage.getItem("password") ? <span className="icon-red">{t("dialog.password.wrong")}</span> : "",
inputType: "password",
buttonText: t("dialog.login")
});
if (result) {
localStorage.setItem("password", result);
reloadConfig();
checkConfig().then((config) => config?.viewMode ? showPasswordDialog() : false).catch(() => showPasswordDialog());
},
onClose: () => {
const newConfig = await checkConfig().catch(() => null);
if (newConfig?.viewMode) {
showPasswordDialog();
}
} else {
localStorage.removeItem("password");
}
});
};
const startSpeedtest = async () => {
await updateStatus();
if (status.paused) return setDialog({
title: t("failed"),
description: t("header.paused"),
buttonText: t("dialog.okay")
});
if (status.paused) {
alert.openAlert(t("failed"), t("header.paused"), { buttonText: t("dialog.okay") });
return;
}
if (status.running) return;
@@ -101,7 +105,7 @@ const HeaderComponent = () => {
return (
<header>
{showAboutDialog && <AboutDialog onClose={() => setShowAboutDialog(false)}/>}
<AboutDialog open={showAboutDialog} onClose={() => setShowAboutDialog(false)}/>
<div className="header-main">
<div className="header-left">
{config.viewMode && <h2>{t("header.title")}</h2>}
@@ -115,38 +119,43 @@ const HeaderComponent = () => {
<div className="header-right">
{updateAvailable ?
<div><FontAwesomeIcon icon={faCircleArrowUp} className="header-icon icon-orange update-icon"
onClick={() => setDialog({
title: t("header.new_update"),
buttonText: t("dialog.okay"),
description: updateInfo(updateAvailable)
})} /></div> : <></>}
onClick={() => alert.openAlert(
t("header.new_update"),
updateInfo(updateAvailable),
{ buttonText: t("dialog.okay") }
)} /></div> : <></>}
{!(status.paused || config.viewMode) ? <div className="tooltip-element tooltip-bottom">
<FontAwesomeIcon icon={faGaugeHigh}
className={"header-icon " + (status.running ? "test-running" : "")}
onClick={startSpeedtest} />
<span className="tooltip">{t("header." + (status.running ? "running_tooltip" : "start_tooltip"))}</span>
</div> : <></>}
{!(status.paused || config.viewMode) ?
<Tooltip content={t("header." + (status.running ? "running_tooltip" : "start_tooltip"))} position="bottom">
<FontAwesomeIcon icon={faGaugeHigh}
className={"header-icon " + (status.running ? "test-running" : "")}
onClick={startSpeedtest} />
</Tooltip>
: <></>}
{(config.viewMode ? <div className="tooltip-element tooltip-bottom">
<FontAwesomeIcon icon={faLock} className={"header-icon"} onClick={showPasswordDialog} />
<span className="tooltip">{t("header.admin_login")}</span>
</div> : <></>)}
{config.viewMode ?
<Tooltip content={t("header.admin_login")} position="bottom">
<FontAwesomeIcon icon={faLock} className={"header-icon"} onClick={showPasswordDialog} />
</Tooltip>
: <></>}
{(config.previewMode ? <div className="tooltip-element tooltip-bottom">
<FontAwesomeIcon icon={faDownload} className={"header-icon"} onClick={openDownloadPage} />
<span className="tooltip">{t("header.download")}</span>
</div> : <></>)}
{config.previewMode ?
<Tooltip content={t("header.download")} position="bottom">
<FontAwesomeIcon icon={faDownload} className={"header-icon"} onClick={openDownloadPage} />
</Tooltip>
: <></>}
{!config.viewMode && <div className="tooltip-element tooltip-bottom">
<FontAwesomeIcon icon={faServer} className="header-icon" onClick={() => navigate("/nodes")} />
<span className="tooltip">{t("header.servers")}</span>
</div>}
{!config.viewMode &&
<Tooltip content={t("header.servers")} position="bottom">
<FontAwesomeIcon icon={faServer} className="header-icon" onClick={() => navigate("/nodes")} />
</Tooltip>
}
<div className="tooltip-element tooltip-bottom" id="open-header">
<FontAwesomeIcon icon={icon} className="header-icon" onClick={switchDropdown} />
<span className="tooltip">{t("dropdown.settings")}</span>
</div>
<Tooltip content={t("dropdown.settings")} position="bottom">
<div id="open-header">
<FontAwesomeIcon icon={icon} className="header-icon" onClick={switchDropdown} />
</div>
</Tooltip>
</div>
</div>
<DropdownComponent isOpen={isDropdownOpen} switchDropdown={switchDropdown} />

View File

@@ -15,9 +15,7 @@ header
.header-right
display: flex
justify-content: flex-end
.header-right svg
margin-left: 15px
gap: 15px
.header-main *
font-size: 24pt

View File

@@ -1,28 +1,27 @@
import {DialogContext, DialogProvider} from "@/common/contexts/Dialog";
import {Dialog, DialogHeader, DialogBody, DialogFooter} from "@/common/contexts/Dialog";
import {t} from "i18next";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faArrowDown, faArrowUp, faCheck, faClose, faExclamationTriangle, faTableTennis, faWandMagicSparkles} from "@fortawesome/free-solid-svg-icons";
import {faArrowDown, faArrowUp, faCheck, faExclamationTriangle, faTableTennis, faWandMagicSparkles} from "@fortawesome/free-solid-svg-icons";
import "./styles.sass";
import React, {useContext, useEffect, useState} from "react";
import {jsonRequest, patchRequest} from "@/common/utils/RequestUtil";
import {ConfigContext} from "@/common/contexts/Config";
import {ToastNotificationContext} from "@/common/contexts/ToastNotification";
export const Dialog = () => {
const close = useContext(DialogContext);
export const OptimalValuesDialog = ({open, onClose}) => {
const [config, reloadConfig] = useContext(ConfigContext);
const updateToast = useContext(ToastNotificationContext);
const [ping, setPing] = useState(config.ping || "");
const [download, setDownload] = useState(config.download || "");
const [upload, setUpload] = useState(config.upload || "");
const [recommendations, setRecommendations] = useState(null);
useEffect(() => {
if (!open) return;
jsonRequest("/recommendations").then((result) => {
if (!result.message) setRecommendations(result);
}).catch(() => {});
}, []);
}, [open]);
const applyRecommendations = () => {
if (recommendations) {
@@ -32,17 +31,15 @@ export const Dialog = () => {
}
};
const update = async () => {
const update = async (close) => {
if ((ping && /[^0-9.]/.test(ping)) || (download && /[^0-9.]/.test(download)) || (upload && /[^0-9.]/.test(upload))) {
updateToast(t("dropdown.invalid"), "red", faExclamationTriangle);
return;
}
try {
if (ping !== config.ping) await patchRequest("/config/ping", {value: ping});
if (download !== config.download) await patchRequest("/config/download", {value: download});
if (upload !== config.upload) await patchRequest("/config/upload", {value: upload});
reloadConfig();
updateToast(t("dropdown.changes_applied"), "green", faCheck);
close();
@@ -52,65 +49,60 @@ export const Dialog = () => {
};
return (
<>
<div className="dialog-header">
<h4 className="dialog-text">{t("optimal_values.title")}</h4>
<FontAwesomeIcon icon={faClose} className="dialog-text dialog-icon" onClick={() => close()}/>
</div>
<div className="optimal-values-content">
<div className="optimal-values-speeds">
<div className="optimal-values-speed">
<div className="optimal-values-speed-header">
<FontAwesomeIcon icon={faTableTennis}/>
<div className="optimal-values-speed-text">
<h2>{t("latest.ping")}</h2>
<p>{t("welcome.ms")}</p>
<Dialog open={open} onClose={onClose} className="optimal-values-dialog">
{({close}) => (
<>
<DialogHeader onClose={close}>{t("optimal_values.title")}</DialogHeader>
<DialogBody>
<div className="optimal-values-content">
<div className="optimal-values-speeds">
<div className="optimal-values-speed">
<div className="optimal-values-speed-header">
<FontAwesomeIcon icon={faTableTennis}/>
<div className="optimal-values-speed-text">
<h2>{t("latest.ping")}</h2>
<p>{t("welcome.ms")}</p>
</div>
</div>
<input type="number" placeholder={recommendations?.ping || ""} className="dialog-input"
value={ping} onChange={(e) => setPing(e.target.value)}/>
</div>
<div className="optimal-values-speed">
<div className="optimal-values-speed-header">
<FontAwesomeIcon icon={faArrowDown}/>
<div className="optimal-values-speed-text">
<h2>{t("latest.down")}</h2>
<p>{t("welcome.mbps")}</p>
</div>
</div>
<input type="number" placeholder={recommendations?.download || ""} className="dialog-input"
value={download} onChange={(e) => setDownload(e.target.value)}/>
</div>
<div className="optimal-values-speed">
<div className="optimal-values-speed-header">
<FontAwesomeIcon icon={faArrowUp}/>
<div className="optimal-values-speed-text">
<h2>{t("latest.up")}</h2>
<p>{t("welcome.mbps")}</p>
</div>
</div>
<input type="number" placeholder={recommendations?.upload || ""} className="dialog-input"
value={upload} onChange={(e) => setUpload(e.target.value)}/>
</div>
</div>
</div>
<input type="number" placeholder={recommendations?.ping || ""} className="dialog-input"
value={ping} onChange={(e) => setPing(e.target.value)}/>
</div>
<div className="optimal-values-speed">
<div className="optimal-values-speed-header">
<FontAwesomeIcon icon={faArrowDown}/>
<div className="optimal-values-speed-text">
<h2>{t("latest.down")}</h2>
<p>{t("welcome.mbps")}</p>
</div>
</div>
<input type="number" placeholder={recommendations?.download || ""} className="dialog-input"
value={download} onChange={(e) => setDownload(e.target.value)}/>
</div>
<div className="optimal-values-speed">
<div className="optimal-values-speed-header">
<FontAwesomeIcon icon={faArrowUp}/>
<div className="optimal-values-speed-text">
<h2>{t("latest.up")}</h2>
<p>{t("welcome.mbps")}</p>
</div>
</div>
<input type="number" placeholder={recommendations?.upload || ""} className="dialog-input"
value={upload} onChange={(e) => setUpload(e.target.value)}/>
</div>
</div>
</div>
<div className="dialog-buttons">
{recommendations && (
<button className="dialog-btn" onClick={applyRecommendations}>
<FontAwesomeIcon icon={faWandMagicSparkles}/>
<span>{t("optimal_values.use_recommended")}</span>
</button>
)}
<button className="dialog-btn" onClick={update}>{t("dialog.update")}</button>
</div>
</>
);
};
export const OptimalValuesDialog = (props) => {
return (
<DialogProvider close={props.onClose}>
<Dialog/>
</DialogProvider>
</DialogBody>
<DialogFooter>
{recommendations && (
<button className="dialog-btn" onClick={applyRecommendations}>
<FontAwesomeIcon icon={faWandMagicSparkles}/>
<span>{t("optimal_values.use_recommended")}</span>
</button>
)}
<button className="dialog-btn" onClick={() => update(close)}>{t("dialog.update")}</button>
</DialogFooter>
</>
)}
</Dialog>
);
};