From 05acb1669e8d9c931a4fee013d0c28034cfad4f9 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Tue, 19 May 2020 23:21:51 -0400 Subject: [PATCH] Added segment chooser when skipping multiple segments. Now the skip notice supports skipping multiple segments. --- public/_locales/en/messages.json | 3 + src/components/SkipNoticeComponent.tsx | 176 ++++++++++++++++++------- src/content.ts | 81 ++++++------ src/render/SkipNotice.tsx | 15 ++- src/types.ts | 4 +- 5 files changed, 188 insertions(+), 91 deletions(-) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index a9a77c7f..9d8cf88a 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -566,5 +566,8 @@ }, "nonMusicCategoryOnMusic": { "message": "This video is categorized as music. Are you sure you would like to submit segments with non-music categories? Unless this video is not actually music, you should not be submitting this segment. Please read the guidelines if you are confused." + }, + "multipleSegments": { + "message": "Multiple Segments" } } diff --git a/src/components/SkipNoticeComponent.tsx b/src/components/SkipNoticeComponent.tsx index 602b0ffc..8c71b947 100644 --- a/src/components/SkipNoticeComponent.tsx +++ b/src/components/SkipNoticeComponent.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as CompileConfig from "../../config.json"; import Config from "../config" -import { ContentContainer, SponsorHideType } from "../types"; +import { ContentContainer, SponsorHideType, SponsorTime } from "../types"; import Utils from "../utils"; var utils = new Utils(); @@ -9,9 +9,17 @@ var utils = new Utils(); import NoticeComponent from "./NoticeComponent"; import NoticeTextSelectionComponent from "./NoticeTextSectionComponent"; +enum SkipNoticeAction { + None, + Upvote, + Downvote, + CategoryVote, + Unskip +} + +export interface SkipNoticeProps { + segments: SponsorTime[]; -export interface SkipNoticeProps { - UUID: string; autoSkip: boolean; // Contains functions and variables from the content script needed by the skip notice contentContainer: ContentContainer; @@ -29,14 +37,16 @@ export interface SkipNoticeState { countdownText: string; unskipText: string; - unskipCallback: () => void; + unskipCallback: (index: number) => void; downvoting: boolean; choosingCategory: boolean; + + actionState: SkipNoticeAction; } class SkipNoticeComponent extends React.Component { - UUID: string; + segments: SponsorTime[]; autoSkip: boolean; // Contains functions and variables from the content script needed by the skip notice contentContainer: ContentContainer; @@ -57,22 +67,30 @@ class SkipNoticeComponent extends React.Component 1 ? "multipleSegments" : "category_" + this.segments[0].category); + let noticeTitle = categoryName + " " + chrome.i18n.getMessage("skipped"); if (!this.autoSkip) { - noticeTitle = chrome.i18n.getMessage("skip") + " " + chrome.i18n.getMessage("category_" + this.getSponsorTime().category) + "?"; + noticeTitle = chrome.i18n.getMessage("skip") + " " + categoryName + "?"; } //add notice this.amountOfPreviousNotices = document.getElementsByClassName("sponsorSkipNotice").length; + + // Sort segments + if (this.segments.length > 1) { + this.segments.sort((a, b) => a.segment[0] - b.segment[0]); + } //this is the suffix added at the end of every id - this.idSuffix = this.UUID + this.amountOfPreviousNotices; + for (const segment of this.segments) { + this.idSuffix += segment.UUID; + } + this.idSuffix += this.amountOfPreviousNotices; if (this.amountOfPreviousNotices > 0) { //another notice exists @@ -92,22 +110,20 @@ class SkipNoticeComponent extends React.Component this.unskip(index), downvoting: false, - choosingCategory: false + choosingCategory: false, + + actionState: SkipNoticeAction.None } if (!this.autoSkip) { - Object.assign(this.state, this.getUnskippedModeInfo(chrome.i18n.getMessage("skip"))); + // Assume manual skip is only skipping 1 submission + Object.assign(this.state, this.getUnskippedModeInfo(0, chrome.i18n.getMessage("skip"))); } } - // Helper method - getSponsorTime() { - return utils.getSponsorTimeFromUUID(this.contentContainer().sponsorTimes, this.UUID); - } - componentDidMount() { if (Config.config.audioNotificationOnSkip && this.audio) { this.audio.volume = this.contentContainer().v.volume * 0.1; @@ -173,7 +189,7 @@ class SkipNoticeComponent extends React.Component + onClick={() => this.prepAction(SkipNoticeAction.Unskip)}> {this.state.unskipText} @@ -198,7 +214,7 @@ class SkipNoticeComponent extends React.Component this.contentContainer().vote(0, this.UUID, undefined, this)}> + onClick={() => this.prepAction(SkipNoticeAction.Downvote)}> {chrome.i18n.getMessage("downvoteDescription")} @@ -220,7 +236,7 @@ class SkipNoticeComponent extends React.Component @@ -228,11 +244,23 @@ class SkipNoticeComponent extends React.Component {/* Submit Button */} - + {chrome.i18n.getMessage("submit")} + + } + + + + } + + {/* Segment Chooser Row */} + {this.state.actionState !== SkipNoticeAction.None && + + + {this.getSubmissionChooser()} } @@ -241,6 +269,32 @@ class SkipNoticeComponent extends React.Component this.performAction(i)} + key={"submission" + i + this.segments[i].category + this.idSuffix}> + {(i + 1) + ". " + chrome.i18n.getMessage("category_" + this.segments[i].category)} + + ); + } + + return elements; + } + + prepAction(action: SkipNoticeAction) { + if (this.segments.length === 1) { + this.performAction(0, action); + } else { + this.setState({ + actionState: action + }); + } + } + getMessageBoxes(): JSX.Element[] | JSX.Element { if (this.state.messages.length === 0) { // Add a spacer if there is no text @@ -265,6 +319,31 @@ class SkipNoticeComponent extends React.Component { + if (this.segments.length > 1) { + // Use the action selectors as a submit button + this.prepAction(SkipNoticeAction.CategoryVote); + } }); } @@ -324,37 +408,38 @@ class SkipNoticeComponent extends React.Component { + this.setState(this.getUnskippedModeInfo(index, buttonText), () => { this.noticeRef.current.resetCountdown(); }); } - getUnskippedModeInfo(buttonText: string) { + getUnskippedModeInfo(index: number, buttonText: string) { + let self = this; let maxCountdownTime = function() { - let sponsorTime = this.getSponsorTime(); - let duration = Math.round((sponsorTime.segment[1] - this.contentContainer().v.currentTime) * (1 / this.contentContainer().v.playbackRate)); + let sponsorTime = self.segments[index]; + let duration = Math.round((sponsorTime.segment[1] - self.contentContainer().v.currentTime) * (1 / self.contentContainer().v.playbackRate)); return Math.max(duration, 4); - }.bind(this); + }; return { unskipText: buttonText, - unskipCallback: this.reskip.bind(this), + unskipCallback: (index) => this.reskip(index), //change max duration to however much of the sponsor is left maxCountdownTime: maxCountdownTime, @@ -363,8 +448,8 @@ class SkipNoticeComponent extends React.Component Config.config !== null, 1000, 1).then(() => videoIDChange(getYo //this only happens if there is an error var sponsorLookupRetries = 0; -//the last time in the video a sponsor was skipped -//used for the go back button -var lastSponsorTimeSkipped: number = null; -//used for ratings -var lastSponsorTimeSkippedUUID: string = null; - //if showing the start sponsor button or the end sponsor button on the player var showingStartSponsor = true; @@ -483,6 +477,19 @@ function startSponsorSchedule(includeIntersectingSegments: boolean = false, curr let timeUntilSponsor = skipTime[0] - currentTime; let videoID = sponsorVideoID; + // Find all indexes in between the start and end + let skippingSegments = [skipInfo.array[skipInfo.index]]; + if (skipInfo.index !== skipInfo.endIndex) { + skippingSegments = []; + + for (const segment of skipInfo.array) { + if (utils.getCategorySelection(segment.category).option === CategorySkipOption.AutoSkip && + segment.segment[0] >= skipTime[0] && segment.segment[1] <= skipTime[1]) { + skippingSegments.push(segment); + } + } + } + // Don't skip if this category should not be skipped if (utils.getCategorySelection(currentSkip.category).option === CategorySkipOption.ShowOverlay) return; @@ -493,7 +500,7 @@ function startSponsorSchedule(includeIntersectingSegments: boolean = false, curr if (incorrectVideoCheck(videoID, currentSkip)) return; if (video.currentTime >= skipTime[0] && video.currentTime < skipTime[1]) { - skipToTime(video, skipInfo.endIndex, skipInfo.array, skipInfo.openNotice); + skipToTime(video, skipTime, skippingSegments, skipInfo.openNotice); // TODO: Know the autoSkip settings for ALL items being skipped if (utils.getCategorySelection(currentSkip.category).option === CategorySkipOption.ManualSkip) { @@ -955,58 +962,58 @@ function previewTime(time: number) { } //skip from the start time to the end time for a certain index sponsor time -function skipToTime(v: HTMLVideoElement, index: number, sponsorTimes: SponsorTime[], openNotice: boolean) { - let autoSkip: boolean = utils.getCategorySelection(sponsorTimes[index].category).option === CategorySkipOption.AutoSkip; +function skipToTime(v: HTMLVideoElement, skipTime: number[], skippingSegments: SponsorTime[], openNotice: boolean) { + // There will only be one submission if it is manual skip + let autoSkip: boolean = utils.getCategorySelection(skippingSegments[0].category).option === CategorySkipOption.AutoSkip; if (autoSkip || previewResetter !== null) { - v.currentTime = sponsorTimes[index].segment[1]; + v.currentTime = skipTime[1]; } - lastSponsorTimeSkipped = sponsorTimes[index].segment[0]; - - let currentUUID: string = sponsorTimes[index].UUID; - lastSponsorTimeSkippedUUID = currentUUID; - if (openNotice) { //send out the message saying that a sponsor message was skipped if (!Config.config.dontShowNotice || !autoSkip) { - let skipNotice = new SkipNotice(currentUUID, autoSkip, skipNoticeContentContainer); - - //auto-upvote this sponsor - if (Config.config.trackViewCount && autoSkip && Config.config.autoUpvote) { - vote(1, currentUUID); - } + let skipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer); } //send telemetry that a this sponsor was skipped - if (Config.config.trackViewCount && !sponsorSkipped[index] && autoSkip) { - utils.sendRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + currentUUID); + if (Config.config.trackViewCount && autoSkip) { + let alreadySkipped = false; + let isPreviewSegment = false; + for (const segment of skippingSegments) { + let index = sponsorTimes.indexOf(segment); + if (index !== -1 && !sponsorSkipped[index]) { + utils.sendRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID); + + sponsorSkipped[index] = true; + } else if (sponsorSkipped[index]) { + alreadySkipped = true; + } + + if (index !== -1) isPreviewSegment = true; + } + // Count this as a skip - Config.config.minutesSaved = Config.config.minutesSaved + (sponsorTimes[index].segment[1] - sponsorTimes[index].segment[0]) / 60; - Config.config.skipCount = Config.config.skipCount + 1; - - sponsorSkipped[index] = true; + if (!alreadySkipped && !isPreviewSegment) { + Config.config.minutesSaved = Config.config.minutesSaved + (skipTime[1] - skipTime[0]) / 60; + Config.config.skipCount = Config.config.skipCount + 1; + } } } } -function unskipSponsorTime(UUID) { +function unskipSponsorTime(segment: SponsorTime) { if (sponsorTimes != null) { //add a tiny bit of time to make sure it is not skipped again - video.currentTime = utils.getSponsorTimeFromUUID(sponsorTimes, UUID).segment[0] + 0.001; + video.currentTime = segment.segment[0] + 0.001; checkIfInsideSegment(); } } -function reskipSponsorTime(UUID) { - if (sponsorTimes != null) { - video.currentTime = utils.getSponsorTimeFromUUID(sponsorTimes, UUID).segment[1]; - - // See if any skips need to be done if this is inside of another segment - startSponsorSchedule(true, utils.getSponsorTimeFromUUID(sponsorTimes, UUID).segment[1]); - } +function reskipSponsorTime(segment: SponsorTime) { + video.currentTime = segment.segment[1]; } /** @@ -1370,7 +1377,7 @@ function vote(type: number, UUID: string, category?: string, skipNotice?: SkipNo if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) { //success (treat rate limits as a success) if (type === 0 || category) { - skipNotice.afterDownvote.bind(skipNotice)(type, category); + skipNotice.afterDownvote.bind(skipNotice)(utils.getSponsorTimeFromUUID(sponsorTimes, UUID), type, category); } } else if (response.successType == 0) { //failure: duplicate vote diff --git a/src/render/SkipNotice.tsx b/src/render/SkipNotice.tsx index 49265fb8..4b969098 100644 --- a/src/render/SkipNotice.tsx +++ b/src/render/SkipNotice.tsx @@ -2,17 +2,18 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import SkipNoticeComponent from "../components/SkipNoticeComponent"; +import { SponsorTime } from "../types"; class SkipNotice { - UUID: string; + segments: SponsorTime[]; autoSkip: boolean; // Contains functions and variables from the content script needed by the skip notice contentContainer: () => any; noticeElement: HTMLDivElement; - constructor(UUID: string, autoSkip: boolean = false, contentContainer) { - this.UUID = UUID; + constructor(segments: SponsorTime[], autoSkip: boolean = false, contentContainer) { + this.segments = segments; this.autoSkip = autoSkip; this.contentContainer = contentContainer; @@ -35,7 +36,11 @@ class SkipNotice { let amountOfPreviousNotices = document.getElementsByClassName("sponsorSkipNotice").length; //this is the suffix added at the end of every id - let idSuffix = this.UUID + amountOfPreviousNotices; + let idSuffix = ""; + for (const segment of this.segments) { + idSuffix += segment.UUID; + } + idSuffix += amountOfPreviousNotices; this.noticeElement = document.createElement("div"); this.noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix; @@ -43,7 +48,7 @@ class SkipNotice { referenceNode.prepend(this.noticeElement); ReactDOM.render( - this.close()} />, diff --git a/src/types.ts b/src/types.ts index 0bf80bd9..002f4daf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,12 +5,12 @@ interface ContentContainer { (): { vote: (type: any, UUID: any, category?: string, skipNotice?: SkipNoticeComponent) => void, dontShowNoticeAgain: () => void, - unskipSponsorTime: (UUID: any) => void, + unskipSponsorTime: (segment: SponsorTime) => void, sponsorTimes: SponsorTime[], sponsorTimesSubmitting: SponsorTime[], v: HTMLVideoElement, sponsorVideoID, - reskipSponsorTime: (UUID: any) => void, + reskipSponsorTime: (segment: SponsorTime) => void, updatePreviewBar: () => void, onMobileYouTube: boolean, sponsorSubmissionNotice: SubmissionNotice,