Merge pull request #288 from ajayyy/react

Moving elements to React
This commit is contained in:
Ajay Ramachandran
2020-04-10 00:10:07 -04:00
committed by GitHub
30 changed files with 3249 additions and 966 deletions

View File

@@ -1,4 +1,6 @@
{
"serverAddress": "https://sponsor.ajay.app",
"serverAddressComment": "This specifies the default SponsorBlock server to conect to"
}
"testingServerAddress": "https://sponsor.ajay.app/test",
"serverAddressComment": "This specifies the default SponsorBlock server to conect to",
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "offtopic"]
}

View File

@@ -1,7 +1,7 @@
{
"name": "__MSG_fullName__",
"short_name": "__MSG_Name__",
"version": "1.2.24",
"version": "1.2.25",
"default_locale": "en",
"description": "__MSG_Description__",
"content_scripts": [{
@@ -32,6 +32,7 @@
"icons/downvote.png",
"icons/report.png",
"icons/close.png",
"icons/beep.ogg",
"icons/PlayerInfoIconSponsorBlocker256px.png",
"icons/PlayerDeleteIconSponsorBlocker256px.png",
"popup.html",

925
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,15 @@
"description": "",
"main": "background.js",
"dependencies": {
"concurrently": "^5.1.0"
"@types/react": "^16.9.22",
"@types/react-dom": "^16.9.5",
"babel": "^6.23.0",
"babel-core": "^6.26.3",
"babel-loader": "^8.0.6",
"babel-preset-env": "^1.7.0",
"concurrently": "^5.1.0",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"devDependencies": {
"web-ext": "^4.0.0",

View File

@@ -291,6 +291,12 @@
"autoSkipDescription": {
"message": "Auto skip will skip sponsors for you. If disabled, a notice will appear asking if you'd like to skip."
},
"audioNotification": {
"message": "Audio Notification On Skip"
},
"audioNotificationDescription": {
"message": "Audio notification on skip will play a sound whenever a sponsor is skipped. If disabled (or auto skip is disabled), no sound will be played."
},
"youHaveSkipped": {
"message": "You have skipped "
},
@@ -441,6 +447,24 @@
"incorrectlyFormattedOptions": {
"message": "This JSON is not formatted correctly. Your options have not been changed."
},
"confirmNoticeTitle" : {
"message": "Submit Segment"
},
"submit": {
"message": "Submit"
},
"cancel": {
"message": "Cancel"
},
"delete": {
"message": "Delete"
},
"preview": {
"message": "Preview"
},
"edit": {
"message": "Edit"
},
"copyDebugInformation": {
"message": "Copy Debug Information To Clipboard"
},
@@ -461,5 +485,48 @@
},
"keyAlreadyUsed": {
"message": "is bound to another action. Please select another key."
},
"to": {
"message": "to",
"description": "Used between sponsor times. Example: 1:20 to 1:30"
},
"category_sponsor": {
"message": "Sponsor"
},
"category_intro": {
"message": "Intro"
},
"category_outro": {
"message": "Outro"
},
"category_interaction": {
"message": "Interaction (Redundant Like, Subscribe, Follow, etc.)"
},
"category_selfpromo": {
"message": "Self-Promotion and Merchandise"
},
"category_offtopic": {
"message": "Offtopic tangent (Subjective)"
},
"disable": {
"message": "Disable"
},
"manualSkip": {
"message": "Manual Skip"
},
"showOverlay": {
"message": "Show Overlay On Player"
},
"enableTestingServer": {
"message": "Enable Beta Testing Server"
},
"whatEnableTestingServer": {
"message": "Your submissions and votes WILL NOT COUNT towards the main server. Only use this for testing."
},
"testingServerWarning": {
"message": "All submissions and votes WILL NOT COUNT towards the main server while connecting to the test server. Make sure to disable this when you want to make real submissions."
},
"bracketNow": {
"message": "(Now)"
}
}

View File

@@ -80,13 +80,15 @@
border-radius: 5px;
animation: fadeIn 0.5s;
border-spacing: 5px 10px;
padding-left: 5px;
padding-right: 5px;
}
.sponsorSkipNoticeFadeIn {
animation: fadeIn 0.5s;
}
.sponsorSkipNoticeFadeOut {
animation: fadeOut 3s cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
@@ -311,4 +313,59 @@
.sponsorSkipDontShowButton:active {
position:relative;
top:1px;
}
/* Submission Notice */
.sponsorTimeDisplay {
font-size: 15px;
}
.sponsorTimeEditButton {
text-decoration: underline;
margin-left: 20px;
margin-right: 20px;
font-size: 13px;
cursor: pointer;
}
.sponsorTimeEdit > input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.sponsorTimeEdit {
font-size: 14px;
-moz-appearance: textfield;
}
.sponsorTimeEditMinutes {
width: 30px;
}
.sponsorTimeEditSeconds {
width: 60px;
}
.sponsorNowButton {
font-size: 11px;
cursor: pointer;
text-decoration: underline;
}
.sponsorTimeCategories {
margin-top: 5px;
margin-bottom: 5px;
background-color: rgba(28, 28, 28, 0.9);
border-color: rgb(130,0,0,0.9);
color: white;
border-width: 3px;
padding: 3px;
}

BIN
public/icons/beep.ogg Normal file

Binary file not shown.

View File

@@ -323,4 +323,27 @@ svg {
font-size: 14px;
color: white;
}
/* React styles */
.categoryTableElement {
font-size: 16px;
color: white;
}
.categoryTableElement > * {
padding-right: 15px;
padding-bottom: 15px;
}
.categoryOptionsSelector {
background-color: #c00000;
color: white;
border: none;
font-size: 14px;
padding: 5px;
border-radius: 5px;
}

View File

@@ -24,6 +24,13 @@
<div id="options" class="hidden">
<div id="category-type" option-type="react-CategoryChooserComponent">
</div>
<br/>
<br/>
<div id="support-invidious" option-type="toggle" sync-option="supportInvidious">
<label class="switch-container" label-name="__MSG_supportInvidious__">
<label class="switch">
@@ -76,24 +83,6 @@
<br/>
<br/>
<div option-type="toggle" toggle-type="reverse" sync-option="disableAutoSkip">
<label class="switch-container" label-name="__MSG_autoSkip__">
<label class="switch">
<input type="checkbox" checked>
<span class="slider round"></span>
</label>
</label>
<br/>
<br/>
<div class="small-description">__MSG_autoSkipDescription__</div>
</div>
<br/>
<br/>
<div option-type="keybind-change" sync-option="startSponsorKeybind">
<div class="option-button trigger-button">
@@ -228,6 +217,23 @@
<div class="small-description">__MSG_whatUploadButton__</div>
</div>
<br/>
<br/>
<div option-type="toggle" sync-option="audioNotificationOnSkip">
<label class="switch-container" label-name="__MSG_audioNotification__">
<label class="switch">
<input type="checkbox" checked>
<span class="slider round"></span>
</label>
</label>
<br/>
<br/>
<div class="small-description">__MSG_audioNotificationDescription__</div>
</div>
<br/>
<br/>
@@ -346,7 +352,23 @@
<br/>
<br/>
<div option-type="toggle" sync-option="testingServer" confirm-message="testingServerWarning">
<label class="switch-container" label-name="__MSG_enableTestingServer__">
<label class="switch">
<input type="checkbox">
<span class="slider round"></span>
</label>
</label>
<br/>
<br/>
<div class="small-description">__MSG_whatEnableTestingServer__</div>
</div>
<br/>
<br/>
<div option-type="text-change" sync-option="serverAddress">
<label class="text-label-container">

View File

@@ -39,29 +39,15 @@
<div id="submissionSection" class="popupElement" style="display: none">
<h3 class="popupElement">__MSG_lastTimes__</h3>
<b>
<div id="sponsorMessageTimes" class="popupElement">
</div>
</b>
<b>Sponsor Editing has been moved and will appear after you click submit</b>
<br/>
<button id="clearTimes" class="smallButton popupElement">__MSG_clearTimesButton__</button>
<div id="submitTimesContainer" class="popupElement" style="display: none">
<br/>
<br/>
<button id="submitTimes" class="smallButton popupElement">__MSG_submitTimesButton__</button>
<div id="submitTimesInfoMessageContainer" class="popupElement" style="display: none">
<h3 id="submitTimesInfoMessage" class="popupElement">
</h3>
</div>
</div>
</div>

View File

@@ -184,13 +184,13 @@ function submitVote(type, UUID, callback) {
});
}
async function submitTimes(videoID, callback) {
async function submitTimes(videoID: string, callback) {
//get the video times from storage
let sponsorTimes = Config.config.sponsorTimes.get(videoID);
let userID = Config.config.userID;
if (sponsorTimes != undefined && sponsorTimes.length > 0) {
let durationResult = <Types.videoDurationResponse> await new Promise((resolve, reject) => {
let durationResult = <Types.VideoDurationResponse> await new Promise((resolve, reject) => {
chrome.tabs.query({
active: true,
currentWindow: true
@@ -218,21 +218,24 @@ async function submitTimes(videoID, callback) {
+ "&userID=" + userID, function(xmlhttp, error) {
if (xmlhttp.readyState == 4 && !error) {
callback({
statusCode: xmlhttp.status
statusCode: xmlhttp.status,
responseText: xmlhttp.responseText
});
if (xmlhttp.status == 200) {
//save the amount contributed
if (!increasedContributionAmount) {
increasedContributionAmount = true;
Config.config.sponsorTimesContributed = Config.config.sponsorTimesContributed + sponsorTimes.length;
}
} else if (error) {
callback({
statusCode: -1
});
}
}
} else if (error) {
callback({
statusCode: -1
});
}
});
}
}

View File

@@ -0,0 +1,53 @@
import * as React from "react";
import Config from "../config"
import * as CompileConfig from "../../config.json";
import CategorySkipOptionsComponent from "./CategorySkipOptionsComponent";
export interface CategoryChooserProps {
}
export interface CategoryChooserState {
}
class CategoryChooserComponent extends React.Component<CategoryChooserProps, CategoryChooserState> {
constructor(props: CategoryChooserProps) {
super(props);
// Setup state
this.state = {
}
}
render() {
return (
<table id="categoryChooserTable"
className="categoryChooserTable">
<tbody>
{this.getCategorySkipOptions()}
</tbody>
</table>
);
}
getCategorySkipOptions(): JSX.Element[] {
let elements: JSX.Element[] = [];
for (const category of CompileConfig.categoryList) {
elements.push(
<CategorySkipOptionsComponent category={category}
defaultColor={"00d400"}
key={category}>
</CategorySkipOptionsComponent>
);
}
return elements;
}
}
export default CategoryChooserComponent;

View File

@@ -0,0 +1,130 @@
import * as React from "react";
import Config from "../config"
import { CategorySkipOption } from "../types";
export interface CategorySkipOptionsProps {
category: string;
defaultColor: string;
}
export interface CategorySkipOptionsState {
color: string;
}
class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsProps, CategorySkipOptionsState> {
constructor(props: CategorySkipOptionsProps) {
super(props);
// Setup state
this.state = {
color: props.defaultColor
}
}
render() {
let defaultOption = "disable";
// Set the default opton properly
for (const categorySelection of Config.config.categorySelections) {
if (categorySelection.name === this.props.category) {
switch (categorySelection.option) {
case CategorySkipOption.ShowOverlay:
defaultOption = "showOverlay";
break;
case CategorySkipOption.ManualSkip:
defaultOption = "manualSkip";
break;
case CategorySkipOption.AutoSkip:
defaultOption = "autoSkip";
break;
}
}
}
return (
<tr id={this.props.category + "OptionsRow"}
className="categoryTableElement">
<td id={this.props.category + "OptionName"}
className="categoryTableLabel">
{chrome.i18n.getMessage("category_" + this.props.category)}
</td>
<td id={this.props.category + "SkipOption"}>
<select
className="categoryOptionsSelector"
defaultValue={defaultOption}
onChange={this.skipOptionSelected.bind(this)}>
{this.getCategorySkipOptions()}
</select>
</td>
{/* TODO: Add colour chooser */}
</tr>
);
}
skipOptionSelected(event: React.ChangeEvent<HTMLSelectElement>): void {
let option: CategorySkipOption;
this.removeCurrentCategorySelection();
switch (event.target.value) {
case "disable":
return;
case "showOverlay":
option = CategorySkipOption.ShowOverlay;
break;
case "manualSkip":
option = CategorySkipOption.ManualSkip;
break;
case "autoSkip":
option = CategorySkipOption.AutoSkip;
break;
}
Config.config.categorySelections.push({
name: this.props.category,
option: option
});
// Forces the Proxy to send this to the chrome storage API
Config.config.categorySelections = Config.config.categorySelections;
}
/** Removes this category from the config list of category selections */
removeCurrentCategorySelection(): void {
// Remove it if it exists
for (let i = 0; i < Config.config.categorySelections.length; i++) {
if (Config.config.categorySelections[i].name === this.props.category) {
Config.config.categorySelections.splice(i, 1);
// Forces the Proxy to send this to the chrome storage API
Config.config.categorySelections = Config.config.categorySelections;
break;
}
}
}
getCategorySkipOptions(): JSX.Element[] {
let elements: JSX.Element[] = [];
""
let optionNames = ["disable", "showOverlay", "manualSkip", "autoSkip"];
for (const optionName of optionNames) {
elements.push(
<option key={optionName} value={optionName}>
{chrome.i18n.getMessage(optionName)}
</option>
);
}
return elements;
}
}
export default CategorySkipOptionsComponent;

View File

@@ -0,0 +1,252 @@
import * as React from "react";
export interface NoticeProps {
noticeTitle: string,
maxCountdownTime?: () => number,
amountOfPreviousNotices?: number,
timed?: boolean,
idSuffix?: string,
fadeIn?: boolean,
// Callback for when this is closed
closeListener?: () => void,
zIndex?: number
}
export interface NoticeState {
noticeTitle: string,
maxCountdownTime?: () => number,
countdownTime: number,
countdownText: string,
}
class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
countdownInterval: NodeJS.Timeout;
idSuffix: any;
amountOfPreviousNotices: number;
constructor(props: NoticeProps) {
super(props);
let maxCountdownTime = () => {
if (this.props.maxCountdownTime) return this.props.maxCountdownTime();
else return 4;
};
//the id for the setInterval running the countdown
this.countdownInterval = null;
this.amountOfPreviousNotices = props.amountOfPreviousNotices || 0;
this.idSuffix = props.idSuffix || "";
// Setup state
this.state = {
noticeTitle: props.noticeTitle,
maxCountdownTime,
//the countdown until this notice closes
countdownTime: maxCountdownTime(),
countdownText: null,
}
}
componentDidMount() {
this.startCountdown();
}
render() {
let noticeStyle: React.CSSProperties = {
zIndex: this.props.zIndex || (50 + this.amountOfPreviousNotices)
}
return (
<table id={"sponsorSkipNotice" + this.idSuffix}
className={"sponsorSkipObject sponsorSkipNotice" + (this.props.fadeIn ? " sponsorSkipNoticeFadeIn" : "")}
style={noticeStyle}
onMouseEnter={this.pauseCountdown.bind(this)}
onMouseLeave={this.startCountdown.bind(this)}>
<tbody>
{/* First row */}
<tr id={"sponsorSkipNoticeFirstRow" + this.idSuffix}>
{/* Left column */}
<td>
{/* Logo */}
<img id={"sponsorSkipLogo" + this.idSuffix}
className="sponsorSkipLogo sponsorSkipObject"
src={chrome.extension.getURL("icons/IconSponsorBlocker256px.png")}>
</img>
<span id={"sponsorSkipMessage" + this.idSuffix}
className="sponsorSkipMessage sponsorSkipObject">
{this.state.noticeTitle}
</span>
</td>
{/* Right column */}
<td className="sponsorSkipNoticeRightSection"
style={{top: "11px"}}>
{/* Time left */}
{this.props.timed ? (
<span id={"sponsorSkipNoticeTimeLeft" + this.idSuffix}
className="sponsorSkipObject sponsorSkipNoticeTimeLeft">
{this.state.countdownText || (this.state.countdownTime + "s")}
</span>
) : ""}
{/* Close button */}
<img src={chrome.extension.getURL("icons/close.png")}
className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeCloseButton sponsorSkipNoticeRightButton"
onClick={() => this.close()}>
</img>
</td>
</tr>
{this.props.children}
</tbody>
</table>
);
}
//called every second to lower the countdown before hiding the notice
countdown() {
if (!this.props.timed) return;
let countdownTime = this.state.countdownTime - 1;
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
let notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
notice.style.removeProperty("animation");
notice.classList.add("sponsorSkipNoticeFadeOut");
}
this.setState({
countdownTime
})
}
pauseCountdown() {
if (!this.props.timed) return;
//remove setInterval
clearInterval(this.countdownInterval);
this.countdownInterval = null;
//reset countdown and inform the user
this.setState({
countdownTime: this.state.maxCountdownTime(),
countdownText: chrome.i18n.getMessage("paused")
});
//remove the fade out class if it exists
let notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
notice.classList.remove("sponsorSkipNoticeFadeOut");
notice.style.animation = "none";
}
startCountdown() {
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(),
countdownText: null
});
this.countdownInterval = setInterval(this.countdown.bind(this), 1000);
}
resetCountdown() {
if (!this.props.timed) return;
this.setState({
countdownTime: this.state.maxCountdownTime(),
countdownText: null
});
}
/**
* @param silent If true, the close listener will not be called
*/
close(silent?: boolean) {
//TODO: Change to a listener in the renderer (not component)
let notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
if (notice != null) {
notice.remove();
}
//remove setInterval
if (this.countdownInterval !== null) clearInterval(this.countdownInterval);
if (this.props.closeListener && !silent) this.props.closeListener();
}
changeNoticeTitle(title) {
this.setState({
noticeTitle: title
});
}
addNoticeInfoMessage(message: string, message2: string = "") {
//TODO: Replace
let previousInfoMessage = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix);
if (previousInfoMessage != null) {
//remove it
document.getElementById("sponsorSkipNotice" + this.idSuffix).removeChild(previousInfoMessage);
}
let previousInfoMessage2 = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix + "2");
if (previousInfoMessage2 != null) {
//remove it
document.getElementById("sponsorSkipNotice" + this.idSuffix).removeChild(previousInfoMessage2);
}
//add info
let 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) {
let 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));
}
}
}
export default NoticeComponent;

View File

@@ -0,0 +1,28 @@
import * as React from "react";
export interface NoticeTextSelectionProps {
text: string,
idSuffix: string
}
export interface NoticeTextSelectionState {
}
class NoticeTextSelectionComponent extends React.Component<NoticeTextSelectionProps, NoticeTextSelectionState> {
constructor(props: NoticeTextSelectionProps) {
super(props);
}
render() {
return (
<p id={"sponsorTimesInfoMessage" + this.props.idSuffix}
className="sponsorTimesInfoMessage">
{this.props.text}
</p>
);
}
}
export default NoticeTextSelectionComponent;

View File

@@ -0,0 +1,318 @@
import * as React from "react";
import Config from "../config"
import { ContentContainer } from "../types";
import Utils from "../utils";
var utils = new Utils();
import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
export interface SkipNoticeProps {
UUID: string;
autoSkip: boolean;
// Contains functions and variables from the content script needed by the skip notice
contentContainer: ContentContainer;
}
export interface SkipNoticeState {
noticeTitle: string,
messages: string[],
countdownTime: number,
maxCountdownTime: () => number;
countdownText: string,
unskipText: string,
unskipCallback: () => void
}
class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeState> {
UUID: string;
autoSkip: boolean;
// Contains functions and variables from the content script needed by the skip notice
contentContainer: ContentContainer;
amountOfPreviousNotices: number;
audio: HTMLAudioElement;
idSuffix: any;
noticeRef: React.MutableRefObject<NoticeComponent>;
constructor(props: SkipNoticeProps) {
super(props);
this.noticeRef = React.createRef();
this.UUID = props.UUID;
this.autoSkip = props.autoSkip;
this.contentContainer = props.contentContainer;
this.audio = null;
let noticeTitle = chrome.i18n.getMessage("noticeTitle");
if (!this.autoSkip) {
noticeTitle = chrome.i18n.getMessage("noticeTitleNotSkipped");
}
//add notice
this.amountOfPreviousNotices = document.getElementsByClassName("sponsorSkipNotice").length;
//this is the suffix added at the end of every id
this.idSuffix = this.UUID + this.amountOfPreviousNotices;
if (this.amountOfPreviousNotices > 0) {
//another notice exists
let previousNotice = document.getElementsByClassName("sponsorSkipNotice")[0];
previousNotice.classList.add("secondSkipNotice")
}
// Setup state
this.state = {
noticeTitle,
messages: [],
//the countdown until this notice closes
maxCountdownTime: () => 4,
countdownTime: 4,
countdownText: null,
unskipText: chrome.i18n.getMessage("unskip"),
unskipCallback: this.unskip.bind(this)
}
if (!this.autoSkip) {
Object.assign(this.state, this.getUnskippedModeInfo(chrome.i18n.getMessage("skip")));
}
}
componentDidMount() {
if (Config.config.audioNotificationOnSkip && this.audio) {
this.audio.volume = this.contentContainer().v.volume * 0.1;
this.audio.play();
}
}
render() {
let noticeStyle: React.CSSProperties = {
zIndex: 50 + this.amountOfPreviousNotices
}
if (this.contentContainer().onMobileYouTube) {
noticeStyle.bottom = "4em";
noticeStyle.transform = "scale(0.8) translate(10%, 10%)";
}
return (
<NoticeComponent noticeTitle={this.state.noticeTitle}
amountOfPreviousNotices={this.amountOfPreviousNotices}
idSuffix={this.idSuffix}
fadeIn={true}
timed={true}
maxCountdownTime={this.state.maxCountdownTime}
ref={this.noticeRef}>
{(Config.config.audioNotificationOnSkip) && <audio ref={(source) => { this.audio = source; }}>
<source src={chrome.extension.getURL("icons/beep.ogg")} type="audio/ogg"></source>
</audio>}
{/* Text Boxes */}
{this.getMessageBoxes()}
{/* Last Row */}
<tr id={"sponsorSkipNoticeSecondRow" + this.idSuffix}>
{/* Vote Button Container */}
<td id={"sponsorTimesVoteButtonsContainer" + this.idSuffix}
className="sponsorTimesVoteButtonsContainer">
{/* Report Text */}
<span id={"sponsorTimesReportText" + this.idSuffix}
className="sponsorTimesInfoMessage sponsorTimesVoteButtonMessage"
title={chrome.i18n.getMessage("reportButtonInfo")}
style={{marginRight: "5px"}}>
{chrome.i18n.getMessage("reportButtonTitle")}
</span>
{/* Report Button */}
<img id={"sponsorTimesDownvoteButtonsContainer" + this.idSuffix}
className="sponsorSkipObject voteButton"
src={chrome.extension.getURL("icons/report.png")}
title={chrome.i18n.getMessage("reportButtonInfo")}
onClick={() => this.contentContainer().vote(0, this.UUID, this)}>
</img>
</td>
{/* Unskip Button */}
<td className="sponsorSkipNoticeUnskipSection">
<button id={"sponsorSkipUnskipButton" + this.idSuffix}
className="sponsorSkipObject sponsorSkipNoticeButton"
style={{marginLeft: "4px"}}
onClick={this.state.unskipCallback}>
{this.state.unskipText}
</button>
</td>
{/* Never show button if autoSkip is enabled */}
{!this.autoSkip ? "" :
<td className="sponsorSkipNoticeRightSection">
<button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton"
onClick={this.contentContainer().dontShowNoticeAgain}>
{chrome.i18n.getMessage("Hide")}
</button>
</td>
}
</tr>
</NoticeComponent>
);
}
getMessageBoxes(): JSX.Element[] | JSX.Element {
if (this.state.messages.length === 0) {
// Add a spacer if there is no text
return (
<tr id={"sponsorSkipNoticeSpacer" + this.idSuffix}
className="sponsorBlockSpacer">
</tr>
);
}
let elements: JSX.Element[] = [];
for (let i = 0; i < this.state.messages.length; i++) {
elements.push(
<NoticeTextSelectionComponent idSuffix={this.idSuffix}
text={this.state.messages[i]}
key={i}>
</NoticeTextSelectionComponent>
)
}
return elements;
}
unskip() {
this.contentContainer().unskipSponsorTime(this.UUID);
this.unskippedMode(chrome.i18n.getMessage("reskip"));
}
/** Sets up notice to be not skipped yet */
unskippedMode(buttonText: string) {
//setup new callback and reset countdown
this.setState(this.getUnskippedModeInfo(buttonText), () => {
this.noticeRef.current.resetCountdown();
});
}
getUnskippedModeInfo(buttonText: string) {
let maxCountdownTime = function() {
let sponsorTime = utils.getSponsorTimeFromUUID(this.contentContainer().sponsorTimes, this.UUID);
let duration = Math.round((sponsorTime.segment[1] - this.contentContainer().v.currentTime) * (1 / this.contentContainer().v.playbackRate));
return Math.max(duration, 4);
}.bind(this);
return {
unskipText: buttonText,
unskipCallback: this.reskip.bind(this),
//change max duration to however much of the sponsor is left
maxCountdownTime: maxCountdownTime,
countdownTime: maxCountdownTime()
}
}
reskip() {
this.contentContainer().reskipSponsorTime(this.UUID);
//reset countdown
this.setState({
unskipText: chrome.i18n.getMessage("unskip"),
unskipCallback: this.unskip.bind(this),
maxCountdownTime: () => 4,
countdownTime: 4
});
// See if the title should be changed
if (!this.autoSkip) {
this.setState({
noticeTitle: chrome.i18n.getMessage("noticeTitle")
});
if(Config.config.autoUpvote) this.contentContainer().vote(1, this.UUID);
}
}
afterDownvote() {
this.addVoteButtonInfo(chrome.i18n.getMessage("voted"));
this.setNoticeInfoMessage(chrome.i18n.getMessage("hitGoBack"));
//remove this sponsor from the sponsors looked up
//find which one it is
for (let i = 0; i < this.contentContainer().sponsorTimes.length; i++) {
if (this.contentContainer().sponsorTimes[i].UUID == this.UUID) {
//this one is the one to hide
//add this as a hidden sponsorTime
this.contentContainer().hiddenSponsorTimes.push(i);
this.contentContainer().updatePreviewBar();
break;
}
}
}
setNoticeInfoMessage(...messages: string[]) {
this.setState({
messages
})
}
addVoteButtonInfo(message) {
this.resetVoteButtonInfo();
//hide report button and text for it
let downvoteButton = document.getElementById("sponsorTimesDownvoteButtonsContainer" + this.idSuffix);
if (downvoteButton != null) {
downvoteButton.style.display = "none";
}
let downvoteButtonText = document.getElementById("sponsorTimesReportText" + this.idSuffix);
if (downvoteButtonText != null) {
downvoteButtonText.style.display = "none";
}
//add info
let thanksForVotingText = document.createElement("td");
thanksForVotingText.id = "sponsorTimesVoteButtonInfoMessage" + this.idSuffix;
thanksForVotingText.className = "sponsorTimesInfoMessage sponsorTimesVoteButtonMessage";
thanksForVotingText.innerText = message;
//add element to div
document.getElementById("sponsorSkipNoticeSecondRow" + this.idSuffix).prepend(thanksForVotingText);
}
resetVoteButtonInfo() {
let previousInfoMessage = document.getElementById("sponsorTimesVoteButtonInfoMessage" + this.idSuffix);
if (previousInfoMessage != null) {
//remove it
document.getElementById("sponsorSkipNoticeSecondRow" + this.idSuffix).removeChild(previousInfoMessage);
}
//show button again
document.getElementById("sponsorTimesDownvoteButtonsContainer" + this.idSuffix).style.removeProperty("display");
}
}
export default SkipNoticeComponent;

View File

@@ -0,0 +1,302 @@
import * as React from "react";
import Config from "../config";
import * as CompileConfig from "../../config.json";
import Utils from "../utils";
import { ContentContainer, SponsorTime } from "../types";
import SubmissionNoticeComponent from "./SubmissionNoticeComponent";
var utils = new Utils();
export interface SponsorTimeEditProps {
index: number,
idSuffix: string,
// Contains functions and variables from the content script needed by the skip notice
contentContainer: ContentContainer,
submissionNotice: SubmissionNoticeComponent;
}
export interface SponsorTimeEditState {
editing: boolean;
sponsorTimeEdits: string[][];
}
class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, SponsorTimeEditState> {
idSuffix: string;
categoryOptionRef: React.RefObject<HTMLSelectElement>;
constructor(props: SponsorTimeEditProps) {
super(props);
this.categoryOptionRef = React.createRef();
this.idSuffix = this.props.idSuffix;
this.state = {
editing: false,
sponsorTimeEdits: [[null, null], [null, null]]
};
}
componentDidMount() {
// Prevent inputs from triggering key events
document.getElementById("sponsorTimesContainer" + this.idSuffix).addEventListener('keydown', function (event) {
event.stopPropagation();
});
}
render() {
let style: React.CSSProperties = {
textAlign: "center"
};
if (this.props.index != 0) {
style.marginTop = "15px";
}
// Create time display
let timeDisplay: JSX.Element;
let sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
let segment = sponsorTime.segment;
if (this.state.editing) {
timeDisplay = (
<div id={"sponsorTimesContainer" + this.idSuffix}
className="sponsorTimeDisplay">
<span id={"nowButton0" + this.idSuffix}
className="sponsorNowButton"
onClick={() => this.setTimeToNow(0)}>
{chrome.i18n.getMessage("bracketNow")}
</span>
<input id={"submittingTimeMinutes0" + this.idSuffix}
className="sponsorTimeEdit sponsorTimeEditMinutes"
type="number"
value={this.state.sponsorTimeEdits[0][0]}
onChange={(e) => {
let sponsorTimeEdits = this.state.sponsorTimeEdits;
sponsorTimeEdits[0][0] = e.target.value;
this.setState({sponsorTimeEdits});
}}>
</input>
<input id={"submittingTimeSeconds0" + this.idSuffix}
className="sponsorTimeEdit sponsorTimeEditSeconds"
type="number"
value={this.state.sponsorTimeEdits[0][1]}
onChange={(e) => {
let sponsorTimeEdits = this.state.sponsorTimeEdits;
sponsorTimeEdits[0][1] = e.target.value;
this.setState({sponsorTimeEdits});
}}>
</input>
<span>
{" " + chrome.i18n.getMessage("to") + " "}
</span>
<input id={"submittingTimeMinutes1" + this.idSuffix}
className="sponsorTimeEdit sponsorTimeEditMinutes"
type="text"
value={this.state.sponsorTimeEdits[1][0]}
onChange={(e) => {
let sponsorTimeEdits = this.state.sponsorTimeEdits;
sponsorTimeEdits[1][0] = e.target.value;
this.setState({sponsorTimeEdits});
}}>
</input>
<input id={"submittingTimeSeconds1" + this.idSuffix}
className="sponsorTimeEdit sponsorTimeEditSeconds"
type="text"
value={this.state.sponsorTimeEdits[1][1]}
onChange={(e) => {
let sponsorTimeEdits = this.state.sponsorTimeEdits;
sponsorTimeEdits[1][1] = e.target.value;
this.setState({sponsorTimeEdits});
}}>
</input>
<span id={"nowButton1" + this.idSuffix}
className="sponsorNowButton"
onClick={() => this.setTimeToNow(1)}>
{chrome.i18n.getMessage("bracketNow")}
</span>
</div>
);
} else {
timeDisplay = (
<div id={"sponsorTimesContainer" + this.idSuffix}
className="sponsorTimeDisplay"
onClick={this.toggleEditTime.bind(this)}>
{utils.getFormattedTime(segment[0], true) +
((!isNaN(segment[1])) ? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segment[1], true) : "")}
</div>
);
}
return (
<div style={style}>
{timeDisplay}
{/* Category */}
<select id={"sponsorTimeCategories" + this.idSuffix}
className="sponsorTimeCategories"
defaultValue={sponsorTime.category}
ref={this.categoryOptionRef}
onChange={this.saveEditTimes.bind(this)}>
{this.getCategoryOptions()}
</select>
<br/>
{/* Editing Tools */}
<span id={"sponsorTimeDeleteButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={this.deleteTime.bind(this)}>
{chrome.i18n.getMessage("delete")}
</span>
{(!isNaN(segment[1])) ? (
<span id={"sponsorTimePreviewButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={this.previewTime.bind(this)}>
{chrome.i18n.getMessage("preview")}
</span>
): ""}
{(!isNaN(segment[1])) ? (
<span id={"sponsorTimeEditButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={this.toggleEditTime.bind(this)}>
{this.state.editing ? chrome.i18n.getMessage("save") : chrome.i18n.getMessage("edit")}
</span>
): ""}
</div>
);
}
getCategoryOptions() {
let elements = [];
//TODO: Remove this when testing server is not needed
let categoryList = Config.config.testingServer ? CompileConfig.categoryList : ["sponsor"];
for (const category of categoryList) {
elements.push(
<option value={category}
key={category}>
{chrome.i18n.getMessage("category_" + category)}
</option>
);
}
return elements;
}
setTimeToNow(index: number) {
let sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
sponsorTime.segment[index] =
this.props.contentContainer().v.currentTime;
this.setState({
sponsorTimeEdits: this.getFormattedSponsorTimesEdits(sponsorTime)
}, this.saveEditTimes);
}
toggleEditTime(): void {
if (this.state.editing) {
this.setState({
editing: false
});
this.saveEditTimes();
} else {
let sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
this.setState({
editing: true,
sponsorTimeEdits: this.getFormattedSponsorTimesEdits(sponsorTime)
});
}
}
/** Returns an array in the sponsorTimeEdits form (minutes and seconds) from a normal seconds sponsor time */
getFormattedSponsorTimesEdits(sponsorTime: SponsorTime): string[][] {
return [[String(utils.getFormattedMinutes(sponsorTime.segment[0])), String(utils.getFormattedSeconds(sponsorTime.segment[0]))],
[String(utils.getFormattedMinutes(sponsorTime.segment[1])), String(utils.getFormattedSeconds(sponsorTime.segment[1]))]];
}
saveEditTimes() {
let sponsorTimesSubmitting = this.props.contentContainer().sponsorTimesSubmitting;
if (this.state.editing) {
sponsorTimesSubmitting[this.props.index].segment =
[utils.getRawSeconds(parseFloat(this.state.sponsorTimeEdits[0][0]), parseFloat(this.state.sponsorTimeEdits[0][1])),
utils.getRawSeconds(parseFloat(this.state.sponsorTimeEdits[1][0]), parseFloat(this.state.sponsorTimeEdits[1][1]))];
}
sponsorTimesSubmitting[this.props.index].category = this.categoryOptionRef.current.value;
Config.config.sponsorTimes.set(this.props.contentContainer().sponsorVideoID, utils.getSegmentsFromSponsorTimes(sponsorTimesSubmitting));
this.props.contentContainer().updatePreviewBar();
}
previewTime(): void {
let sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
let index = this.props.index;
let skipTime = sponsorTimes[index][0];
if (this.state.editing) {
// Save edits before previewing
this.saveEditTimes();
}
this.props.contentContainer().previewTime(skipTime - 2);
}
deleteTime(): void {
let sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
let index = this.props.index;
//if it is not a complete sponsor time
if (sponsorTimes[index].segment.length < 2) {
//update video player
this.props.contentContainer().changeStartSponsorButton(true, false);
}
sponsorTimes.splice(index, 1);
//save this
Config.config.sponsorTimes.set(this.props.contentContainer().sponsorVideoID, sponsorTimes);
this.props.contentContainer().updatePreviewBar();
//if they are all removed
if (sponsorTimes.length == 0) {
this.props.submissionNotice.cancel();
//update video player
this.props.contentContainer().changeStartSponsorButton(true, false);
} else {
//update display
this.props.submissionNotice.forceUpdate();
}
}
}
export default SponsorTimeEditComponent;

View File

@@ -0,0 +1,150 @@
import * as React from "react";
import Config from "../config"
import { ContentContainer } from "../types";
import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
import SponsorTimeEditComponent from "./SponsorTimeEditComponent";
export interface SubmissionNoticeProps {
// Contains functions and variables from the content script needed by the skip notice
contentContainer: ContentContainer;
callback: () => any;
}
export interface SubmissionNoticeeState {
noticeTitle: string,
messages: string[],
idSuffix: string;
}
class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, SubmissionNoticeeState> {
// Contains functions and variables from the content script needed by the skip notice
contentContainer: ContentContainer;
callback: () => any;
noticeRef: React.MutableRefObject<NoticeComponent>;
timeEditRefs: React.RefObject<SponsorTimeEditComponent>[];
constructor(props: SubmissionNoticeProps) {
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() {
return (
<NoticeComponent noticeTitle={this.state.noticeTitle}
idSuffix={this.state.idSuffix}
ref={this.noticeRef}
closeListener={this.cancel.bind(this)}
zIndex={50000}>
{/* 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[] = [];
this.timeEditRefs = [];
let sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
for (let i = 0; i < sponsorTimes.length; i++) {
let timeRef = React.createRef<SponsorTimeEditComponent>();
elements.push(
<SponsorTimeEditComponent key={i}
idSuffix={this.state.idSuffix + i}
index={i}
contentContainer={this.props.contentContainer}
submissionNotice={this}
ref={timeRef}>
</SponsorTimeEditComponent>
);
this.timeEditRefs.push(timeRef);
}
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 + i}
text={this.state.messages[i]}
key={i}>
</NoticeTextSelectionComponent>
);
}
return elements;
}
cancel() {
this.noticeRef.current.close(true);
this.contentContainer().resetSponsorSubmissionNotice();
}
submit() {
// save all items
for (const ref of this.timeEditRefs) {
ref.current.saveEditTimes();
}
this.props.callback();
this.cancel();
}
}
export default SubmissionNoticeComponent;

View File

@@ -1,4 +1,5 @@
import * as CompileConfig from "../config.json";
import { CategorySelection, CategorySkipOption } from "./types";
interface SBConfig {
userID: string,
@@ -10,7 +11,6 @@ interface SBConfig {
skipCount: number,
sponsorTimesContributed: number,
disableSkipping: boolean,
disableAutoSkip: boolean,
trackViewCount: boolean,
dontShowNotice: boolean,
hideVideoPlayerControls: boolean,
@@ -24,8 +24,13 @@ interface SBConfig {
supportInvidious: boolean,
serverAddress: string,
minDuration: number,
audioNotificationOnSkip,
checkForUnlistedVideos: boolean,
mobileUpdateShowCount: number
mobileUpdateShowCount: number,
testingServer: boolean,
// What categories should be skipped
categorySelections: CategorySelection[]
}
interface SBObject {
@@ -105,7 +110,6 @@ var Config: SBObject = {
skipCount: 0,
sponsorTimesContributed: 0,
disableSkipping: false,
disableAutoSkip: false,
trackViewCount: true,
dontShowNotice: false,
hideVideoPlayerControls: false,
@@ -119,8 +123,15 @@ var Config: SBObject = {
supportInvidious: false,
serverAddress: CompileConfig.serverAddress,
minDuration: 0,
audioNotificationOnSkip: false,
checkForUnlistedVideos: false,
mobileUpdateShowCount: 0
mobileUpdateShowCount: 0,
testingServer: false,
categorySelections: [{
name: "sponsor",
option: CategorySkipOption.AutoSkip
}]
},
localConfig: null,
config: null,
@@ -225,11 +236,14 @@ function fetchConfig() {
});
}
function migrateOldFormats() { // Convert sponsorTimes format
for (const key in Config.localConfig) {
if (key.startsWith("sponsorTimes") && key !== "sponsorTimes" && key !== "sponsorTimesContributed") {
Config.config.sponsorTimes.set(key.substr(12), Config.config[key]);
delete Config.config[key];
function migrateOldFormats() {
if (Config.config["disableAutoSkip"]) {
for (const selection of Config.config.categorySelections) {
if (selection.name === "sponsor") {
selection.option = CategorySkipOption.ManualSkip;
chrome.storage.sync.remove("disableAutoSkip");
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,15 +8,55 @@
let barTypes = {
"undefined": {
color: "#00d400",
opacity: "0.5"
opacity: "0.7"
},
"sponsor": {
color: "#00d400",
opacity: "0.5"
opacity: "0.7"
},
"previewSponsor": {
color: "#0000d4",
opacity: "0.5"
"preview-sponsor": {
color: "#007800",
opacity: "0.7"
},
"intro": {
color: "#00ffff",
opacity: "0.7"
},
"preview-intro": {
color: "#008080",
opacity: "0.7"
},
"outro": {
color: "#0202ed",
opacity: "0.7"
},
"preview-outro": {
color: "#000070",
opacity: "0.7"
},
"interaction": {
color: "#cc00ff",
opacity: "0.7"
},
"preview-interaction": {
color: "#6c0087",
opacity: "0.7"
},
"selfpromo": {
color: "#ffff00",
opacity: "0.7"
},
"preview-selfpromo": {
color: "#bfbf35",
opacity: "0.7"
},
"offtopic": {
color: "#ff9900",
opacity: "0.7"
},
"preview-offtopic": {
color: "#a6634a",
opacity: "0.7"
}
};

View File

@@ -1,444 +0,0 @@
'use strict';
import Config from "../config";
/**
* The notice that tells the user that a sponsor was just skipped
*/
class SkipNotice {
parent: HTMLElement;
UUID: string;
manualSkip: boolean;
// Contains functions and variables from the content script needed by the skip notice
contentContainer: () => any;
maxCountdownTime: () => number;
countdownTime: any;
countdownInterval: NodeJS.Timeout;
unskipCallback: any;
idSuffix: any;
constructor(parent: HTMLElement, UUID: string, manualSkip: boolean = false, contentContainer) {
this.parent = parent;
this.UUID = UUID;
this.manualSkip = manualSkip;
this.contentContainer = contentContainer;
let noticeTitle = chrome.i18n.getMessage("noticeTitle");
if (manualSkip) {
noticeTitle = chrome.i18n.getMessage("noticeTitleNotSkipped");
}
this.maxCountdownTime = () => 4;
//the countdown until this notice closes
this.countdownTime = this.maxCountdownTime();
//the id for the setInterval running the countdown
this.countdownInterval = null;
//the unskip button's callback
this.unskipCallback = this.unskip.bind(this);
//add notice
let amountOfPreviousNotices = document.getElementsByClassName("sponsorSkipNotice").length;
//this is the suffix added at the end of every id
this.idSuffix = this.UUID + amountOfPreviousNotices;
if (amountOfPreviousNotices > 0) {
//already exists
let previousNotice = document.getElementsByClassName("sponsorSkipNotice")[0];
previousNotice.classList.add("secondSkipNotice")
}
let noticeElement = document.createElement("div");
//what sponsor time this is about
noticeElement.id = "sponsorSkipNotice" + this.idSuffix;
noticeElement.classList.add("sponsorSkipObject");
noticeElement.classList.add("sponsorSkipNotice");
noticeElement.style.zIndex = String(50 + amountOfPreviousNotices);
if (contentContainer().onMobileYouTube) {
noticeElement.style.bottom = "4em";
noticeElement.style.transform = "scale(0.8) translate(10%, 10%)";
}
//add mouse enter and leave listeners
noticeElement.addEventListener("mouseenter", this.pauseCountdown.bind(this));
noticeElement.addEventListener("mouseleave", this.startCountdown.bind(this));
//the row that will contain the info
let firstRow = document.createElement("tr");
firstRow.id = "sponsorSkipNoticeFirstRow" + this.idSuffix;
let logoColumn = document.createElement("td");
let logoElement = document.createElement("img");
logoElement.id = "sponsorSkipLogo" + this.idSuffix;
logoElement.className = "sponsorSkipLogo sponsorSkipObject";
logoElement.src = chrome.extension.getURL("icons/IconSponsorBlocker256px.png");
let noticeMessage = document.createElement("span");
noticeMessage.id = "sponsorSkipMessage" + this.idSuffix;
noticeMessage.classList.add("sponsorSkipMessage");
noticeMessage.classList.add("sponsorSkipObject");
noticeMessage.innerText = noticeTitle;
//create the first column
logoColumn.appendChild(logoElement);
logoColumn.appendChild(noticeMessage);
//add the x button
let closeButtonContainer = document.createElement("td");
closeButtonContainer.className = "sponsorSkipNoticeRightSection";
closeButtonContainer.style.top = "11px";
let timeLeft = document.createElement("span");
timeLeft.id = "sponsorSkipNoticeTimeLeft" + this.idSuffix;
timeLeft.innerText = this.countdownTime + "s";
timeLeft.className = "sponsorSkipObject sponsorSkipNoticeTimeLeft";
let hideButton = document.createElement("img");
hideButton.src = chrome.extension.getURL("icons/close.png");
hideButton.className = "sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeCloseButton sponsorSkipNoticeRightButton";
hideButton.addEventListener("click", this.close.bind(this));
closeButtonContainer.appendChild(timeLeft);
closeButtonContainer.appendChild(hideButton);
//add all objects to first row
firstRow.appendChild(logoColumn);
firstRow.appendChild(closeButtonContainer);
let spacer = document.createElement("hr");
spacer.id = "sponsorSkipNoticeSpacer" + this.idSuffix;
spacer.className = "sponsorBlockSpacer";
//the row that will contain the buttons
let secondRow = document.createElement("tr");
secondRow.id = "sponsorSkipNoticeSecondRow" + this.idSuffix;
//thumbs up and down buttons
let voteButtonsContainer = document.createElement("td");
voteButtonsContainer.id = "sponsorTimesVoteButtonsContainer" + this.idSuffix;
voteButtonsContainer.className = "sponsorTimesVoteButtonsContainer"
let reportText = document.createElement("span");
reportText.id = "sponsorTimesReportText" + this.idSuffix;
reportText.className = "sponsorTimesInfoMessage sponsorTimesVoteButtonMessage";
reportText.innerText = chrome.i18n.getMessage("reportButtonTitle");
reportText.style.marginRight = "5px";
reportText.setAttribute("title", chrome.i18n.getMessage("reportButtonInfo"));
let downvoteButton = document.createElement("img");
downvoteButton.id = "sponsorTimesDownvoteButtonsContainer" + this.idSuffix;
downvoteButton.className = "sponsorSkipObject voteButton";
downvoteButton.src = chrome.extension.getURL("icons/report.png");
downvoteButton.addEventListener("click", () => this.contentContainer().vote(0, this.UUID, this));
downvoteButton.setAttribute("title", chrome.i18n.getMessage("reportButtonInfo"));
//add downvote and report text to container
voteButtonsContainer.appendChild(reportText);
voteButtonsContainer.appendChild(downvoteButton);
//add unskip button
let unskipContainer = document.createElement("td");
unskipContainer.className = "sponsorSkipNoticeUnskipSection";
let unskipButton = document.createElement("button");
unskipButton.id = "sponsorSkipUnskipButton" + this.idSuffix;
unskipButton.innerText = chrome.i18n.getMessage("unskip");
unskipButton.className = "sponsorSkipObject sponsorSkipNoticeButton";
unskipButton.addEventListener("click", this.unskipCallback);
unskipButton.style.marginLeft = "4px";
unskipContainer.appendChild(unskipButton);
//add don't show again button
let dontshowContainer = document.createElement("td");
dontshowContainer.className = "sponsorSkipNoticeRightSection";
let dontShowAgainButton = document.createElement("button");
dontShowAgainButton.innerText = chrome.i18n.getMessage("Hide");
dontShowAgainButton.className = "sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton";
dontShowAgainButton.addEventListener("click", this.contentContainer().dontShowNoticeAgain);
// Don't let them hide it if manually skipping
if (!this.manualSkip) {
dontshowContainer.appendChild(dontShowAgainButton);
}
//add to row
secondRow.appendChild(voteButtonsContainer);
secondRow.appendChild(unskipContainer);
secondRow.appendChild(dontshowContainer);
noticeElement.appendChild(firstRow);
noticeElement.appendChild(spacer);
noticeElement.appendChild(secondRow);
//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 = <HTMLElement> player.firstChild;
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 = <HTMLElement> player.children[index];
index++;
}
}
referenceNode.prepend(noticeElement);
if (manualSkip) {
this.unskippedMode(chrome.i18n.getMessage("skip"));
}
this.startCountdown();
}
//called every second to lower the countdown before hiding the notice
countdown() {
this.countdownTime--;
if (this.countdownTime <= 0) {
//remove this from setInterval
clearInterval(this.countdownInterval);
//time to close this notice
this.close();
return;
}
if (this.countdownTime == 3) {
//start fade out animation
let notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
notice.style.removeProperty("animation");
notice.classList.add("sponsorSkipNoticeFadeOut");
}
this.updateTimerDisplay();
}
pauseCountdown() {
//remove setInterval
clearInterval(this.countdownInterval);
this.countdownInterval = null;
//reset countdown
this.countdownTime = this.maxCountdownTime();
//inform the user
let timeLeft = document.getElementById("sponsorSkipNoticeTimeLeft" + this.idSuffix);
timeLeft.innerText = chrome.i18n.getMessage("paused");
//remove the fade out class if it exists
let notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
notice.classList.remove("sponsorSkipNoticeFadeOut");
notice.style.animation = "none";
}
startCountdown() {
//if it has already started, don't start it again
if (this.countdownInterval !== null) return;
this.countdownInterval = setInterval(this.countdown.bind(this), 1000);
this.updateTimerDisplay();
}
updateTimerDisplay() {
//update the timer display
let timeLeft = document.getElementById("sponsorSkipNoticeTimeLeft" + this.idSuffix);
timeLeft.innerText = this.countdownTime + "s";
}
unskip() {
this.contentContainer().unskipSponsorTime(this.UUID);
this.unskippedMode(chrome.i18n.getMessage("reskip"));
}
/** Sets up notice to be not skipped yet */
unskippedMode(buttonText) {
//change unskip button to a reskip button
let unskipButton = this.changeUnskipButton(buttonText);
//setup new callback
this.unskipCallback = this.reskip.bind(this);
unskipButton.addEventListener("click", this.unskipCallback);
//change max duration to however much of the sponsor is left
this.maxCountdownTime = function() {
let sponsorTime = this.contentContainer().sponsorTimes[this.contentContainer().UUIDs.indexOf(this.UUID)];
let duration = Math.round(sponsorTime[1] - this.contentContainer().v.currentTime);
return Math.max(duration, 4);
};
this.countdownTime = this.maxCountdownTime();
this.updateTimerDisplay();
}
reskip() {
this.contentContainer().reskipSponsorTime(this.UUID);
//change reskip button to a unskip button
let unskipButton = this.changeUnskipButton(chrome.i18n.getMessage("unskip"));
//setup new callback
this.unskipCallback = this.unskip.bind(this);
unskipButton.addEventListener("click", this.unskipCallback);
//reset duration
this.maxCountdownTime = () => 4;
this.countdownTime = this.maxCountdownTime();
this.updateTimerDisplay();
// See if the title should be changed
if (this.manualSkip) {
this.changeNoticeTitle(chrome.i18n.getMessage("noticeTitle"));
if (Config.config.autoUpvote) this.contentContainer().vote(1, this.UUID);
}
}
/**
* Changes the text on the reskip button
*
* @param {string} text
* @returns {HTMLElement} unskipButton
*/
changeUnskipButton(text) {
let unskipButton = document.getElementById("sponsorSkipUnskipButton" + this.idSuffix);
unskipButton.innerText = text;
unskipButton.removeEventListener("click", this.unskipCallback);
return unskipButton;
}
afterDownvote() {
this.addVoteButtonInfo(chrome.i18n.getMessage("voted"));
this.addNoticeInfoMessage(chrome.i18n.getMessage("hitGoBack"));
//remove this sponsor from the sponsors looked up
//find which one it is
for (let i = 0; i < this.contentContainer().sponsorTimes.length; i++) {
if (this.contentContainer().UUIDs[i] == this.UUID) {
//this one is the one to hide
//add this as a hidden sponsorTime
this.contentContainer().hiddenSponsorTimes.push(i);
this.contentContainer().updatePreviewBar();
break;
}
}
}
changeNoticeTitle(title) {
let noticeElement = document.getElementById("sponsorSkipMessage" + this.idSuffix);
noticeElement.innerText = title;
}
addNoticeInfoMessage(message: string, message2: string = "") {
let previousInfoMessage = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix);
if (previousInfoMessage != null) {
//remove it
document.getElementById("sponsorSkipNotice" + this.idSuffix).removeChild(previousInfoMessage);
}
let previousInfoMessage2 = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix + "2");
if (previousInfoMessage2 != null) {
//remove it
document.getElementById("sponsorSkipNotice" + this.idSuffix).removeChild(previousInfoMessage2);
}
//add info
let thanksForVotingText = document.createElement("p");
thanksForVotingText.id = "sponsorTimesInfoMessage" + this.idSuffix;
thanksForVotingText.className = "sponsorTimesInfoMessage";
thanksForVotingText.innerText = message;
//add element to div
document.getElementById("sponsorSkipNotice" + this.idSuffix).insertBefore(thanksForVotingText, document.getElementById("sponsorSkipNoticeSpacer" + this.idSuffix));
if (message2 !== undefined) {
let thanksForVotingText2 = document.createElement("p");
thanksForVotingText2.id = "sponsorTimesInfoMessage" + this.idSuffix + "2";
thanksForVotingText2.className = "sponsorTimesInfoMessage";
thanksForVotingText2.innerText = message2;
//add element to div
document.getElementById("sponsorSkipNotice" + this.idSuffix).insertBefore(thanksForVotingText2, document.getElementById("sponsorSkipNoticeSpacer" + this.idSuffix));
}
}
resetNoticeInfoMessage() {
let previousInfoMessage = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix);
if (previousInfoMessage != null) {
//remove it
document.getElementById("sponsorSkipNotice" + this.idSuffix).removeChild(previousInfoMessage);
}
}
addVoteButtonInfo(message) {
this.resetVoteButtonInfo();
//hide report button and text for it
let downvoteButton = document.getElementById("sponsorTimesDownvoteButtonsContainer" + this.idSuffix);
if (downvoteButton != null) {
downvoteButton.style.display = "none";
}
let downvoteButtonText = document.getElementById("sponsorTimesReportText" + this.idSuffix);
if (downvoteButtonText != null) {
downvoteButtonText.style.display = "none";
}
//add info
let thanksForVotingText = document.createElement("td");
thanksForVotingText.id = "sponsorTimesVoteButtonInfoMessage" + this.idSuffix;
thanksForVotingText.className = "sponsorTimesInfoMessage sponsorTimesVoteButtonMessage";
thanksForVotingText.innerText = message;
//add element to div
document.getElementById("sponsorSkipNoticeSecondRow" + this.idSuffix).prepend(thanksForVotingText);
}
resetVoteButtonInfo() {
let previousInfoMessage = document.getElementById("sponsorTimesVoteButtonInfoMessage" + this.idSuffix);
if (previousInfoMessage != null) {
//remove it
document.getElementById("sponsorSkipNoticeSecondRow" + this.idSuffix).removeChild(previousInfoMessage);
}
//show button again
document.getElementById("sponsorTimesDownvoteButtonsContainer" + this.idSuffix).style.removeProperty("display");
}
//close this notice
close() {
//reset message
this.resetNoticeInfoMessage();
let notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
if (notice != null) {
notice.remove();
}
//remove setInterval
if (this.countdownInterval !== null) clearInterval(this.countdownInterval);
}
}
export default SkipNotice;

View File

@@ -5,6 +5,7 @@ import * as CompileConfig from "../config.json";
(<any> window).SB = Config;
import Utils from "./utils";
import CategoryChooser from "./render/CategoryChooser";
var utils = new Utils();
window.addEventListener('DOMContentLoaded', init);
@@ -31,6 +32,8 @@ async function init() {
let checkbox = optionsElements[i].querySelector("input");
let reverse = optionsElements[i].getAttribute("toggle-type") === "reverse";
let confirmMessage = optionsElements[i].getAttribute("confirm-message");
if (optionResult != undefined) {
checkbox.checked = optionResult;
@@ -48,6 +51,12 @@ async function init() {
// Add click listener
checkbox.addEventListener("click", () => {
// Confirm if required
if (checkbox.checked && confirmMessage && !confirm(chrome.i18n.getMessage(confirmMessage))){
checkbox.checked = false;
return;
}
Config.config[option] = reverse ? !checkbox.checked : checkbox.checked;
// See if anything extra must be run
@@ -164,6 +173,9 @@ async function init() {
});
break;
case "react-CategoryChooserComponent":
new CategoryChooser(optionsElements[i]);
break;
}
}

View File

@@ -1,6 +1,7 @@
import Config from "./config";
import Utils from "./utils";
import { SponsorTime } from "./types";
var utils = new Utils();
interface MessageListener {
@@ -56,7 +57,6 @@ async function runThePopup(messageListener?: MessageListener) {
"showNoticeAgain",
"optionsButton",
// More controls
"clearTimes",
"submitTimes",
"reportAnIssue",
// sponsorTimesContributions
@@ -82,9 +82,6 @@ async function runThePopup(messageListener?: MessageListener) {
// discordButtons
"discordButtonContainer",
"hideDiscordButton",
// submitTimesInfoMessage
"submitTimesInfoMessageContainer",
"submitTimesInfoMessage",
// Username
"setUsernameContainer",
"setUsernameButton",
@@ -108,7 +105,6 @@ async function runThePopup(messageListener?: MessageListener) {
PageElements.unwhitelistChannel.addEventListener("click", unwhitelistChannel);
PageElements.disableSkipping.addEventListener("click", () => toggleSkipping(true));
PageElements.enableSkipping.addEventListener("click", () => toggleSkipping(false));
PageElements.clearTimes.addEventListener("click", clearTimes);
PageElements.submitTimes.addEventListener("click", submitTimes);
PageElements.showNoticeAgain.addEventListener("click", showNoticeAgain);
PageElements.setUsernameButton.addEventListener("click", setUsernameButton);
@@ -263,8 +259,6 @@ async function runThePopup(messageListener?: MessageListener) {
sponsorTimes = sponsorTimesStorage;
displaySponsorTimes();
//show submission section
PageElements.submissionSection.style.display = "unset";
@@ -279,7 +273,7 @@ async function runThePopup(messageListener?: MessageListener) {
);
}
function infoFound(request) {
function infoFound(request: {found: boolean, sponsorTimes: SponsorTime[], hiddenSponsorTimes: number[]}) {
if(chrome.runtime.lastError) {
//This page doesn't have the injected content script, or at least not yet
displayNoVideo();
@@ -363,28 +357,14 @@ async function runThePopup(messageListener?: MessageListener) {
updateStartTimeChosen();
//display video times on screen
displaySponsorTimes();
//show submission section
PageElements.submissionSection.style.display = "unset";
showSubmitTimesIfNecessary();
}
//display the video times from the array
function displaySponsorTimes() {
//remove all children
while (PageElements.sponsorMessageTimes.firstChild) {
PageElements.sponsorMessageTimes.removeChild(PageElements.sponsorMessageTimes.firstChild);
}
//add sponsor times
PageElements.sponsorMessageTimes.appendChild(getSponsorTimesMessageDiv(sponsorTimes));
}
//display the video times from the array at the top, in a different section
function displayDownloadedSponsorTimes(request) {
function displayDownloadedSponsorTimes(request: {found: boolean, sponsorTimes: SponsorTime[], hiddenSponsorTimes: number[]}) {
if (request.sponsorTimes != undefined) {
//set it to the message
if (PageElements.downloadedSponsorMessageTimes.innerText != chrome.i18n.getMessage("channelWhitelisted")) {
@@ -403,11 +383,11 @@ async function runThePopup(messageListener?: MessageListener) {
extraInfo = " (hidden)";
}
sponsorTimeButton.innerText = getFormattedTime(request.sponsorTimes[i][0]) + " to " + getFormattedTime(request.sponsorTimes[i][1]) + extraInfo;
sponsorTimeButton.innerText = getFormattedTime(request.sponsorTimes[i].segment[0]) + " to " + getFormattedTime(request.sponsorTimes[i].segment[1]) + extraInfo;
let votingButtons = document.createElement("div");
let UUID = request.UUIDs[i];
let UUID = request.sponsorTimes[i].UUID;
//thumbs up and down buttons
let voteButtonsContainer = document.createElement("div");
@@ -451,12 +431,12 @@ async function runThePopup(messageListener?: MessageListener) {
}
//get the message that visually displays the video times
function getSponsorTimesMessage(sponsorTimes) {
function getSponsorTimesMessage(sponsorTimes: SponsorTime[]) {
let sponsorTimesMessage = "";
for (let i = 0; i < sponsorTimes.length; i++) {
for (let s = 0; s < sponsorTimes[i].length; s++) {
let timeMessage = getFormattedTime(sponsorTimes[i][s]);
for (let s = 0; s < sponsorTimes[i].segment.length; s++) {
let timeMessage = getFormattedTime(sponsorTimes[i].segment[s]);
//if this is an end time
if (s == 1) {
timeMessage = " to " + timeMessage;
@@ -692,8 +672,6 @@ async function runThePopup(messageListener?: MessageListener) {
});
if (closeEditMode) {
displaySponsorTimes();
showSubmitTimesIfNecessary();
}
}
@@ -721,9 +699,6 @@ async function runThePopup(messageListener?: MessageListener) {
//save this
Config.config.sponsorTimes.set(currentVideoID, sponsorTimes);
//update display
displaySponsorTimes();
//if they are all removed
if (sponsorTimes.length == 0) {
//update chrome tab
@@ -753,67 +728,17 @@ async function runThePopup(messageListener?: MessageListener) {
});
}
function clearTimes() {
//send new sponsor time state to tab
function submitTimes() {
if (sponsorTimes.length > 0) {
messageHandler.query({
active: true,
currentWindow: true
}, function(tabs) {
messageHandler.sendMessage(tabs[0].id, {
message: "changeStartSponsorButton",
showStartSponsor: true,
uploadButtonVisible: false
});
});
}
//reset sponsorTimes
sponsorTimes = [];
Config.config.sponsorTimes.set(currentVideoID, sponsorTimes);
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{message: "sponsorDataChanged"}
{message: 'submitTimes'},
);
});
displaySponsorTimes();
//hide submission section
document.getElementById("submissionSection").style.display = "none";
resetStartTimeChosen();
}
function submitTimes() {
//make info message say loading
PageElements.submitTimesInfoMessage.innerText = chrome.i18n.getMessage("Loading");
PageElements.submitTimesInfoMessageContainer.style.display = "unset";
if (sponsorTimes.length > 0) {
chrome.runtime.sendMessage({
message: "submitTimes",
videoID: currentVideoID
}, function(response) {
if (response != undefined) {
if (response.statusCode == 200) {
//hide loading message
PageElements.submitTimesInfoMessageContainer.style.display = "none";
clearTimes();
} else {
document.getElementById("submitTimesInfoMessage").innerText = utils.getErrorMessage(response.statusCode);
document.getElementById("submitTimesInfoMessageContainer").style.display = "unset";
PageElements.submitTimesInfoMessageContainer.style.display = "unset";
}
}
});
}
}

View File

@@ -0,0 +1,15 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import CategoryChooserComponent from "../components/CategoryChooserComponent";
class CategoryChooser {
constructor(element: Element) {
ReactDOM.render(
<CategoryChooserComponent/>,
element
);
}
}
export default CategoryChooser;

50
src/render/SkipNotice.tsx Normal file
View File

@@ -0,0 +1,50 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import SkipNoticeComponent from "../components/SkipNoticeComponent";
class SkipNotice {
UUID: string;
autoSkip: boolean;
// Contains functions and variables from the content script needed by the skip notice
contentContainer: () => any;
constructor(UUID: string, autoSkip: boolean = false, contentContainer) {
this.UUID = UUID;
this.autoSkip = autoSkip;
this.contentContainer = contentContainer;
//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 amountOfPreviousNotices = document.getElementsByClassName("sponsorSkipNotice").length;
//this is the suffix added at the end of every id
let idSuffix = this.UUID + amountOfPreviousNotices;
let noticeElement = document.createElement("div");
noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix;
referenceNode.prepend(noticeElement);
ReactDOM.render(
<SkipNoticeComponent UUID={UUID} autoSkip={autoSkip} contentContainer={contentContainer} />,
noticeElement
);
}
}
export default SkipNotice;

View File

@@ -0,0 +1,56 @@
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;
noticeRef: React.MutableRefObject<SubmissionNoticeComponent>;
constructor(contentContainer: () => any, callback: () => any) {
this.noticeRef = React.createRef();
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}
ref={this.noticeRef} />,
noticeElement
);
}
update() {
this.noticeRef.current.forceUpdate();
}
}
export default SubmissionNotice;

View File

@@ -1,7 +1,55 @@
interface videoDurationResponse {
import SubmissionNotice from "./render/SubmissionNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent";
interface ContentContainer {
(): {
vote: (type: any, UUID: any, skipNotice?: SkipNoticeComponent) => void,
dontShowNoticeAgain: () => void,
unskipSponsorTime: (UUID: any) => void,
sponsorTimes: SponsorTime[],
sponsorTimesSubmitting: SponsorTime[],
hiddenSponsorTimes: number[],
v: HTMLVideoElement,
sponsorVideoID,
reskipSponsorTime: (UUID: any) => void,
updatePreviewBar: () => void,
onMobileYouTube: boolean,
sponsorSubmissionNotice: SubmissionNotice,
resetSponsorSubmissionNotice: () => void,
changeStartSponsorButton: (showStartSponsor: any, uploadButtonVisible: any) => Promise<boolean>,
previewTime: (time: number) => void
}
}
interface VideoDurationResponse {
duration: number;
}
enum CategorySkipOption {
ShowOverlay,
ManualSkip,
AutoSkip
}
interface CategorySelection {
name: string;
option: CategorySkipOption
}
interface SponsorTime {
segment: number[];
UUID: string;
category: string;
}
type VideoID = string;
export {
videoDurationResponse
VideoDurationResponse,
ContentContainer,
CategorySelection,
CategorySkipOption,
SponsorTime,
VideoID
};

View File

@@ -1,4 +1,7 @@
import Config from "./config";
import { CategorySelection, SponsorTime } from "./types";
import * as CompileConfig from "../config.json";
class Utils {
@@ -154,6 +157,42 @@ class Utils {
});
}
/**
* Gets just the timestamps from a sponsorTimes array
*
* @param sponsorTimes
*/
getSegmentsFromSponsorTimes(sponsorTimes: SponsorTime[]): number[][] {
let segments: number[][] = [];
for (const sponsorTime of sponsorTimes) {
segments.push(sponsorTime.segment);
}
return segments;
}
getSponsorIndexFromUUID(sponsorTimes: SponsorTime[], UUID: string): number {
for (let i = 0; i < sponsorTimes.length; i++) {
if (sponsorTimes[i].UUID === UUID) {
return i;
}
}
return -1;
}
getSponsorTimeFromUUID(sponsorTimes: SponsorTime[], UUID: string): SponsorTime {
return sponsorTimes[this.getSponsorIndexFromUUID(sponsorTimes, UUID)];
}
getCategorySelection(category: string): CategorySelection {
for (const selection of Config.config.categorySelections) {
if (selection.name === category) {
return selection;
}
}
}
localizeHtmlPage() {
//Localize by replacing __MSG_***__ meta tags
var objects = document.getElementsByClassName("sponsorBlockPageBody")[0].children;
@@ -230,6 +269,39 @@ class Utils {
return errorMessage;
}
/**
* Sends a request to the SponsorBlock server with address added as a query
*
* @param type The request type. "GET", "POST", etc.
* @param address The address to add to the SponsorBlock server address
* @param callback
*/
async asyncRequestToServer(type: string, address: string, data = {}) {
let serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress;
// If GET, convert JSON to parameters
if (type.toLowerCase() === "get") {
for (const key in data) {
let seperator = address.includes("?") ? "&" : "?";
let value = (typeof(data[key]) === "string") ? data[key]: JSON.stringify(data[key]);
address += seperator + key + "=" + value;
}
data = null;
}
const response = await fetch(serverAddress + address, {
method: type,
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow',
body: data ? JSON.stringify(data) : null
});
return response;
}
/**
* Sends a request to the SponsorBlock server with address added as a query
*
@@ -239,8 +311,10 @@ class Utils {
*/
sendRequestToServer(type: string, address: string, callback?: (xmlhttp: XMLHttpRequest, err: boolean) => any) {
let xmlhttp = new XMLHttpRequest();
xmlhttp.open(type, Config.config.serverAddress + address, true);
let serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress;
xmlhttp.open(type, serverAddress + address, true);
if (callback != undefined) {
xmlhttp.onreadystatechange = function () {
@@ -256,6 +330,37 @@ class Utils {
xmlhttp.send();
}
getFormattedMinutes(seconds: number) {
return Math.floor(seconds / 60);
}
getFormattedSeconds(seconds: number) {
return seconds % 60;
}
getFormattedTime(seconds: number, precise?: boolean) {
let minutes = Math.floor(seconds / 60);
let secondsNum: number = seconds - minutes * 60;
if (!precise) {
secondsNum = Math.floor(secondsNum);
}
let secondsDisplay: string = String(secondsNum.toFixed(3));
if (secondsNum < 10) {
//add a zero
secondsDisplay = "0" + secondsDisplay;
}
let formatted = minutes + ":" + secondsDisplay;
return formatted;
}
getRawSeconds(minutes: number, seconds: number): number {
return minutes * 60 + seconds;
}
/**
* Is this Firefox (web-extensions)
*/

View File

@@ -7,6 +7,7 @@
"outDir": "dist/js",
"noEmitOnError": true,
"typeRoots": [ "node_modules/@types" ],
"resolveJsonModule": true
"resolveJsonModule": true,
"jsx": "react"
}
}