aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--public/options/options.html12
-rw-r--r--src/components/UpcomingNoticeComponent.tsx99
-rw-r--r--src/config.ts2
-rw-r--r--src/content.ts39
-rw-r--r--src/render/UpcomingNotice.tsx54
-rw-r--r--src/types.ts2
-rw-r--r--src/utils/categoryUtils.ts10
7 files changed, 216 insertions, 2 deletions
diff --git a/public/options/options.html b/public/options/options.html
index b9258f33..e63b9837 100644
--- a/public/options/options.html
+++ b/public/options/options.html
@@ -209,6 +209,18 @@
<div class="small-description">__MSG_skipNoticeDurationDescription__</div>
</div>
+ <div data-type="toggle" data-toggle-type="reverse" data-sync="dontShowUpcomingNotice">
+ <div class="switch-container">
+ <label class="switch">
+ <input id="dontShowUpcomingNotice" type="checkbox" checked>
+ <span class="slider round"></span>
+ </label>
+ <label class="switch-label" for="dontShowUpcomingNotice">
+ __MSG_showUpcomingNotice__
+ </label>
+ </div>
+ </div>
+
<div data-type="toggle" data-toggle-type="reverse" data-sync="dontShowNotice">
<div class="switch-container">
<label class="switch">
diff --git a/src/components/UpcomingNoticeComponent.tsx b/src/components/UpcomingNoticeComponent.tsx
new file mode 100644
index 00000000..71eb2d47
--- /dev/null
+++ b/src/components/UpcomingNoticeComponent.tsx
@@ -0,0 +1,99 @@
+import * as React from "react";
+import { ContentContainer, NoticeVisbilityMode, SponsorTime } from "../types";
+import NoticeComponent from "./NoticeComponent";
+import Config from "../config";
+import { getUpcomingText } from "../utils/categoryUtils";
+
+export interface UpcomingNoticeProps {
+ segments: SponsorTime[];
+
+ autoSkip: boolean;
+ timeUntilSegment: number;
+ contentContainer: ContentContainer;
+
+ closeListener: () => void;
+ showKeybindHint?: boolean;
+}
+
+class UpcomingNoticeComponent extends React.Component<UpcomingNoticeProps> {
+ noticeTitle: string;
+ segments: SponsorTime[];
+ autoSkip: boolean;
+ contentContainer: ContentContainer;
+
+ amountOfPreviousNotices: number;
+ timeUntilSegment: number;
+
+ idSuffix: string;
+
+ noticeRef: React.MutableRefObject<NoticeComponent>;
+
+ configListener: () => void;
+
+ constructor(props: UpcomingNoticeProps) {
+ super(props);
+ this.noticeRef = React.createRef();
+
+ this.segments = props.segments;
+ this.autoSkip = props.autoSkip;
+ this.contentContainer = props.contentContainer;
+ this.timeUntilSegment = props.timeUntilSegment;
+
+ const previousUpcomingNotices = document.getElementsByClassName("sponsorSkipNoticeParent");
+ this.amountOfPreviousNotices = previousUpcomingNotices.length;
+
+ 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
+ for (const segment of this.segments) {
+ this.idSuffix += segment.UUID;
+ }
+ this.idSuffix += this.amountOfPreviousNotices;
+
+ this.noticeTitle = getUpcomingText(this.segments);
+ }
+
+ render(): React.ReactElement {
+ const noticeStyle: React.CSSProperties = { };
+ if (this.contentContainer().onMobileYouTube) {
+ noticeStyle.bottom = "4em";
+ noticeStyle.transform = "scale(0.8) translate(10%, 10%)";
+ }
+
+ return (
+ <NoticeComponent
+ noticeTitle={this.noticeTitle}
+ amountOfPreviousNotices={this.amountOfPreviousNotices}
+ idSuffix={this.idSuffix}
+ fadeIn
+ startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll
+ || (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAutoSkip && this.autoSkip)}
+ timed
+ maxCountdownTime={() => Math.round(this.timeUntilSegment / 1000)}
+ style={noticeStyle}
+ biggerCloseButton={this.contentContainer().onMobileYouTube}
+ ref={this.noticeRef}
+ closeListener={() => this.closeListener()}
+ logoFill={Config.config.barTypes[this.segments[0].category].color}
+ limitWidth
+ dontPauseCountdown />
+ )
+ }
+
+ closeListener(): void {
+ this.clearConfigListener();
+
+ this.props.closeListener();
+ }
+
+ clearConfigListener(): void {
+ if (this.configListener) {
+ Config.configSyncListeners.splice(Config.configSyncListeners.indexOf(this.configListener), 1);
+ this.configListener = null;
+ }
+ }
+}
+
+export default UpcomingNoticeComponent; \ No newline at end of file
diff --git a/src/config.ts b/src/config.ts
index 3fbe23ba..0aff6355 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -31,6 +31,7 @@ interface SBConfig {
trackDownvotes: boolean;
trackDownvotesInPrivate: boolean;
dontShowNotice: boolean;
+ dontShowUpcomingNotice: boolean;
noticeVisibilityMode: NoticeVisbilityMode;
hideVideoPlayerControls: boolean;
hideInfoButtonPlayerControls: boolean;
@@ -293,6 +294,7 @@ const syncDefaults = {
trackDownvotes: true,
trackDownvotesInPrivate: false,
dontShowNotice: false,
+ dontShowUpcomingNotice: false,
noticeVisibilityMode: NoticeVisbilityMode.FadedForAutoSkip,
hideVideoPlayerControls: false,
hideInfoButtonPlayerControls: false,
diff --git a/src/content.ts b/src/content.ts
index 262b51c9..5a9af1d3 100644
--- a/src/content.ts
+++ b/src/content.ts
@@ -20,6 +20,7 @@ import Utils from "./utils";
import PreviewBar, { PreviewBarSegment } from "./js-components/previewBar";
import SkipNotice from "./render/SkipNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent";
+import UpcomingNotice from "./render/UpcomingNotice";
import SubmissionNotice from "./render/SubmissionNotice";
import { Message, MessageResponse, VoteResponse } from "./messageTypes";
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
@@ -77,6 +78,7 @@ let importingChaptersWaiting = false;
let triedImportingChapters = false;
// List of open skip notices
const skipNotices: SkipNotice[] = [];
+const upcomingNotices: UpcomingNotice[] = [];
let activeSkipKeybindElement: ToggleSkippable = null;
let retryFetchTimeout: NodeJS.Timeout = null;
let shownSegmentFailedToFetchWarning = false;
@@ -107,6 +109,7 @@ const lastNextChapterKeybind = {
let currentSkipSchedule: NodeJS.Timeout = null;
let currentSkipInterval: NodeJS.Timeout = null;
let currentVirtualTimeInterval: NodeJS.Timeout = null;
+let currentUpcomingSchedule: NodeJS.Timeout = null;
/** Has the sponsor been skipped */
let sponsorSkipped: boolean[] = [];
@@ -177,6 +180,7 @@ const skipNoticeContentContainer: ContentContainer = () => ({
sponsorTimes,
sponsorTimesSubmitting,
skipNotices,
+ upcomingNotices,
sponsorVideoID: getVideoID(),
reskipSponsorTime,
updatePreviewBar,
@@ -417,6 +421,10 @@ function resetValues() {
skipNotices.pop()?.close();
}
+ for (let i = 0; i < upcomingNotices.length; i++) {
+ upcomingNotices.pop()?.close();
+ }
+
hideDeArrowPromotion();
}
@@ -601,6 +609,11 @@ function cancelSponsorSchedule(): void {
clearInterval(currentSkipInterval);
currentSkipInterval = null;
}
+
+ if (currentUpcomingSchedule !== null) {
+ clearTimeout(currentUpcomingSchedule);
+ currentUpcomingSchedule = null;
+ }
}
/**
@@ -782,7 +795,17 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
const offset = (isFirefoxOrSafari() && !isSafari() ? 600 : 150);
// Schedule for right before to be more precise than normal timeout
- currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - offset));
+ const offsetDelayTime = Math.max(0, delayTime - offset);
+ currentSkipSchedule = setTimeout(skippingFunction, offsetDelayTime);
+
+ // Show the notice only if the segment hasn't already started
+ if (!Config.config.dontShowUpcomingNotice && getCurrentTime() < skippingSegments[0].segment[0]) {
+ const maxPopupTime = 3000;
+ const timeUntilPopup = Math.max(0, offsetDelayTime - maxPopupTime);
+ const popupTime = offsetDelayTime - timeUntilPopup;
+ const autoSkip = shouldAutoSkip(skippingSegments[0])
+ currentUpcomingSchedule = setTimeout(createUpcomingNotice, timeUntilPopup, skippingSegments, popupTime, autoSkip);
+ }
}
}
}
@@ -1784,6 +1807,19 @@ function createSkipNotice(skippingSegments: SponsorTime[], autoSkip: boolean, un
activeSkipKeybindElement = newSkipNotice;
}
+function createUpcomingNotice(skippingSegments: SponsorTime[], timeLeft: number, autoSkip: boolean) {
+ for (const upcomingNotice of upcomingNotices) {
+ if (skippingSegments.length === upcomingNotice.segments.length
+ && skippingSegments.every((segment) => upcomingNotice.segments.some((s) => s.UUID === segment.UUID))) {
+ // Upcoming notice already exists
+ return;
+ }
+ }
+
+ const newUpcomingNotice = new UpcomingNotice(skippingSegments, skipNoticeContentContainer, timeLeft, autoSkip);
+ upcomingNotices.push(newUpcomingNotice);
+}
+
function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null, forceSeek = false) {
if (segment.actionType === ActionType.Mute) {
getVideo().muted = false;
@@ -2561,6 +2597,7 @@ function hotkeyListener(e: KeyboardEvent): void {
} else if (keybindEquals(key, closeSkipNoticeKey)) {
for (let i = 0; i < skipNotices.length; i++) {
skipNotices.pop().close();
+ upcomingNotices.pop().close();
}
return;
diff --git a/src/render/UpcomingNotice.tsx b/src/render/UpcomingNotice.tsx
new file mode 100644
index 00000000..8c0aeffe
--- /dev/null
+++ b/src/render/UpcomingNotice.tsx
@@ -0,0 +1,54 @@
+import * as React from "react";
+import { createRoot, Root } from "react-dom/client";
+import { ContentContainer, SponsorTime } from "../types";
+import UpcomingNoticeComponent from "../components/UpcomingNoticeComponent";
+
+import Utils from "../utils";
+const utils = new Utils();
+
+class UpcomingNotice {
+ segments: SponsorTime[];
+ // Contains functions and variables from the content script needed by the skip notice
+ contentContainer: ContentContainer;
+
+ noticeElement: HTMLDivElement;
+
+ upcomingNoticeRef: React.MutableRefObject<UpcomingNoticeComponent>;
+ root: Root;
+
+ constructor(segments: SponsorTime[], contentContainer: ContentContainer, timeLeft: number, autoSkip: boolean) {
+ this.upcomingNoticeRef = React.createRef();
+
+ this.segments = segments;
+ this.contentContainer = contentContainer;
+
+ const referenceNode = utils.findReferenceNode();
+
+ this.noticeElement = document.createElement("div");
+ this.noticeElement.className = "sponsorSkipNoticeContainer";
+
+ referenceNode.prepend(this.noticeElement);
+
+ this.root = createRoot(this.noticeElement);
+ this.root.render(
+ <UpcomingNoticeComponent
+ segments={segments}
+ autoSkip={autoSkip}
+ contentContainer={contentContainer}
+ timeUntilSegment={timeLeft}
+ ref={this.upcomingNoticeRef}
+ closeListener={() => this.close()} />
+ );
+ }
+
+ close(): void {
+ this.root.unmount();
+
+ this.noticeElement.remove();
+
+ const upcomingNotices = this.contentContainer().upcomingNotices;
+ upcomingNotices.splice(upcomingNotices.indexOf(this), 1);
+ }
+}
+
+export default UpcomingNotice; \ No newline at end of file
diff --git a/src/types.ts b/src/types.ts
index fdaee7b8..ed43fb97 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,6 +1,7 @@
import SubmissionNotice from "./render/SubmissionNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent";
import SkipNotice from "./render/SkipNotice";
+import UpcomingNotice from "./render/UpcomingNotice";
export interface ContentContainer {
(): {
@@ -10,6 +11,7 @@ export interface ContentContainer {
sponsorTimes: SponsorTime[];
sponsorTimesSubmitting: SponsorTime[];
skipNotices: SkipNotice[];
+ upcomingNotices: UpcomingNotice[];
sponsorVideoID;
reskipSponsorTime: (segment: SponsorTime, forceSeek?: boolean) => void;
updatePreviewBar: () => void;
diff --git a/src/utils/categoryUtils.ts b/src/utils/categoryUtils.ts
index d93c2c7b..38f4b8e9 100644
--- a/src/utils/categoryUtils.ts
+++ b/src/utils/categoryUtils.ts
@@ -36,6 +36,15 @@ export function getSkippingText(segments: SponsorTime[], autoSkip: boolean): str
}
}
+export function getUpcomingText(segments: SponsorTime[]): string {
+ const categoryName = chrome.i18n.getMessage(segments.length > 1 ? "multipleSegments"
+ : "category_" + segments[0].category + "_short") || chrome.i18n.getMessage("category_" + segments[0].category);
+
+ const messageId = "upcoming";
+ return chrome.i18n.getMessage(messageId).replace("{0}", categoryName);
+}
+
+
export function getCategorySuffix(category: Category): string {
if (category.startsWith("poi_")) {
return "_POI";
@@ -51,5 +60,4 @@ export function getCategorySuffix(category: Category): string {
export function shortCategoryName(categoryName: string): string {
return chrome.i18n.getMessage("category_" + categoryName + "_short") || chrome.i18n.getMessage("category_" + categoryName);
}
-
export const DEFAULT_CATEGORY = "chooseACategory"; \ No newline at end of file