Added basic react submission confirmation notice

This commit is contained in:
Ajay Ramachandran
2020-03-11 17:50:50 -04:00
parent a02aef591e
commit a182354254
10 changed files with 307 additions and 38 deletions

View File

@@ -425,5 +425,23 @@
}, },
"mobileUpdateInfo": { "mobileUpdateInfo": {
"message": "m.youtube.com is now supported" "message": "m.youtube.com is now supported"
},
"confirmNoticeTitle" : {
"message": "Submit Segment"
},
"submit": {
"message": "Submit"
},
"cancel": {
"message": "Cancel"
},
"delete": {
"message": "Delete"
},
"preview": {
"message": "Preview"
},
"edit": {
"message": "Edit"
} }
} }

View File

@@ -80,13 +80,15 @@
border-radius: 5px; border-radius: 5px;
animation: fadeIn 0.5s;
border-spacing: 5px 10px; border-spacing: 5px 10px;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
} }
.sponsorSkipNoticeFadeIn {
animation: fadeIn 0.5s;
}
.sponsorSkipNoticeFadeOut { .sponsorSkipNoticeFadeOut {
animation: fadeOut 3s cubic-bezier(0.55, 0.055, 0.675, 0.19); animation: fadeOut 3s cubic-bezier(0.55, 0.055, 0.675, 0.19);
} }

View File

@@ -6,12 +6,16 @@ export interface NoticeProps {
maxCountdownTime?: () => number, maxCountdownTime?: () => number,
amountOfPreviousNotices?: number, amountOfPreviousNotices?: number,
timed?: boolean, timed?: boolean,
idSuffix: string idSuffix?: string,
fadeIn?: boolean
} }
export interface NoticeState { export interface NoticeState {
noticeTitle: string, noticeTitle: string,
maxCountdownTime?: () => number,
countdownTime: number, countdownTime: number,
countdownText: string, countdownText: string,
} }
@@ -25,21 +29,23 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
constructor(props: NoticeProps) { constructor(props: NoticeProps) {
super(props); super(props);
if (props.maxCountdownTime === undefined) props.maxCountdownTime = () => 4; let maxCountdownTime = props.maxCountdownTime || (() => 4);
//the id for the setInterval running the countdown //the id for the setInterval running the countdown
this.countdownInterval = null; this.countdownInterval = null;
this.amountOfPreviousNotices = props.amountOfPreviousNotices || 0; this.amountOfPreviousNotices = props.amountOfPreviousNotices || 0;
this.idSuffix = props.idSuffix; this.idSuffix = props.idSuffix || "";
// Setup state // Setup state
this.state = { this.state = {
noticeTitle: props.noticeTitle, noticeTitle: props.noticeTitle,
maxCountdownTime,
//the countdown until this notice closes //the countdown until this notice closes
countdownTime: props.maxCountdownTime(), countdownTime: maxCountdownTime(),
countdownText: null, countdownText: null,
} }
} }
@@ -55,7 +61,8 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
return ( return (
<table id={"sponsorSkipNotice" + this.idSuffix} <table id={"sponsorSkipNotice" + this.idSuffix}
className="sponsorSkipObject sponsorSkipNotice" style={noticeStyle} className={"sponsorSkipObject sponsorSkipNotice" + (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "")}
style={noticeStyle}
onMouseEnter={this.pauseCountdown.bind(this)} onMouseEnter={this.pauseCountdown.bind(this)}
onMouseLeave={this.startCountdown.bind(this)}> <tbody> onMouseLeave={this.startCountdown.bind(this)}> <tbody>
@@ -141,7 +148,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
//reset countdown and inform the user //reset countdown and inform the user
this.setState({ this.setState({
countdownTime: this.props.maxCountdownTime(), countdownTime: this.state.maxCountdownTime(),
countdownText: chrome.i18n.getMessage("paused") countdownText: chrome.i18n.getMessage("paused")
}); });
@@ -158,7 +165,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
if (this.countdownInterval !== null) return; if (this.countdownInterval !== null) return;
this.setState({ this.setState({
countdownTime: this.props.maxCountdownTime(), countdownTime: this.state.maxCountdownTime(),
countdownText: null countdownText: null
}); });
@@ -169,7 +176,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
if (!this.props.timed) return; if (!this.props.timed) return;
this.setState({ this.setState({
countdownTime: this.props.maxCountdownTime(), countdownTime: this.state.maxCountdownTime(),
countdownText: null countdownText: null
}); });
} }

View File

@@ -10,10 +10,6 @@ export interface NoticeTextSelectionState {
} }
class NoticeTextSelectionComponent extends React.Component<NoticeTextSelectionProps, NoticeTextSelectionState> { class NoticeTextSelectionComponent extends React.Component<NoticeTextSelectionProps, NoticeTextSelectionState> {
countdownInterval: NodeJS.Timeout;
idSuffix: any;
amountOfPreviousNotices: number;
constructor(props: NoticeTextSelectionProps) { constructor(props: NoticeTextSelectionProps) {
super(props); super(props);

View File

@@ -91,6 +91,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
<NoticeComponent noticeTitle={this.state.noticeTitle} <NoticeComponent noticeTitle={this.state.noticeTitle}
amountOfPreviousNotices={this.amountOfPreviousNotices} amountOfPreviousNotices={this.amountOfPreviousNotices}
idSuffix={this.idSuffix} idSuffix={this.idSuffix}
fadeIn={true}
timed={true} timed={true}
maxCountdownTime={this.state.maxCountdownTime} maxCountdownTime={this.state.maxCountdownTime}
ref={this.noticeRef}> ref={this.noticeRef}>
@@ -167,7 +168,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
for (let i = 0; i < this.state.messages.length; i++) { for (let i = 0; i < this.state.messages.length; i++) {
elements.push( elements.push(
<NoticeTextSelectionComponent idSuffix={this.idSuffix} <NoticeTextSelectionComponent idSuffix={this.idSuffix}
text={this.state.messages[i]}> text={this.state.messages[i]}
key={i}>
</NoticeTextSelectionComponent> </NoticeTextSelectionComponent>
) )
} }

View File

@@ -0,0 +1,46 @@
import * as React from "react";
export interface SponsorTimeEditProps {
index: number,
idSuffix: string,
// Contains functions and variables from the content script needed by the skip notice
contentContainer: () => any;
}
export interface SponsorTimeEditState {
}
class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, SponsorTimeEditState> {
constructor(props: SponsorTimeEditProps) {
super(props);
}
render() {
return (
<>
<div id={"sponsorTimesContainer" + this.props.index + this.props.idSuffix}
className="sponsorTime">
{this.props.contentContainer().sponsorTimesSubmitting[this.props.index][0]
+ " to " + this.props.contentContainer().sponsorTimesSubmitting[this.props.index][1]}
</div>
<span id={"sponsorTimeDeleteButton" + this.props.index + this.props.idSuffix}>
{chrome.i18n.getMessage("delete")}
</span>
<span id={"sponsorTimePreviewButton" + this.props.index + this.props.idSuffix}>
{chrome.i18n.getMessage("preview")}
</span>
<span id={"sponsorTimeEditButton" + this.props.index + this.props.idSuffix}>
{chrome.i18n.getMessage("edit")}
</span>
</>
);
}
}
export default SponsorTimeEditComponent;

View File

@@ -0,0 +1,141 @@
import * as React from "react";
import Config from "../config"
import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
import SponsorTimeEditComponent from "./SponsorTimeEditComponent";
export interface SkipNoticeProps {
// Contains functions and variables from the content script needed by the skip notice
contentContainer: () => any;
callback: () => any;
}
export interface SkipNoticeState {
noticeTitle: string,
messages: string[],
idSuffix: string;
}
class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeState> {
// Contains functions and variables from the content script needed by the skip notice
contentContainer: () => any;
callback: () => any;
noticeRef: React.MutableRefObject<NoticeComponent>;
constructor(props: SkipNoticeProps) {
super(props);
this.noticeRef = React.createRef();
this.contentContainer = props.contentContainer;
this.callback = props.callback;
let noticeTitle = chrome.i18n.getMessage("confirmNoticeTitle");
// Setup state
this.state = {
noticeTitle,
messages: [],
idSuffix: "SubmissionNotice"
}
}
render() {
let noticeStyle: React.CSSProperties = {};
if (this.contentContainer().onMobileYouTube) {
noticeStyle.bottom = "4em";
noticeStyle.transform = "scale(0.8) translate(10%, 10%)";
}
return (
<NoticeComponent noticeTitle={this.state.noticeTitle}
idSuffix={this.state.idSuffix}
ref={this.noticeRef}>
{/* Text Boxes */}
{this.getMessageBoxes()}
{/* Sponsor Time List */}
<tr id={"sponsorSkipNoticeMiddleRow" + this.state.idSuffix}>
<td>
{this.getSponsorTimeMessages()}
</td>
</tr>
{/* Last Row */}
<tr id={"sponsorSkipNoticeSecondRow" + this.state.idSuffix}>
<td className="sponsorSkipNoticeRightSection"
style={{position: "relative"}}>
{/* Cancel Button */}
<button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton"
onClick={this.cancel.bind(this)}>
{chrome.i18n.getMessage("cancel")}
</button>
{/* Submit Button */}
<button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton"
onClick={this.submit.bind(this)}>
{chrome.i18n.getMessage("submit")}
</button>
</td>
</tr>
</NoticeComponent>
);
}
getSponsorTimeMessages(): JSX.Element[] | JSX.Element {
let elements: JSX.Element[] = [];
let sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
for (let i = 0; i < sponsorTimes.length; i++) {
elements.push(
<SponsorTimeEditComponent key={i}
idSuffix={this.state.idSuffix}
index={i}
contentContainer={this.props.contentContainer}>
</SponsorTimeEditComponent>
)
}
return elements;
}
getMessageBoxes(): JSX.Element[] | JSX.Element {
let elements: JSX.Element[] = [];
for (let i = 0; i < this.state.messages.length; i++) {
elements.push(
<NoticeTextSelectionComponent idSuffix={this.state.idSuffix}
text={this.state.messages[i]}
key={i}>
</NoticeTextSelectionComponent>
)
}
return elements;
}
cancel() {
this.noticeRef.current.close();
this.contentContainer().resetSponsorSubmissionNotice();
}
submit() {
this.props.callback();
this.cancel();
}
}
export default SkipNoticeComponent;

View File

@@ -8,6 +8,7 @@ import runThePopup from "./popup";
import PreviewBar from "./js-components/previewBar"; import PreviewBar from "./js-components/previewBar";
import SkipNotice from "./render/SkipNotice"; import SkipNotice from "./render/SkipNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent"; import SkipNoticeComponent from "./components/SkipNoticeComponent";
import SubmissionNotice from "./render/SubmissionNotice";
// Hack to get the CSS loaded on permission-based sites (Invidious) // Hack to get the CSS loaded on permission-based sites (Invidious)
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS); utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
@@ -87,18 +88,23 @@ var sponsorTimesSubmitting = [];
//this is used to close the popup on YouTube when the other popup opens //this is used to close the popup on YouTube when the other popup opens
var popupInitialised = false; var popupInitialised = false;
var submissionNotice: SubmissionNotice = null;
// Contains all of the functions and variables needed by the skip notice // Contains all of the functions and variables needed by the skip notice
var skipNoticeContentContainer = () => ({ var skipNoticeContentContainer = () => ({
vote, vote,
dontShowNoticeAgain, dontShowNoticeAgain,
unskipSponsorTime, unskipSponsorTime,
sponsorTimes, sponsorTimes,
sponsorTimesSubmitting,
UUIDs, UUIDs,
v: video, v: video,
reskipSponsorTime, reskipSponsorTime,
hiddenSponsorTimes, hiddenSponsorTimes,
updatePreviewBar, updatePreviewBar,
onMobileYouTube onMobileYouTube,
sponsorSubmissionNotice: submissionNotice,
resetSponsorSubmissionNotice
}); });
//get messages from the background script and the popup //get messages from the background script and the popup
@@ -1205,12 +1211,21 @@ function sponsorMessageStarted(callback) {
toggleStartSponsorButton(); toggleStartSponsorButton();
} }
/**
* Helper method for the submission notice to clear itself when it closes
*/
function resetSponsorSubmissionNotice() {
submissionNotice = null;
}
function submitSponsorTimes() { function submitSponsorTimes() {
if (document.getElementById("submitButton").style.display == "none") { if (document.getElementById("submitButton").style.display == "none") {
//don't submit, not ready //don't submit, not ready
return; return;
} }
if (submissionNotice !== null) return;
//it can't update to this info yet //it can't update to this info yet
closeInfoMenu(); closeInfoMenu();
@@ -1242,11 +1257,7 @@ function submitSponsorTimes() {
} }
} }
let confirmMessage = chrome.i18n.getMessage("submitCheck") + "\n\n" + getSponsorTimesMessage(sponsorTimes) submissionNotice = new SubmissionNotice(skipNoticeContentContainer, sendSubmitMessage);
+ "\n\n" + chrome.i18n.getMessage("confirmMSG") + "\n\n" + chrome.i18n.getMessage("guildlinesSummary");
if(!confirm(confirmMessage)) return;
sendSubmitMessage();
} }
} }
@@ -1314,7 +1325,7 @@ function getSponsorTimesMessage(sponsorTimes) {
for (let i = 0; i < sponsorTimes.length; i++) { for (let i = 0; i < sponsorTimes.length; i++) {
for (let s = 0; s < sponsorTimes[i].length; s++) { for (let s = 0; s < sponsorTimes[i].length; s++) {
let timeMessage = getFormattedTime(sponsorTimes[i][s]); let timeMessage = utils.getFormattedTime(sponsorTimes[i][s]);
//if this is an end time //if this is an end time
if (s == 1) { if (s == 1) {
timeMessage = " to " + timeMessage; timeMessage = " to " + timeMessage;
@@ -1377,22 +1388,6 @@ function addCSS() {
} }
} }
//converts time in seconds to minutes:seconds
function getFormattedTime(seconds) {
let minutes = Math.floor(seconds / 60);
let secondsNum: number = Math.round(seconds - minutes * 60);
let secondsDisplay: string = String(secondsNum);
if (secondsNum < 10) {
//add a zero
secondsDisplay = "0" + secondsNum;
}
let formatted = minutes + ":" + secondsDisplay;
return formatted;
}
function sendRequestToCustomServer(type, fullAddress, callback) { function sendRequestToCustomServer(type, fullAddress, callback) {
let xmlhttp = new XMLHttpRequest(); let xmlhttp = new XMLHttpRequest();

View File

@@ -0,0 +1,47 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import SubmissionNoticeComponent from "../components/SubmissionNoticeComponent";
class SubmissionNotice {
// Contains functions and variables from the content script needed by the skip notice
contentContainer: () => any;
callback: () => any;
constructor(contentContainer: () => any, callback: () => any) {
this.contentContainer = contentContainer;
this.callback = callback;
//get reference node
let referenceNode = document.getElementById("player-container-id")
|| document.getElementById("movie_player") || document.querySelector("#player-container .video-js");
if (referenceNode == null) {
//for embeds
let player = document.getElementById("player");
referenceNode = player.firstChild as HTMLElement;
let index = 1;
//find the child that is the video player (sometimes it is not the first)
while (!referenceNode.classList.contains("html5-video-player") || !referenceNode.classList.contains("ytp-embed")) {
referenceNode = player.children[index] as HTMLElement;
index++;
}
}
let noticeElement = document.createElement("div");
noticeElement.id = "submissionNoticeContainer";
referenceNode.prepend(noticeElement);
ReactDOM.render(
<SubmissionNoticeComponent
contentContainer={contentContainer}
callback={callback} />,
noticeElement
);
}
}
export default SubmissionNotice;

View File

@@ -256,6 +256,21 @@ class Utils {
xmlhttp.send(); xmlhttp.send();
} }
getFormattedTime(seconds) {
let minutes = Math.floor(seconds / 60);
let secondsNum: number = Math.round(seconds - minutes * 60);
let secondsDisplay: string = String(secondsNum);
if (secondsNum < 10) {
//add a zero
secondsDisplay = "0" + secondsNum;
}
let formatted = minutes + ":" + secondsDisplay;
return formatted;
}
/** /**
* Is this Firefox (web-extensions) * Is this Firefox (web-extensions)
*/ */