mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-06 11:37:02 +03:00
330 lines
12 KiB
TypeScript
330 lines
12 KiB
TypeScript
import * as React from "react";
|
|
import Config from "../config"
|
|
import GenericNotice from "../render/GenericNotice";
|
|
import { Category, ContentContainer } from "../types";
|
|
import * as CompileConfig from "../../config.json";
|
|
|
|
import NoticeComponent from "./NoticeComponent";
|
|
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
|
|
import SponsorTimeEditComponent from "./SponsorTimeEditComponent";
|
|
import { getGuidelineInfo } from "../utils/constants";
|
|
import { exportTimes } from "../utils/exporter";
|
|
import { getVideo, isCurrentTimeWrong } from "../../maze-utils/src/video";
|
|
|
|
export interface SubmissionNoticeProps {
|
|
// Contains functions and variables from the content script needed by the skip notice
|
|
contentContainer: ContentContainer;
|
|
|
|
callback: () => Promise<boolean>;
|
|
|
|
closeListener: () => void;
|
|
}
|
|
|
|
export interface SubmissionNoticeState {
|
|
noticeTitle: string;
|
|
messages: string[];
|
|
idSuffix: string;
|
|
}
|
|
|
|
class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, SubmissionNoticeState> {
|
|
// Contains functions and variables from the content script needed by the skip notice
|
|
contentContainer: ContentContainer;
|
|
|
|
callback: () => unknown;
|
|
|
|
noticeRef: React.MutableRefObject<NoticeComponent>;
|
|
timeEditRefs: React.RefObject<SponsorTimeEditComponent>[];
|
|
|
|
videoObserver: MutationObserver;
|
|
|
|
guidelinesReminder: GenericNotice;
|
|
|
|
lastSegmentCount: number;
|
|
|
|
constructor(props: SubmissionNoticeProps) {
|
|
super(props);
|
|
this.noticeRef = React.createRef();
|
|
|
|
this.contentContainer = props.contentContainer;
|
|
this.callback = props.callback;
|
|
|
|
const noticeTitle = chrome.i18n.getMessage("confirmNoticeTitle");
|
|
|
|
this.lastSegmentCount = this.props.contentContainer().sponsorTimesSubmitting.length;
|
|
|
|
// Setup state
|
|
this.state = {
|
|
noticeTitle,
|
|
messages: [],
|
|
idSuffix: "SubmissionNotice"
|
|
};
|
|
}
|
|
|
|
componentDidMount(): void {
|
|
// Catch and rerender when the video size changes
|
|
//TODO: Use ResizeObserver when it is supported in TypeScript
|
|
this.videoObserver = new MutationObserver(() => {
|
|
this.forceUpdate();
|
|
});
|
|
|
|
this.videoObserver.observe(getVideo(), {
|
|
attributes: true
|
|
});
|
|
|
|
// Prevent zooming while changing times
|
|
document.getElementById("sponsorSkipNoticeMiddleRow" + this.state.idSuffix).addEventListener('wheel', function (event) {
|
|
if (event.ctrlKey) {
|
|
event.preventDefault();
|
|
}
|
|
}, {passive: false});
|
|
}
|
|
|
|
componentWillUnmount(): void {
|
|
if (this.videoObserver) {
|
|
this.videoObserver.disconnect();
|
|
}
|
|
}
|
|
|
|
componentDidUpdate() {
|
|
const currentSegmentCount = this.props.contentContainer().sponsorTimesSubmitting.length;
|
|
if (currentSegmentCount > this.lastSegmentCount) {
|
|
this.lastSegmentCount = currentSegmentCount;
|
|
|
|
this.scrollToBottom();
|
|
}
|
|
}
|
|
|
|
scrollToBottom() {
|
|
const scrollElement = this.noticeRef.current.getElement().current.querySelector("#sponsorSkipNoticeMiddleRowSubmissionNotice");
|
|
scrollElement.scrollTo({
|
|
top: scrollElement.scrollHeight + 1000
|
|
});
|
|
}
|
|
|
|
render(): React.ReactElement {
|
|
const sortButton =
|
|
<img id={"sponsorSkipSortButton" + this.state.idSuffix}
|
|
className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipSmallButton"
|
|
onClick={() => this.sortSegments()}
|
|
title={chrome.i18n.getMessage("sortSegments")}
|
|
key="sortButton"
|
|
src={chrome.runtime.getURL("icons/sort.svg")}>
|
|
</img>;
|
|
const exportButton =
|
|
<img id={"sponsorSkipExportButton" + this.state.idSuffix}
|
|
className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipSmallButton"
|
|
onClick={() => this.exportSegments()}
|
|
title={chrome.i18n.getMessage("exportSegments")}
|
|
key="exportButton"
|
|
src={chrome.runtime.getURL("icons/export.svg")}>
|
|
</img>;
|
|
return (
|
|
<NoticeComponent noticeTitle={this.state.noticeTitle}
|
|
idSuffix={this.state.idSuffix}
|
|
ref={this.noticeRef}
|
|
closeListener={this.cancel.bind(this)}
|
|
zIndex={5000}
|
|
firstColumn={[sortButton, exportButton]}>
|
|
|
|
{/* Text Boxes */}
|
|
{this.getMessageBoxes()}
|
|
|
|
{/* Sponsor Time List */}
|
|
<tr id={"sponsorSkipNoticeMiddleRow" + this.state.idSuffix}
|
|
className="sponsorTimeMessagesRow"
|
|
style={{maxHeight: (getVideo()?.offsetHeight - 200) + "px"}}
|
|
onMouseDown={(e) => e.stopPropagation()}>
|
|
<td style={{width: "100%"}}>
|
|
{this.getSponsorTimeMessages()}
|
|
</td>
|
|
</tr>
|
|
|
|
{/* Last Row */}
|
|
<tr id={"sponsorSkipNoticeSecondRow" + this.state.idSuffix}>
|
|
|
|
<td className="sponsorSkipNoticeRightSection"
|
|
style={{position: "relative"}}>
|
|
|
|
{/* Guidelines button */}
|
|
<button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton"
|
|
onClick={() => window.open("https://wiki.sponsor.ajay.app/w/Guidelines")}>
|
|
|
|
{chrome.i18n.getMessage(Config.config.submissionCountSinceCategories > 3 ? "guidelines" : "readTheGuidelines")}
|
|
</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 {
|
|
const elements: JSX.Element[] = [];
|
|
this.timeEditRefs = [];
|
|
|
|
const sponsorTimes = this.props.contentContainer().sponsorTimesSubmitting;
|
|
|
|
for (let i = 0; i < sponsorTimes.length; i++) {
|
|
const timeRef = React.createRef<SponsorTimeEditComponent>();
|
|
|
|
elements.push(
|
|
<SponsorTimeEditComponent key={sponsorTimes[i].UUID}
|
|
idSuffix={this.state.idSuffix + i}
|
|
index={i}
|
|
contentContainer={this.props.contentContainer}
|
|
submissionNotice={this}
|
|
categoryChangeListener={this.categoryChangeListener.bind(this)}
|
|
ref={timeRef}>
|
|
</SponsorTimeEditComponent>
|
|
);
|
|
|
|
this.timeEditRefs.push(timeRef);
|
|
}
|
|
|
|
return elements;
|
|
}
|
|
|
|
getMessageBoxes(): JSX.Element[] | JSX.Element {
|
|
const 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(): void {
|
|
this.guidelinesReminder?.close();
|
|
this.noticeRef.current.close(true);
|
|
|
|
this.contentContainer().resetSponsorSubmissionNotice(false);
|
|
|
|
this.props.closeListener();
|
|
}
|
|
|
|
submit(): void {
|
|
if (isCurrentTimeWrong()) {
|
|
alert(chrome.i18n.getMessage("submissionFailedServerSideAds"));
|
|
return;
|
|
}
|
|
|
|
// save all items
|
|
for (const ref of this.timeEditRefs) {
|
|
ref.current.saveEditTimes();
|
|
}
|
|
|
|
const sponsorTimesSubmitting = this.props.contentContainer().sponsorTimesSubmitting;
|
|
for (const sponsorTime of sponsorTimesSubmitting) {
|
|
if (sponsorTime.category === "chooseACategory") {
|
|
alert(chrome.i18n.getMessage("youMustSelectACategory"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check if any non music categories are being used on a music video
|
|
if (this.contentContainer().videoInfo?.microformat?.playerMicroformatRenderer?.category === "Music") {
|
|
for (const sponsorTime of sponsorTimesSubmitting) {
|
|
if (sponsorTime.category === "sponsor") {
|
|
if (!confirm(chrome.i18n.getMessage("nonMusicCategoryOnMusic"))) return;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.props.callback().then((success) => {
|
|
if (success) {
|
|
this.cancel();
|
|
}
|
|
});
|
|
}
|
|
|
|
sortSegments(): void {
|
|
let sponsorTimesSubmitting = this.props.contentContainer().sponsorTimesSubmitting;
|
|
sponsorTimesSubmitting = sponsorTimesSubmitting.sort((a, b) => a.segment[0] - b.segment[0]);
|
|
|
|
Config.local.unsubmittedSegments[this.props.contentContainer().sponsorVideoID] = sponsorTimesSubmitting;
|
|
Config.forceLocalUpdate("unsubmittedSegments");
|
|
|
|
this.forceUpdate();
|
|
}
|
|
|
|
exportSegments() {
|
|
const sponsorTimesSubmitting = this.props.contentContainer()
|
|
.sponsorTimesSubmitting.sort((a, b) => a.segment[0] - b.segment[0]);
|
|
window.navigator.clipboard.writeText(exportTimes(sponsorTimesSubmitting));
|
|
|
|
new GenericNotice(null, "exportCopied", {
|
|
title: chrome.i18n.getMessage(`CopiedExclamation`),
|
|
timed: true,
|
|
maxCountdownTime: () => 0.6,
|
|
referenceNode: document.querySelector(".noticeLeftIcon"),
|
|
dontPauseCountdown: true,
|
|
style: {
|
|
top: 0,
|
|
bottom: 0,
|
|
minWidth: 0,
|
|
right: "30px",
|
|
margin: "auto"
|
|
},
|
|
hideLogo: true,
|
|
hideRightInfo: true,
|
|
extraClass: "exportCopiedNotice"
|
|
});
|
|
}
|
|
|
|
categoryChangeListener(index: number, category: Category): void {
|
|
const dialogWidth = this.noticeRef?.current?.getElement()?.current?.offsetWidth;
|
|
if (category !== "chooseACategory" && Config.config.showCategoryGuidelines
|
|
&& getVideo().offsetWidth > dialogWidth * 2) {
|
|
const options = {
|
|
title: chrome.i18n.getMessage(`category_${category}`),
|
|
textBoxes: getGuidelineInfo(category),
|
|
buttons: [{
|
|
name: chrome.i18n.getMessage("FullDetails"),
|
|
listener: () => window.open(CompileConfig.wikiLinks[category])
|
|
},
|
|
{
|
|
name: chrome.i18n.getMessage("Hide"),
|
|
listener: () => {
|
|
Config.config.showCategoryGuidelines = false;
|
|
this.guidelinesReminder?.close();
|
|
this.guidelinesReminder = null;
|
|
}
|
|
}],
|
|
timed: false,
|
|
style: {
|
|
right: `${dialogWidth + 10}px`,
|
|
},
|
|
extraClass: "sb-guidelines-notice"
|
|
};
|
|
|
|
if (options.textBoxes) {
|
|
if (this.guidelinesReminder) {
|
|
this.guidelinesReminder.update(options);
|
|
} else {
|
|
this.guidelinesReminder = new GenericNotice(null, "GuidelinesReminder", options);
|
|
}
|
|
} else {
|
|
this.guidelinesReminder?.close();
|
|
this.guidelinesReminder = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export default SubmissionNoticeComponent;
|