mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-06 11:37:02 +03:00
399 lines
14 KiB
TypeScript
399 lines
14 KiB
TypeScript
import * as React from "react";
|
|
import Config from "../config";
|
|
|
|
enum CountdownMode {
|
|
Timer,
|
|
Paused,
|
|
Stopped
|
|
}
|
|
|
|
export interface NoticeProps {
|
|
noticeTitle: string;
|
|
|
|
maxCountdownTime?: () => number;
|
|
dontPauseCountdown?: boolean;
|
|
amountOfPreviousNotices?: number;
|
|
showInSecondSlot?: boolean;
|
|
timed?: boolean;
|
|
idSuffix?: string;
|
|
|
|
fadeIn?: boolean;
|
|
startFaded?: boolean;
|
|
firstColumn?: React.ReactElement[] | React.ReactElement;
|
|
firstRow?: React.ReactElement;
|
|
bottomRow?: React.ReactElement[];
|
|
|
|
smaller?: boolean;
|
|
limitWidth?: boolean;
|
|
extraClass?: string;
|
|
hideLogo?: boolean;
|
|
hideRightInfo?: boolean;
|
|
|
|
// Callback for when this is closed
|
|
closeListener: () => void;
|
|
onMouseEnter?: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
|
|
|
zIndex?: number;
|
|
style?: React.CSSProperties;
|
|
biggerCloseButton?: boolean;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
export interface NoticeState {
|
|
maxCountdownTime: () => number;
|
|
|
|
countdownTime: number;
|
|
countdownMode: CountdownMode;
|
|
|
|
mouseHovering: boolean;
|
|
|
|
startFaded: boolean;
|
|
}
|
|
|
|
class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
|
|
countdownInterval: NodeJS.Timeout;
|
|
|
|
idSuffix: string;
|
|
|
|
amountOfPreviousNotices: number;
|
|
|
|
parentRef: React.RefObject<HTMLDivElement>;
|
|
|
|
constructor(props: NoticeProps) {
|
|
super(props);
|
|
|
|
this.parentRef = React.createRef();
|
|
|
|
const maxCountdownTime = () => {
|
|
if (this.props.maxCountdownTime) return this.props.maxCountdownTime();
|
|
else return Config.config.skipNoticeDuration;
|
|
};
|
|
|
|
//the id for the setInterval running the countdown
|
|
this.countdownInterval = null;
|
|
|
|
this.amountOfPreviousNotices = props.amountOfPreviousNotices || 0;
|
|
|
|
this.idSuffix = props.idSuffix || "";
|
|
|
|
// Setup state
|
|
this.state = {
|
|
maxCountdownTime,
|
|
|
|
//the countdown until this notice closes
|
|
countdownTime: maxCountdownTime(),
|
|
countdownMode: CountdownMode.Timer,
|
|
mouseHovering: false,
|
|
|
|
startFaded: this.props.startFaded ?? false
|
|
}
|
|
}
|
|
|
|
componentDidMount(): void {
|
|
this.startCountdown();
|
|
}
|
|
|
|
render(): React.ReactElement {
|
|
const noticeStyle: React.CSSProperties = {
|
|
zIndex: this.props.zIndex || (1000 + this.amountOfPreviousNotices),
|
|
...(this.props.style ?? {})
|
|
}
|
|
|
|
return (
|
|
<div id={"sponsorSkipNotice" + this.idSuffix}
|
|
className={"sponsorSkipObject sponsorSkipNoticeParent"
|
|
+ (this.props.showInSecondSlot ? " secondSkipNotice" : "")
|
|
+ (this.props.extraClass ? ` ${this.props.extraClass}` : "")}
|
|
onMouseEnter={(e) => this.onMouseEnter(e) }
|
|
onMouseLeave={() => this.timerMouseLeave()}
|
|
ref={this.parentRef}
|
|
style={noticeStyle} >
|
|
<div className={"sponsorSkipNoticeTableContainer"
|
|
+ (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "")
|
|
+ (this.state.startFaded ? " sponsorSkipNoticeFaded" : "") }>
|
|
<table className={"sponsorSkipObject sponsorSkipNotice"
|
|
+ (this.props.limitWidth ? " sponsorSkipNoticeLimitWidth" : "")}>
|
|
<tbody>
|
|
|
|
{/* First row */}
|
|
<tr id={"sponsorSkipNoticeFirstRow" + this.idSuffix}
|
|
className="sponsorSkipNoticeFirstRow">
|
|
{/* Left column */}
|
|
<td className="noticeLeftIcon">
|
|
{/* Logo */}
|
|
{!this.props.hideLogo &&
|
|
<img id={"sponsorSkipLogo" + this.idSuffix}
|
|
className="sponsorSkipLogo sponsorSkipObject"
|
|
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
|
|
</img>
|
|
}
|
|
|
|
<span id={"sponsorSkipMessage" + this.idSuffix}
|
|
style={{float: "left", marginRight: this.props.hideLogo ? "0px" : null}}
|
|
className="sponsorSkipMessage sponsorSkipObject">
|
|
|
|
{this.props.noticeTitle}
|
|
</span>
|
|
|
|
{this.props.firstColumn}
|
|
</td>
|
|
|
|
{this.props.firstRow}
|
|
|
|
{/* Right column */}
|
|
{!this.props.hideRightInfo &&
|
|
<td className="sponsorSkipNoticeRightSection"
|
|
style={{top: "9.32px"}}>
|
|
|
|
{/* Time left */}
|
|
{this.props.timed ? (
|
|
<span id={"sponsorSkipNoticeTimeLeft" + this.idSuffix}
|
|
onClick={() => this.toggleManualPause()}
|
|
className="sponsorSkipObject sponsorSkipNoticeTimeLeft">
|
|
|
|
{this.getCountdownElements()}
|
|
|
|
</span>
|
|
) : ""}
|
|
|
|
|
|
{/* Close button */}
|
|
<img src={chrome.extension.getURL("icons/close.png")}
|
|
className={"sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeCloseButton sponsorSkipNoticeRightButton"
|
|
+ (this.props.biggerCloseButton ? " biggerCloseButton" : "")}
|
|
onClick={() => this.close()}>
|
|
</img>
|
|
</td>
|
|
}
|
|
</tr>
|
|
|
|
{this.props.children}
|
|
|
|
{!this.props.smaller && this.props.bottomRow ?
|
|
this.props.bottomRow
|
|
: null}
|
|
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* Add as a hidden table to keep the height constant */}
|
|
{this.props.smaller && this.props.bottomRow ?
|
|
<table style={{visibility: "hidden", paddingTop: "14px"}}>
|
|
<tbody>
|
|
{this.props.bottomRow}
|
|
</tbody>
|
|
</table>
|
|
: null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
getCountdownElements(): React.ReactElement[] {
|
|
return [(
|
|
<span
|
|
id={"skipNoticeTimerText" + this.idSuffix}
|
|
key="skipNoticeTimerText"
|
|
className={this.state.countdownMode !== CountdownMode.Timer ? "hidden" : ""} >
|
|
{this.state.countdownTime + "s"}
|
|
</span>
|
|
),(
|
|
<img
|
|
id={"skipNoticeTimerPaused" + this.idSuffix}
|
|
key="skipNoticeTimerPaused"
|
|
className={this.state.countdownMode !== CountdownMode.Paused ? "hidden" : ""}
|
|
src={chrome.runtime.getURL("icons/pause.svg")}
|
|
alt={chrome.i18n.getMessage("paused")} />
|
|
),(
|
|
<img
|
|
id={"skipNoticeTimerStopped" + this.idSuffix}
|
|
key="skipNoticeTimerStopped"
|
|
className={this.state.countdownMode !== CountdownMode.Stopped ? "hidden" : ""}
|
|
src={chrome.runtime.getURL("icons/stop.svg")}
|
|
alt={chrome.i18n.getMessage("manualPaused")} />
|
|
)];
|
|
}
|
|
|
|
onMouseEnter(event: React.MouseEvent<HTMLElement, MouseEvent>): void {
|
|
if (this.props.onMouseEnter) this.props.onMouseEnter(event);
|
|
|
|
this.fadedMouseEnter();
|
|
this.timerMouseEnter();
|
|
}
|
|
|
|
fadedMouseEnter(): void {
|
|
if (this.state.startFaded) {
|
|
this.setState({
|
|
startFaded: false
|
|
});
|
|
}
|
|
}
|
|
|
|
timerMouseEnter(): void {
|
|
if (this.state.countdownMode === CountdownMode.Stopped) return;
|
|
|
|
this.pauseCountdown();
|
|
|
|
this.setState({
|
|
mouseHovering: true
|
|
});
|
|
}
|
|
|
|
timerMouseLeave(): void {
|
|
if (this.state.countdownMode === CountdownMode.Stopped) return;
|
|
|
|
this.startCountdown();
|
|
|
|
this.setState({
|
|
mouseHovering: false
|
|
});
|
|
}
|
|
|
|
toggleManualPause(): void {
|
|
this.setState({
|
|
countdownMode: this.state.countdownMode === CountdownMode.Stopped ? CountdownMode.Timer : CountdownMode.Stopped
|
|
}, () => {
|
|
if (this.state.countdownMode === CountdownMode.Stopped || this.state.mouseHovering) {
|
|
this.pauseCountdown();
|
|
} else {
|
|
this.startCountdown();
|
|
}
|
|
});
|
|
}
|
|
|
|
//called every second to lower the countdown before hiding the notice
|
|
countdown(): void {
|
|
if (!this.props.timed) return;
|
|
|
|
const countdownTime = Math.min(this.state.countdownTime - 1, this.state.maxCountdownTime());
|
|
|
|
if (countdownTime <= 0) {
|
|
//remove this from setInterval
|
|
clearInterval(this.countdownInterval);
|
|
|
|
//time to close this notice
|
|
this.close();
|
|
|
|
return;
|
|
}
|
|
|
|
if (countdownTime == 3) {
|
|
//start fade out animation
|
|
const notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
|
|
notice?.style.removeProperty("animation");
|
|
notice?.classList.add("sponsorSkipNoticeFadeOut");
|
|
}
|
|
|
|
this.setState({
|
|
countdownTime
|
|
})
|
|
}
|
|
|
|
removeFadeAnimation(): void {
|
|
//remove the fade out class if it exists
|
|
const notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
|
|
notice.classList.remove("sponsorSkipNoticeFadeOut");
|
|
notice.style.animation = "none";
|
|
}
|
|
|
|
pauseCountdown(): void {
|
|
if (!this.props.timed || this.props.dontPauseCountdown) return;
|
|
|
|
//remove setInterval
|
|
if (this.countdownInterval) clearInterval(this.countdownInterval);
|
|
this.countdownInterval = null;
|
|
|
|
//reset countdown and inform the user
|
|
this.setState({
|
|
countdownTime: this.state.maxCountdownTime(),
|
|
countdownMode: this.state.countdownMode === CountdownMode.Timer ? CountdownMode.Paused : this.state.countdownMode
|
|
});
|
|
|
|
this.removeFadeAnimation();
|
|
}
|
|
|
|
startCountdown(): void {
|
|
if (!this.props.timed) return;
|
|
|
|
//if it has already started, don't start it again
|
|
if (this.countdownInterval !== null) return;
|
|
|
|
this.setState({
|
|
countdownTime: this.state.maxCountdownTime(),
|
|
countdownMode: CountdownMode.Timer
|
|
});
|
|
|
|
this.setupInterval();
|
|
}
|
|
|
|
setupInterval(): void {
|
|
if (this.countdownInterval) clearInterval(this.countdownInterval);
|
|
|
|
this.countdownInterval = setInterval(this.countdown.bind(this), 1000);
|
|
}
|
|
|
|
resetCountdown(): void {
|
|
if (!this.props.timed) return;
|
|
|
|
this.setupInterval();
|
|
|
|
this.setState({
|
|
countdownTime: this.state.maxCountdownTime(),
|
|
countdownMode: CountdownMode.Timer
|
|
});
|
|
|
|
this.removeFadeAnimation();
|
|
}
|
|
|
|
/**
|
|
* @param silent If true, the close listener will not be called
|
|
*/
|
|
close(silent?: boolean): void {
|
|
//remove setInterval
|
|
if (this.countdownInterval !== null) clearInterval(this.countdownInterval);
|
|
|
|
if (!silent) this.props.closeListener();
|
|
}
|
|
|
|
addNoticeInfoMessage(message: string, message2 = ""): void {
|
|
//TODO: Replace
|
|
|
|
const previousInfoMessage = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix);
|
|
if (previousInfoMessage != null) {
|
|
//remove it
|
|
document.getElementById("sponsorSkipNotice" + this.idSuffix).removeChild(previousInfoMessage);
|
|
}
|
|
|
|
const previousInfoMessage2 = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix + "2");
|
|
if (previousInfoMessage2 != null) {
|
|
//remove it
|
|
document.getElementById("sponsorSkipNotice" + this.idSuffix).removeChild(previousInfoMessage2);
|
|
}
|
|
|
|
//add info
|
|
const thanksForVotingText = document.createElement("p");
|
|
thanksForVotingText.id = "sponsorTimesInfoMessage" + this.idSuffix;
|
|
thanksForVotingText.className = "sponsorTimesInfoMessage";
|
|
thanksForVotingText.innerText = message;
|
|
|
|
//add element to div
|
|
document.querySelector("#sponsorSkipNotice" + this.idSuffix + " > tbody").insertBefore(thanksForVotingText, document.getElementById("sponsorSkipNoticeSpacer" + this.idSuffix));
|
|
|
|
if (message2 !== undefined) {
|
|
const thanksForVotingText2 = document.createElement("p");
|
|
thanksForVotingText2.id = "sponsorTimesInfoMessage" + this.idSuffix + "2";
|
|
thanksForVotingText2.className = "sponsorTimesInfoMessage";
|
|
thanksForVotingText2.innerText = message2;
|
|
|
|
//add element to div
|
|
document.querySelector("#sponsorSkipNotice" + this.idSuffix + " > tbody").insertBefore(thanksForVotingText2, document.getElementById("sponsorSkipNoticeSpacer" + this.idSuffix));
|
|
}
|
|
}
|
|
|
|
getElement(): React.RefObject<HTMLDivElement> {
|
|
return this.parentRef;
|
|
}
|
|
}
|
|
|
|
export default NoticeComponent;
|