Rewrite popup in react

This commit is contained in:
Ajay
2025-06-02 04:11:52 -04:00
parent d7db6d58c5
commit e4a050f905
14 changed files with 1446 additions and 208 deletions

View File

@@ -0,0 +1,207 @@
import * as React from "react";
import { getHash } from "../../maze-utils/src/hash";
import { getErrorMessage } from "../../maze-utils/src/formating";
import Config from "../config";
import { asyncRequestToServer } from "../utils/requests";
import PencilIcon from "../svg-icons/pencilIcon";
import ClipboardIcon from "../svg-icons/clipboardIcon";
import CheckIcon from "../svg-icons/checkIcon";
export const YourWorkComponent = () => {
const [isSettingUsername, setIsSettingUsername] = React.useState(false);
const [username, setUsername] = React.useState("");
const [newUsername, setNewUsername] = React.useState("");
const [usernameSubmissionStatus, setUsernameSubmissionStatus] = React.useState("");
const [submissionCount, setSubmissionCount] = React.useState("");
const [viewCount, setViewCount] = React.useState(0);
const [minutesSaved, setMinutesSaved] = React.useState(0);
const [showDonateMessage, setShowDonateMessage] = React.useState(false);
React.useEffect(() => {
(async () => {
const values = ["userName", "viewCount", "minutesSaved", "vip", "permissions", "segmentCount"];
const result = await asyncRequestToServer("GET", "/api/userInfo", {
publicUserID: await getHash(Config.config!.userID!),
values
});
if (result.ok) {
const userInfo = JSON.parse(result.responseText);
setUsername(userInfo.userName);
setSubmissionCount(Math.max(Config.config.sponsorTimesContributed ?? 0, userInfo.segmentCount).toLocaleString());
setViewCount(userInfo.viewCount);
setMinutesSaved(userInfo.minutesSaved);
Config.config!.isVip = userInfo.vip;
Config.config!.permissions = userInfo.permissions;
setShowDonateMessage(Config.config.showDonationLink && Config.config.donateClicked <= 0 && Config.config.showPopupDonationCount < 5
&& viewCount < 50000 && !Config.config.isVip && Config.config.skipCount > 10);
}
})();
}, []);
return (
<div className="sbYourWorkBox">
<h2 className="sbHeader" style={{ "padding": "8px 15px" }}>
{chrome.i18n.getMessage("yourWork")}
</h2>
<div className="sbYourWorkCols">
{/* Username */}
<div id="usernameElement">
<p className="u-mZ grey-text">
{chrome.i18n.getMessage("Username")}:
{/* loading/errors */}
<span id="setUsernameStatus"
className={`u-mZ white-text${!usernameSubmissionStatus ? " hidden" : ""}`}>
{usernameSubmissionStatus}
</span>
</p>
<div id="setUsernameContainer" className={isSettingUsername ? " hidden" : ""}>
<p id="usernameValue">{username}</p>
<button id="setUsernameButton"
title={chrome.i18n.getMessage("setUsername")}
onClick={() => {
setNewUsername(username);
setIsSettingUsername(!isSettingUsername);
}}>
<PencilIcon id="sbPopupIconEdit" className="sbPopupButton" />
</button>
<button id="copyUserID"
title={chrome.i18n.getMessage("copyPublicID")}
onClick={async () => {
window.navigator.clipboard.writeText(await getHash(Config.config!.userID!));
}}>
<ClipboardIcon id="sbPopupIconCopyUserID" className="sbPopupButton" />
</button>
</div>
<div id="setUsername" className={!isSettingUsername ? " hidden" : " SBExpanded"}>
<input id="usernameInput"
placeholder={chrome.i18n.getMessage("Username")}
value={newUsername}
onChange={(e) => {
setNewUsername(e.target.value);
}}/>
<button id="submitUsername"
onClick={() => {
if (newUsername.length > 0) {
setUsernameSubmissionStatus(chrome.i18n.getMessage("Loading"));
asyncRequestToServer("POST", `/api/setUsername?userID=${Config.config!.userID}&username=${newUsername}`)
.then((result) => {
if (result.ok) {
setUsernameSubmissionStatus("");
setUsername(newUsername);
setIsSettingUsername(!isSettingUsername);
} else {
setUsernameSubmissionStatus(getErrorMessage(result.status, result.responseText));
}
}).catch((e) => {
setUsernameSubmissionStatus(`${chrome.i18n.getMessage("Error")}: ${e}`);
});
}
}}>
<CheckIcon id="sbPopupIconCheck" className="sbPopupButton" />
</button>
</div>
</div>
<SubmissionCounts
isSettingUsername={isSettingUsername}
submissionCount={submissionCount}
/>
</div>
<TimeSavedMessage
viewCount={viewCount}
minutesSaved={minutesSaved}
/>
{showDonateMessage && <DonateMessage onClose={() => {
setShowDonateMessage(false);
Config.config.showPopupDonationCount = 100;
}} />}
</div>
);
};
function SubmissionCounts(props: { isSettingUsername: boolean; submissionCount: string }): JSX.Element {
return <>
<div id="sponsorTimesContributionsContainer" className={props.isSettingUsername ? " hidden" : ""}>
<p className="u-mZ grey-text">
{chrome.i18n.getMessage("Submissions")}:
</p>
<p id="sponsorTimesContributionsDisplay" className="u-mZ">{props.submissionCount}</p>
</div>
</>
}
function TimeSavedMessage({ viewCount, minutesSaved }: { viewCount: number; minutesSaved: number }): JSX.Element {
return (
<>
{
viewCount > 0 &&
<p id="sponsorTimesViewsContainer" className="u-mZ sbStatsSentence">
{chrome.i18n.getMessage("savedPeopleFrom")}
<b>
<span id="sponsorTimesViewsDisplay">{viewCount.toLocaleString()}</span>{" "}
</b>
<span id="sponsorTimesViewsDisplayEndWord">{viewCount !== 1 ? chrome.i18n.getMessage("Segments") : chrome.i18n.getMessage("Segment")}</span>
<br />
<span className="sbExtraInfo">
{"("}{" "}
<b>
<span id="sponsorTimesOthersTimeSavedDisplay">{getFormattedHours(minutesSaved)}</span>{" "}
<span id="sponsorTimesOthersTimeSavedEndWord">{minutesSaved !== 1 ? chrome.i18n.getMessage("minsLower") : chrome.i18n.getMessage("minLower")}</span>{" "}
</b>
<span>{chrome.i18n.getMessage("youHaveSavedTimeEnd")}</span>{" "}
{" )"}
</span>
</p>
}
<p id="sponsorTimesSkipsDoneContainer" className="u-mZ sbStatsSentence">
{chrome.i18n.getMessage("youHaveSkipped")}
<b>
<span id="sponsorTimesSkipsDoneDisplay">{Config.config.skipCount}</span>{" "}
</b>
<span id="sponsorTimesSkipsDoneEndWord">{Config.config.skipCount > 1 ? chrome.i18n.getMessage("Segments") : chrome.i18n.getMessage("Segment")}</span>{" "}
<span className="sbExtraInfo">
{"("}{" "}
<b>
<span id="sponsorTimeSavedDisplay">{Config.config.minutesSaved}</span>{" "}
<span id="sponsorTimeSavedEndWord">{Config.config.minutesSaved !== 1 ? chrome.i18n.getMessage("minsLower") : chrome.i18n.getMessage("minLower")}</span>{" "}
</b>
{")"}
</span>
</p>
</>
);
}
function DonateMessage(props: { onClose: () => void }): JSX.Element {
return (
<div id="sponsorTimesDonateContainer" style={{ alignItems: "center", justifyContent: "center" }}>
<img className="sbHeart" src="/icons/heart.svg" alt="Heart icon" />
<a id="sbConsiderDonateLink" href="https://sponsor.ajay.app/donate" target="_blank" rel="noreferrer" onClick={() => {
Config.config.donateClicked = Config.config.donateClicked + 1;
}}>
{chrome.i18n.getMessage("considerDonating")}
</a>
<img id="sbCloseDonate" src="/icons/close.png" alt={chrome.i18n.getMessage("closeIcon")} height="8" style={{ paddingLeft: "5px", cursor: "pointer" }} onClick={props.onClose} />
</div>
);
}
/**
* Converts time in minutes to 2d 5h 25.1
* If less than 1 hour, just returns minutes
*
* @param {float} minutes
* @returns {string}
*/
function getFormattedHours(minutes) {
minutes = Math.round(minutes * 10) / 10;
const years = Math.floor(minutes / 525600); // Assumes 365.0 days in a year
const days = Math.floor(minutes / 1440) % 365;
const hours = Math.floor(minutes / 60) % 24;
return (years > 0 ? years + chrome.i18n.getMessage("yearAbbreviation") + " " : "") + (days > 0 ? days + chrome.i18n.getMessage("dayAbbreviation") + " " : "") + (hours > 0 ? hours + chrome.i18n.getMessage("hourAbbreviation") + " " : "") + (minutes % 60).toFixed(1);
}