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"; export interface SubmissionNoticeProps { // Contains functions and variables from the content script needed by the skip notice contentContainer: ContentContainer; callback: () => Promise; closeListener: () => void; } export interface SubmissionNoticeState { noticeTitle: string; messages: string[]; idSuffix: string; } class SubmissionNoticeComponent extends React.Component { // Contains functions and variables from the content script needed by the skip notice contentContainer: ContentContainer; callback: () => unknown; noticeRef: React.MutableRefObject; timeEditRefs: React.RefObject[]; 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(this.contentContainer().v, { 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 = this.sortSegments()} title={chrome.i18n.getMessage("sortSegments")} key="sortButton" src={chrome.runtime.getURL("icons/sort.svg")}> ; const exportButton = this.exportSegments()} title={chrome.i18n.getMessage("exportSegments")} key="exportButton" src={chrome.runtime.getURL("icons/export.svg")}> ; return ( {/* Text Boxes */} {this.getMessageBoxes()} {/* Sponsor Time List */} e.stopPropagation()}> {this.getSponsorTimeMessages()} {/* Last Row */} {/* Guidelines button */} {/* Submit Button */} ); } 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(); elements.push( ); 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( ); } return elements; } cancel(): void { this.guidelinesReminder?.close(); this.noticeRef.current.close(true); this.contentContainer().resetSponsorSubmissionNotice(false); this.props.closeListener(); } submit(): void { // 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 && this.contentContainer().v.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;