import * as React from "react"; import { ActionType, SegmentUUID, SponsorHideType, SponsorTime, VideoID } from "../types"; import Config from "../config"; import { waitFor } from "../../maze-utils/src"; import { shortCategoryName } from "../utils/categoryUtils"; import { getErrorMessage, getFormattedTime } from "../../maze-utils/src/formating"; import { AnimationUtils } from "../../maze-utils/src/animationUtils"; import { asyncRequestToServer } from "../utils/requests"; import { Message, MessageResponse, VoteResponse } from "../messageTypes"; import { LoadingStatus } from "./PopupComponent"; import GenericNotice from "../render/GenericNotice"; import { exportTimes } from "../utils/exporter"; import { copyToClipboardPopup } from "./popupUtils"; interface SegmentListComponentProps { videoID: VideoID; currentTime: number; status: LoadingStatus; segments: SponsorTime[]; loopedChapter: SegmentUUID | null; sendMessage: (request: Message) => Promise; } enum SegmentListTab { Segments, Chapter } export const SegmentListComponent = (props: SegmentListComponentProps) => { const [tab, setTab] = React.useState(SegmentListTab.Segments); const [isVip, setIsVip] = React.useState(Config.config?.isVip ?? false); React.useEffect(() => { if (!Config.isReady()) { waitFor(() => Config.isReady()).then(() => { setIsVip(Config.config.isVip); }); } else { setIsVip(Config.config.isVip); } }, []); React.useEffect(() => { setTab(SegmentListTab.Segments); }, [props.videoID]); const tabFilter = (segment: SponsorTime) => { if (tab === SegmentListTab.Chapter) { return segment.actionType === ActionType.Chapter; } else { return segment.actionType !== ActionType.Chapter; } }; return (
s.actionType === ActionType.Chapter) ? "" : "hidden"}> { setTab(SegmentListTab.Segments); }}> {chrome.i18n.getMessage("SegmentsCap")} { setTab(SegmentListTab.Chapter); }}> {chrome.i18n.getMessage("Chapters")}
selectSegment({ segment: null, sendMessage: props.sendMessage })}> { props.segments.map((segment) => ( )) }
); }; function SegmentListItem({ segment, videoID, currentTime, isVip, startingLooped, tabFilter, sendMessage }: { segment: SponsorTime; videoID: VideoID; currentTime: number; isVip: boolean; startingLooped: boolean; tabFilter: (segment: SponsorTime) => boolean; sendMessage: (request: Message) => Promise; }) { const [voteMessage, setVoteMessage] = React.useState(null); const [hidden, setHidden] = React.useState(segment.hidden || SponsorHideType.Visible); const [isLooped, setIsLooped] = React.useState(startingLooped); let extraInfo = ""; if (segment.hidden === SponsorHideType.Downvoted) { // This one is downvoted extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDownvote") + ")"; } else if (segment.hidden === SponsorHideType.MinimumDuration) { // This one is too short extraInfo = " (" + chrome.i18n.getMessage("hiddenDueToDuration") + ")"; } else if (segment.hidden === SponsorHideType.Hidden) { extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")"; } return (
skipSegment({ segment, sendMessage })} onMouseEnter={() => { selectSegment({ segment, sendMessage }); }} className={"votingButtons " + (!tabFilter(segment) ? "hidden" : "")}> = segment.segment[0] ? ( currentTime < segment.segment[1] ? "segmentActive" : "segmentPassed" ) : "" )}>
{ segment.actionType !== ActionType.Chapter && } {(segment.description || shortCategoryName(segment.category)) + extraInfo}
{ segment.actionType === ActionType.Full ? chrome.i18n.getMessage("full") : (getFormattedTime(segment.segment[0], true) + (segment.actionType !== ActionType.Poi ? " " + chrome.i18n.getMessage("to") + " " + getFormattedTime(segment.segment[1], true) : "")) }
{ vote({ type: 1, UUID: segment.UUID, setVoteMessage: setVoteMessage, sendMessage }); }}/> { vote({ type: 0, UUID: segment.UUID, setVoteMessage: setVoteMessage, sendMessage }); }}/> { const stopAnimation = AnimationUtils.applyLoadingAnimation(e.currentTarget, 0.3); if (segment.UUID.length > 60) { copyToClipboardPopup(segment.UUID, sendMessage); } else { const segmentIDData = await asyncRequestToServer("GET", "/api/segmentID", { UUID: segment.UUID, videoID: videoID }); if (segmentIDData.ok && segmentIDData.responseText) { copyToClipboardPopup(segmentIDData.responseText, sendMessage); } } stopAnimation(); }}/> { segment.actionType === ActionType.Chapter && { if (isLooped) { loopChapter({ segment: null, element: e.currentTarget, sendMessage }); } else { loopChapter({ segment, element: e.currentTarget, sendMessage }); } setIsLooped(!isLooped); }}/> } { (segment.actionType === ActionType.Skip || segment.actionType === ActionType.Mute || segment.actionType === ActionType.Poi && [SponsorHideType.Visible, SponsorHideType.Hidden].includes(segment.hidden)) && { const stopAnimation = AnimationUtils.applyLoadingAnimation(e.currentTarget, 0.4); stopAnimation(); if (segment.hidden === SponsorHideType.Hidden) { segment.hidden = SponsorHideType.Visible; setHidden(SponsorHideType.Visible); } else { segment.hidden = SponsorHideType.Hidden; setHidden(SponsorHideType.Hidden); } sendMessage({ message: "hideSegment", type: segment.hidden, UUID: segment.UUID }); }}/> } { segment.actionType !== ActionType.Full && { skipSegment({ segment, element: e.currentTarget, sendMessage }); }}/> }
{voteMessage}
); } async function vote(props: { type: number; UUID: SegmentUUID; setVoteMessage: (message: string | null) => void; sendMessage: (request: Message) => Promise; }): Promise { props.setVoteMessage(chrome.i18n.getMessage("Loading")); const response = await props.sendMessage({ message: "submitVote", type: props.type, UUID: props.UUID }) as VoteResponse; if (response != undefined) { // See if it was a success or failure if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) { // Success (treat rate limits as a success) props.setVoteMessage(chrome.i18n.getMessage("voted")); } else if (response.successType == -1) { props.setVoteMessage(getErrorMessage(response.statusCode, response.responseText)); } setTimeout(() => props.setVoteMessage(null), 1500); } } function skipSegment({ segment, element, sendMessage }: { segment: SponsorTime; element?: HTMLElement; sendMessage: (request: Message) => Promise; }): void { if (segment.actionType === ActionType.Chapter) { sendMessage({ message: "unskip", UUID: segment.UUID }); } else { sendMessage({ message: "reskip", UUID: segment.UUID }); } if (element) { const stopAnimation = AnimationUtils.applyLoadingAnimation(element, 0.3); stopAnimation(); } } function selectSegment({ segment, sendMessage }: { segment: SponsorTime | null; sendMessage: (request: Message) => Promise; }): void { sendMessage({ message: "selectSegment", UUID: segment?.UUID }); } function loopChapter({ segment, element, sendMessage }: { segment: SponsorTime; element: HTMLElement; sendMessage: (request: Message) => Promise; }): void { sendMessage({ message: "loopChapter", UUID: segment?.UUID }); if (element) { const stopAnimation = AnimationUtils.applyLoadingAnimation(element, 0.3); stopAnimation(); } } interface ImportSegmentsProps { status: LoadingStatus; segments: SponsorTime[]; sendMessage: (request: Message) => Promise; } function ImportSegments(props: ImportSegmentsProps) { const [importMenuVisible, setImportMenuVisible] = React.useState(false); const textArea = React.useRef(null); return (
) }