diff options
author | Ajay Ramachandran <[email protected]> | 2021-05-16 20:48:22 -0400 |
---|---|---|
committer | GitHub <[email protected]> | 2021-05-16 20:48:22 -0400 |
commit | 592af4e20f08549b2afb63c3e94cadb791d27b93 (patch) | |
tree | e85de1f939db9a254466d60e8da936135fd768da | |
parent | daa7a653c9f21f8e83d4043654985493a2705b6f (diff) | |
parent | ecfcb0b846b07ae2fa5b5a4c1aa5e863f04efb5c (diff) | |
download | SponsorBlock-592af4e20f08549b2afb63c3e94cadb791d27b93.tar.gz SponsorBlock-592af4e20f08549b2afb63c3e94cadb791d27b93.zip |
Merge pull request #569 from opl-/cleanup/segment-creation
Clean up segment creation code
-rw-r--r-- | public/_locales/en/messages.json | 3 | ||||
-rw-r--r-- | public/_locales/pl/messages.json | 3 | ||||
-rw-r--r-- | public/popup.html | 2 | ||||
-rw-r--r-- | src/components/SponsorTimeEditComponent.tsx | 4 | ||||
-rw-r--r-- | src/config.ts | 1 | ||||
-rw-r--r-- | src/content.ts | 329 | ||||
-rw-r--r-- | src/messageTypes.ts | 11 | ||||
-rw-r--r-- | src/popup.ts | 120 | ||||
-rw-r--r-- | src/types.ts | 8 | ||||
-rw-r--r-- | src/utils.ts | 4 |
10 files changed, 222 insertions, 263 deletions
diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 035578bd..f58eefb4 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -79,6 +79,9 @@ "sponsorEnd": { "message": "Segment Ends Now" }, + "sponsorCancel": { + "message": "Cancel Creating Segment" + }, "noVideoID": { "message": "No YouTube video found.\nIf this is incorrect, refresh the tab." }, diff --git a/public/_locales/pl/messages.json b/public/_locales/pl/messages.json index 5544f8ba..faf25472 100644 --- a/public/_locales/pl/messages.json +++ b/public/_locales/pl/messages.json @@ -79,6 +79,9 @@ "sponsorEnd": { "message": "Koniec segmentu" }, + "sponsorCancel": { + "message": "Anuluj tworzenie segmentu" + }, "noVideoID": { "message": "Nie znaleziono filmu YouTube.\nJeżeli to błąd, odśwież stronę." }, diff --git a/public/popup.html b/public/popup.html index 435007f7..4f3669eb 100644 --- a/public/popup.html +++ b/public/popup.html @@ -71,7 +71,7 @@ </div> <div id="submissionSection" style="display: none"> <b style="display: block; margin-top: 12px;">__MSG_submissionEditHint__</b> - <div id="submitTimesContainer" style="display: none; margin-top: 12px;"> + <div id="submitTimesContainer" style="margin-top: 12px;"> <button id="submitTimes" class="mediumButton">__MSG_submitTimesButton__</button> </div> </div> diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index e00813ca..aa2ff326 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -344,7 +344,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo //if it is not a complete sponsor time if (sponsorTimes[index].segment.length < 2) { //update video player - this.props.contentContainer().changeStartSponsorButton(true, false); + this.props.contentContainer().updateEditButtonsOnPlayer(); } sponsorTimes.splice(index, 1); @@ -359,7 +359,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo this.props.submissionNotice.cancel(); //update video player - this.props.contentContainer().changeStartSponsorButton(true, false); + this.props.contentContainer().updateEditButtonsOnPlayer(); } else { //update display this.props.submissionNotice.forceUpdate(); diff --git a/src/config.ts b/src/config.ts index 3afb029c..a385e581 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,6 +6,7 @@ const utils = new Utils(); interface SBConfig { userID: string, + /** Contains unsubmitted segments that the user has created. */ segmentTimes: SBMap<string, SponsorTime[]>, defaultCategory: string, whitelistedChannels: string[], diff --git a/src/content.ts b/src/content.ts index 619f783b..22ab7c3e 100644 --- a/src/content.ts +++ b/src/content.ts @@ -1,6 +1,6 @@ import Config from "./config"; -import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, FetchResponse, VideoInfo, StorageChangesObject } from "./types"; +import { SponsorTime, IncompleteSponsorTime, CategorySkipOption, VideoID, SponsorHideType, FetchResponse, VideoInfo, StorageChangesObject } from "./types"; import { ContentContainer } from "./types"; import Utils from "./utils"; @@ -71,8 +71,11 @@ let channelWhitelisted = false; // create preview bar let previewBar: PreviewBar = null; -//the player controls on the YouTube player -let controls = null; +/** Element containing the player controls on the YouTube player. */ +let controls: HTMLElement | null = null; + +/** Contains buttons created by `createButton()`. */ +const playerButtons: Record<string, {button: HTMLButtonElement, image: HTMLImageElement}> = {}; // Direct Links after the config is loaded utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document.URL))); @@ -81,10 +84,10 @@ utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYo //this only happens if there is an error let sponsorLookupRetries = 0; -//if showing the start sponsor button or the end sponsor button on the player -let showingStartSponsor = true; +/** Currently timed segment, which will be added to the unsubmitted segments when ready. */ +let currentlyTimedSegment: IncompleteSponsorTime | null = null; -//the sponsor times being prepared to be submitted +/** Segments created by the user which have not yet been submitted. */ let sponsorTimesSubmitting: SponsorTime[] = []; //becomes true when isInfoFound is called @@ -111,7 +114,7 @@ const skipNoticeContentContainer: ContentContainer = () => ({ onMobileYouTube, sponsorSubmissionNotice: submissionNotice, resetSponsorSubmissionNotice, - changeStartSponsorButton, + updateEditButtonsOnPlayer, previewTime, videoInfo, getRealCurrentTime: getRealCurrentTime @@ -127,11 +130,11 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo videoIDChange(getYouTubeVideoID(document.URL)); break; case "sponsorStart": - sponsorMessageStarted(sendResponse); + startOrEndTimingNewSegment() - break; - case "sponsorDataChanged": - updateSponsorTimesSubmitting(); + sendResponse({ + creatingSegment: currentlyTimedSegment !== null, + }); break; case "isInfoFound": @@ -150,7 +153,8 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo break; case "getVideoID": sendResponse({ - videoID: sponsorVideoID + videoID: sponsorVideoID, + creatingSegment: currentlyTimedSegment !== null, }); break; @@ -171,10 +175,6 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo sponsorsLookup(sponsorVideoID); break; - case "changeStartSponsorButton": - changeStartSponsorButton(request.showStartSponsor, request.uploadButtonVisible); - - break; case "submitTimes": submitSponsorTimes(); break; @@ -298,33 +298,21 @@ async function videoIDChange(id) { //close popup closeInfoMenu(); - + sponsorsLookup(id); - //make sure everything is properly added - updateVisibilityOfPlayerControlsButton().then(() => { - //see if the onvideo control image needs to be changed - const segments = Config.config.segmentTimes.get(sponsorVideoID); - if (segments != null && segments.length > 0 && segments[segments.length - 1].segment.length >= 2) { - changeStartSponsorButton(true, true); - } else if (segments != null && segments.length > 0 && segments[segments.length - 1].segment.length < 2) { - changeStartSponsorButton(false, true); - } else { - changeStartSponsorButton(true, false); - } - }); + // Make sure all player buttons are properly added + updateVisibilityOfPlayerControlsButton(); - //reset sponsor times submitting + // Clear unsubmitted segments from the previous video sponsorTimesSubmitting = []; + currentlyTimedSegment = null; updateSponsorTimesSubmitting(); - - //see if video controls buttons should be added - if (!onInvidious) { - updateVisibilityOfPlayerControlsButton(); - } } function handleMobileControlsMutations(): void { + updateVisibilityOfPlayerControlsButton(); + if (previewBar !== null) { if (document.body.contains(previewBar.container)) { const progressBarBackground = document.querySelector<HTMLElement>(".progress-bar-background"); @@ -1043,8 +1031,9 @@ function reskipSponsorTime(segment: SponsorTime) { startSponsorSchedule(true, segment.segment[1], false); } -function createButton(baseID, title, callback, imageName, isDraggable=false): boolean { - if (document.getElementById(baseID + "Button") != null) return false; +function createButton(baseID: string, title: string, callback: () => void, imageName: string, isDraggable = false): HTMLElement { + const existingElement = document.getElementById(baseID + "Button"); + if (existingElement !== null) return existingElement; // Button HTML const newButton = document.createElement("button"); @@ -1068,9 +1057,15 @@ function createButton(baseID, title, callback, imageName, isDraggable=false): bo newButton.appendChild(newButtonImage); // Add the button to player - controls.prepend(newButton); + if (controls) controls.prepend(newButton); - return true; + // Store the elements to prevent unnecessary querying + playerButtons[baseID] = { + button: newButton, + image: newButtonImage, + }; + + return newButton; } function getControls(): HTMLElement | false { @@ -1080,8 +1075,8 @@ function getControls(): HTMLElement | false { // Mobile YouTube ".player-controls-top", // Invidious/videojs video element's controls element - ".vjs-control-bar" - ] + ".vjs-control-bar", + ]; for (const controlsSelector of controlsSelectors) { const controls = document.querySelectorAll(controlsSelector); @@ -1094,53 +1089,75 @@ function getControls(): HTMLElement | false { return false; } -//adds all the player controls buttons -async function createButtons(): Promise<boolean> { +/** Creates any missing buttons on the YouTube player if possible. */ +async function createButtons(): Promise<void> { if (onMobileYouTube) return; - const result = await utils.wait(getControls).catch(); - - //set global controls variable - controls = result; - - let createdButton = false; + controls = await utils.wait(getControls).catch(); // Add button if does not already exist in html - createdButton = createButton("startSponsor", "sponsorStart", startSponsorClicked, "PlayerStartIconSponsorBlocker256px.png") || createdButton; - createdButton = createButton("info", "openPopup", openInfoMenu, "PlayerInfoIconSponsorBlocker256px.png") || createdButton; - createdButton = createButton("delete", "clearTimes", clearSponsorTimes, "PlayerDeleteIconSponsorBlocker256px.png") || createdButton; - createdButton = createButton("submit", "SubmitTimes", submitSponsorTimes, "PlayerUploadIconSponsorBlocker256px.png") || createdButton; - - return createdButton; + createButton("startSponsor", "sponsorStart", () => closeInfoMenuAnd(() => startOrEndTimingNewSegment()), "PlayerStartIconSponsorBlocker256px.png"); + createButton("cancelSponsor", "sponsorCancel", () => closeInfoMenuAnd(() => cancelCreatingSegment()), "PlayerUploadFailedIconSponsorBlocker256px.png"); + createButton("info", "openPopup", openInfoMenu, "PlayerInfoIconSponsorBlocker256px.png"); + createButton("delete", "clearTimes", () => closeInfoMenuAnd(() => clearSponsorTimes()), "PlayerDeleteIconSponsorBlocker256px.png"); + createButton("submit", "SubmitTimes", submitSponsorTimes, "PlayerUploadIconSponsorBlocker256px.png"); } -//adds or removes the player controls button to what it should be -async function updateVisibilityOfPlayerControlsButton(): Promise<boolean> { - //not on a proper video yet - if (!sponsorVideoID) return false; +/** Creates any missing buttons on the player and updates their visiblity. */ +async function updateVisibilityOfPlayerControlsButton(): Promise<void> { + // Not on a proper video yet + if (!sponsorVideoID) return; - const createdButtons = await createButtons(); - if (!createdButtons) return; + await createButtons(); - if (Config.config.hideVideoPlayerControls || onInvidious) { - document.getElementById("startSponsorButton").style.display = "none"; - document.getElementById("submitButton").style.display = "none"; - } else { - document.getElementById("startSponsorButton").style.removeProperty("display"); - } + updateEditButtonsOnPlayer(); - //don't show the info button on embeds + // Don't show the info button on embeds if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious) { - document.getElementById("infoButton").style.display = "none"; + playerButtons.info.button.style.display = "none"; } else { - document.getElementById("infoButton").style.removeProperty("display"); + playerButtons.info.button.style.removeProperty("display"); } - - if (Config.config.hideDeleteButtonPlayerControls || onInvidious) { - document.getElementById("deleteButton").style.display = "none"; +} + +/** Updates the visibility of buttons on the player related to creating segments. */ +function updateEditButtonsOnPlayer(): void { + // Don't try to update the buttons if we aren't on a YouTube video page + if (!sponsorVideoID) return; + + const buttonsEnabled = !Config.config.hideVideoPlayerControls && !onInvidious; + + let creatingSegment = false; + let submitButtonVisible = false; + let deleteButtonVisible = false; + + // Only check if buttons should be visible if they're enabled + if (buttonsEnabled) { + creatingSegment = currentlyTimedSegment !== null; + + // Show only if there are any segments to submit + submitButtonVisible = sponsorTimesSubmitting.length > 0; + + // Show only if there are any segments to delete + deleteButtonVisible = sponsorTimesSubmitting.length > 0; } - return createdButtons; + // Update the elements + playerButtons.startSponsor.button.style.display = buttonsEnabled ? "unset" : "none"; + playerButtons.cancelSponsor.button.style.display = buttonsEnabled && creatingSegment ? "unset" : "none"; + + if (buttonsEnabled) { + if (creatingSegment) { + playerButtons.startSponsor.image.src = chrome.extension.getURL("icons/PlayerStopIconSponsorBlocker256px.png"); + playerButtons.startSponsor.button.setAttribute("title", chrome.i18n.getMessage("sponsorEnd")); + } else { + playerButtons.startSponsor.image.src = chrome.extension.getURL("icons/PlayerStartIconSponsorBlocker256px.png"); + playerButtons.startSponsor.button.setAttribute("title", chrome.i18n.getMessage("sponsorStart")); + } + } + + playerButtons.submit.button.style.display = submitButtonVisible && !Config.config.hideUploadButtonPlayerControls ? "unset" : "none"; + playerButtons.delete.button.style.display = deleteButtonVisible && !Config.config.hideDeleteButtonPlayerControls ? "unset" : "none"; } /** @@ -1161,30 +1178,40 @@ function getRealCurrentTime(): number { } } -function startSponsorClicked() { - //it can't update to this info yet - closeInfoMenu(); - - toggleStartSponsorButton(); - - //add to sponsorTimes - if (sponsorTimesSubmitting.length > 0 && sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.length < 2) { - //it is an end time - sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment[1] = getRealCurrentTime(); - sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.sort((a, b) => a > b ? 1 : (a < b ? -1 : 0)); - } else { - //it is a start time - sponsorTimesSubmitting.push({ +function startOrEndTimingNewSegment() { + if (!currentlyTimedSegment) { + // Start a new segment + currentlyTimedSegment = { segment: [getRealCurrentTime()], UUID: null, - category: Config.config.defaultCategory + category: Config.config.defaultCategory, + }; + } else { + // Finish creating the new segment + const existingTime = currentlyTimedSegment.segment[0]; + const currentTime = getRealCurrentTime(); + + sponsorTimesSubmitting.push({ + ...currentlyTimedSegment, + // Swap timestamps if the user put the segment end before the start + segment: [Math.min(existingTime, currentTime), Math.max(existingTime, currentTime)], }); + + currentlyTimedSegment = null; + + // Save the newly created segment + Config.config.segmentTimes.set(sponsorVideoID, sponsorTimesSubmitting); } - //save this info - Config.config.segmentTimes.set(sponsorVideoID, sponsorTimesSubmitting); + updateEditButtonsOnPlayer(); - updateSponsorTimesSubmitting(false) + updateSponsorTimesSubmitting(false); +} + +function cancelCreatingSegment() { + currentlyTimedSegment = null; + + updateEditButtonsOnPlayer(); } function updateSponsorTimesSubmitting(getFromConfig = true) { @@ -1213,38 +1240,6 @@ function updateSponsorTimesSubmitting(getFromConfig = true) { } } -async function changeStartSponsorButton(showStartSponsor: boolean, uploadButtonVisible: boolean): Promise<boolean> { - if(!sponsorVideoID || onMobileYouTube) return false; - - //if it isn't visible, there is no data - const shouldHide = (uploadButtonVisible && !(Config.config.hideDeleteButtonPlayerControls || onInvidious)) ? "unset" : "none" - document.getElementById("deleteButton").style.display = shouldHide; - - if (showStartSponsor) { - showingStartSponsor = true; - (<HTMLImageElement> document.getElementById("startSponsorImage")).src = chrome.extension.getURL("icons/PlayerStartIconSponsorBlocker256px.png"); - document.getElementById("startSponsorButton").setAttribute("title", chrome.i18n.getMessage("sponsorStart")); - - if (document.getElementById("startSponsorImage").style.display != "none" && uploadButtonVisible && !Config.config.hideUploadButtonPlayerControls && !onInvidious) { - document.getElementById("submitButton").style.display = "unset"; - } else if (!uploadButtonVisible || onInvidious) { - //disable submit button - document.getElementById("submitButton").style.display = "none"; - } - } else { - showingStartSponsor = false; - (<HTMLImageElement> document.getElementById("startSponsorImage")).src = chrome.extension.getURL("icons/PlayerStopIconSponsorBlocker256px.png"); - document.getElementById("startSponsorButton").setAttribute("title", chrome.i18n.getMessage("sponsorEND")); - - //disable submit button - document.getElementById("submitButton").style.display = "none"; - } -} - -function toggleStartSponsorButton() { - changeStartSponsorButton(!showingStartSponsor, true); -} - function openInfoMenu() { if (document.getElementById("sponsorBlockPopupContainer") != null) { //it's already added @@ -1254,7 +1249,7 @@ function openInfoMenu() { popupInitialised = false; //hide info button - document.getElementById("infoButton").style.display = "none"; + if (playerButtons.info) playerButtons.info.button.style.display = "none"; sendRequestToCustomServer('GET', chrome.extension.getURL("popup.html"), function(xmlhttp) { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { @@ -1316,20 +1311,28 @@ function openInfoMenu() { function closeInfoMenu() { const popup = document.getElementById("sponsorBlockPopupContainer"); - if (popup != null) { - popup.remove(); + if (popup === null) return; - //show info button if it's not an embed - if (!document.URL.includes("/embed/")) { - document.getElementById("infoButton").style.display = "unset"; - } + popup.remove(); + + // Show info button if it's not an embed + if (!document.URL.includes("/embed/") && playerButtons.info) { + playerButtons.info.button.style.display = "unset"; } } -function clearSponsorTimes() { - //it can't update to this info yet +/** + * The content script currently has no way to notify the info menu of changes. As a workaround we close it, thus making it query the new information when reopened. + * + * This function and all its uses should be removed when this issue is fixed. + * */ +function closeInfoMenuAnd<T>(func: () => T): T { closeInfoMenu(); + return func(); +} + +function clearSponsorTimes() { const currentVideoID = sponsorVideoID; const sponsorTimes = Config.config.segmentTimes.get(currentVideoID); @@ -1347,8 +1350,7 @@ function clearSponsorTimes() { updatePreviewBar(); - //set buttons to be correct - changeStartSponsorButton(true, false); + updateEditButtonsOnPlayer(); } } @@ -1414,18 +1416,6 @@ function dontShowNoticeAgain() { closeAllSkipNotices(); } -function sponsorMessageStarted(callback: (response: MessageResponse) => void) { - video = document.querySelector('video'); - - //send back current time - callback({ - time: video.currentTime - }) - - //update button - toggleStartSponsorButton(); -} - /** * Helper method for the submission notice to clear itself when it closes */ @@ -1436,9 +1426,6 @@ function resetSponsorSubmissionNotice() { function submitSponsorTimes() { if (submissionNotice !== null) return; - //it can't update to this info yet - closeInfoMenu(); - if (sponsorTimesSubmitting !== undefined && sponsorTimesSubmitting.length > 0) { submissionNotice = new SubmissionNotice(skipNoticeContentContainer, sendSubmitMessage); } @@ -1447,10 +1434,10 @@ function submitSponsorTimes() { //send the message to the background js //called after all the checks have been made that it's okay to do so -async function sendSubmitMessage(): Promise<void> { - //add loading animation - (<HTMLImageElement> document.getElementById("submitImage")).src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker256px.png"); - document.getElementById("submitButton").style.animation = "rotate 1s 0s infinite"; +async function sendSubmitMessage() { + // Add loading animation + playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker256px.png"); + playerButtons.submit.button.style.animation = "rotate 1s 0s infinite"; //check if a sponsor exceeds the duration of the video for (let i = 0; i < sponsorTimesSubmitting.length; i++) { @@ -1477,17 +1464,19 @@ async function sendSubmitMessage(): Promise<void> { const response = await utils.asyncRequestToServer("POST", "/api/skipSegments", { videoID: sponsorVideoID, userID: Config.config.userID, - segments: sponsorTimesSubmitting + segments: sponsorTimesSubmitting, }); if (response.status === 200) { - //hide loading message - const submitButton = document.getElementById("submitButton"); + // Handle submission success + const submitButton = playerButtons.submit.button; + + // Make the animation finite submitButton.style.animation = "rotate 1s"; - //finish this animation - //when the animation is over, hide the button - const animationEndListener = function() { - changeStartSponsorButton(true, false); + + // When the animation is over, hide the button + const animationEndListener = () => { + updateEditButtonsOnPlayer(); submitButton.style.animation = "none"; @@ -1496,13 +1485,12 @@ async function sendSubmitMessage(): Promise<void> { submitButton.addEventListener("animationend", animationEndListener); - //clear the sponsor times + // Remove segments from storage since they've already been submitted Config.config.segmentTimes.delete(sponsorVideoID); - //add submissions to current sponsors list - if (sponsorTimes === null) sponsorTimes = []; - - sponsorTimes = sponsorTimes.concat(sponsorTimesSubmitting); + // Add submissions to current sponsors list + // FIXME: segments from sponsorTimesSubmitting do not contain UUIDs .-. + sponsorTimes = (sponsorTimes || []).concat(sponsorTimesSubmitting); // Increase contribution count Config.config.sponsorTimesContributed = Config.config.sponsorTimesContributed + sponsorTimesSubmitting.length; @@ -1512,13 +1500,14 @@ async function sendSubmitMessage(): Promise<void> { Config.config.submissionCountSinceCategories = Config.config.submissionCountSinceCategories + 1; // Empty the submitting times + currentlyTimedSegment = null; sponsorTimesSubmitting = []; updatePreviewBar(); } else { - //show that the upload failed - document.getElementById("submitButton").style.animation = "unset"; - (<HTMLImageElement> document.getElementById("submitImage")).src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker256px.png"); + // Show that the upload failed + playerButtons.submit.button.style.animation = "unset"; + playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker256px.png"); alert(utils.getErrorMessage(response.status, response.responseText)); } @@ -1575,7 +1564,7 @@ function hotkeyListener(e: KeyboardEvent): void { } break; case startSponsorKey: - startSponsorClicked(); + startOrEndTimingNewSegment(); break; case submitKey: submitSponsorTimes(); diff --git a/src/messageTypes.ts b/src/messageTypes.ts index cd1914a8..1c69388a 100644 --- a/src/messageTypes.ts +++ b/src/messageTypes.ts @@ -12,7 +12,6 @@ interface DefaultMessage { message: "update" | "sponsorStart" - | "sponsorDataChanged" | "isInfoFound" | "getVideoID" | "getChannelID" @@ -25,13 +24,7 @@ interface BoolValueMessage { value: boolean; } -interface ChangeStartSponsorButtonMessage { - message: "changeStartSponsorButton"; - showStartSponsor: boolean; - uploadButtonVisible: boolean; -} - -export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | ChangeStartSponsorButtonMessage); +export type Message = BaseMessage & (DefaultMessage | BoolValueMessage); interface IsInfoFoundMessageResponse { found: boolean; @@ -47,7 +40,7 @@ interface GetChannelIDResponse { } interface SponsorStartResponse { - time: number; + creatingSegment: boolean; } interface IsChannelWhitelistedResponse { diff --git a/src/popup.ts b/src/popup.ts index c37d3d90..511b2d3e 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -126,8 +126,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> { PageElements.optionsButton.addEventListener("click", openOptions); PageElements.helpButton.addEventListener("click", openHelp); - //if true, the button now selects the end time - let startTimeChosen = false; + /** If true, the content script is in the process of creating a new segment. */ + let creatingSegment = false; //the start and end time pairs (2d) let sponsorTimes: SponsorTime[] = []; @@ -233,11 +233,13 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> { function onTabs(tabs) { messageHandler.sendMessage(tabs[0].id, {message: 'getVideoID'}, function(result) { - if (result != undefined && result.videoID) { + if (result !== undefined && result.videoID) { currentVideoID = result.videoID; + creatingSegment = result.creatingSegment; + loadTabData(tabs); - } else if (result == undefined && chrome.runtime.lastError) { - // this isn't a YouTube video then, or at least the content script is not loaded + } else if (result === undefined && chrome.runtime.lastError) { + //this isn't a YouTube video then, or at least the content script is not loaded displayNoVideo(); } }); @@ -253,19 +255,11 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> { //load video times for this video const sponsorTimesStorage = Config.config.segmentTimes.get(currentVideoID); if (sponsorTimesStorage != undefined && sponsorTimesStorage.length > 0) { - if (sponsorTimesStorage[sponsorTimesStorage.length - 1] != undefined && sponsorTimesStorage[sponsorTimesStorage.length - 1].segment.length < 2) { - startTimeChosen = true; - PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorEnd"); - } - sponsorTimes = sponsorTimesStorage; - - //show submission section - PageElements.submissionSection.style.display = "unset"; - - showSubmitTimesIfNecessary(); } + updateSegmentEditingUI(); + //check if this video's sponsors are known messageHandler.sendMessage( tabs[0].id, @@ -321,51 +315,44 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> { //the content script will get the message if a YouTube page is open messageHandler.query({ active: true, - currentWindow: true - }, tabs => { + currentWindow: true, + }, (tabs) => { messageHandler.sendMessage( tabs[0].id, {from: 'popup', message: 'sponsorStart'}, - startSponsorCallback - ); - }); - } - - function startSponsorCallback(response) { - const sponsorTimesIndex = sponsorTimes.length - (startTimeChosen ? 1 : 0); + async (response) => { + startSponsorCallback(response); - if (sponsorTimes[sponsorTimesIndex] == undefined) { - sponsorTimes[sponsorTimesIndex] = { - segment: [], - category: Config.config.defaultCategory, - UUID: null - }; - } + // Perform a second update after the config changes take effect as a workaround for a race condition + const removeListener = (listener: typeof lateUpdate) => { + const index = Config.configListeners.indexOf(listener); + if (index !== -1) Config.configListeners.splice(index, 1); + }; - sponsorTimes[sponsorTimesIndex].segment[startTimeChosen ? 1 : 0] = response.time; + const lateUpdate = () => { + startSponsorCallback(response); + removeListener(lateUpdate); + }; - const localStartTimeChosen = startTimeChosen; - Config.config.segmentTimes.set(currentVideoID, sponsorTimes); + Config.configListeners.push(lateUpdate); - //send a message to the client script - if (localStartTimeChosen) { - messageHandler.query({ - active: true, - currentWindow: true - }, tabs => { - messageHandler.sendMessage( - tabs[0].id, - {message: "sponsorDataChanged"} - ); - }); - } + // Remove the listener after 200ms in case the changes were propagated by the time we got the response + setTimeout(() => removeListener(lateUpdate), 200); + }, + ); + }); + } - updateStartTimeChosen(); + function startSponsorCallback(response: {creatingSegment: boolean}) { + creatingSegment = response.creatingSegment; - //show submission section - PageElements.submissionSection.style.display = "unset"; + // Only update the segments after a segment was created + if (!creatingSegment) { + sponsorTimes = Config.config.segmentTimes.get(currentVideoID) || []; + } - showSubmitTimesIfNecessary(); + // Update the UI + updateSegmentEditingUI(); } //display the video times from the array at the top, in a different section @@ -484,34 +471,13 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> { PageElements.showNoticeAgain.style.display = "none"; } - function updateStartTimeChosen() { - //update startTimeChosen letiable - if (!startTimeChosen) { - startTimeChosen = true; - PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorEnd"); - } else { - resetStartTimeChosen(); - } - } - - //set it to false - function resetStartTimeChosen() { - startTimeChosen = false; - PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorStart"); - } - - //hides and shows the submit times button when needed - function showSubmitTimesIfNecessary() { - //check if an end time has been specified for the latest sponsor time - if (sponsorTimes.length > 0 && sponsorTimes[sponsorTimes.length - 1].segment.length > 1) { - //show submit times button - document.getElementById("submitTimesContainer").style.display = "flex"; - } else { - //hide submit times button - document.getElementById("submitTimesContainer").style.display = "none"; - } + /** Updates any UI related to segment editing and submission according to the current state. */ + function updateSegmentEditingUI() { + PageElements.sponsorStart.innerText = chrome.i18n.getMessage(creatingSegment ? "sponsorEnd" : "sponsorStart"); + + PageElements.submissionSection.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none"; } - + //make the options div visible function openOptions() { chrome.runtime.sendMessage({"message": "openConfig"}); diff --git a/src/types.ts b/src/types.ts index dd870f37..c5659d3a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,7 +17,7 @@ export interface ContentContainer { onMobileYouTube: boolean, sponsorSubmissionNotice: SubmissionNotice, resetSponsorSubmissionNotice: () => void, - changeStartSponsorButton: (showStartSponsor: boolean, uploadButtonVisible: boolean) => Promise<boolean>, + updateEditButtonsOnPlayer: () => void, previewTime: (time: number, unpause?: boolean) => void, videoInfo: VideoInfo, getRealCurrentTime: () => number @@ -60,6 +60,10 @@ export interface SponsorTime { hidden?: SponsorHideType; } +export type IncompleteSponsorTime = Omit<SponsorTime, 'segment'> & { + segment: [number]; +}; + export interface PreviewBarOption { color: string, opacity: string @@ -160,4 +164,4 @@ export type VideoID = string; export type StorageChangesObject = { [key: string]: chrome.storage.StorageChange }; -export type UnEncodedSegmentTimes = [string, SponsorTime[]][];
\ No newline at end of file +export type UnEncodedSegmentTimes = [string, SponsorTime[]][]; diff --git a/src/utils.ts b/src/utils.ts index bb42afc5..fd5684f4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -23,8 +23,8 @@ export default class Utils { this.backgroundScriptContainer = backgroundScriptContainer; } - // Function that can be used to wait for a condition before returning - async wait(condition: () => HTMLElement | boolean, timeout = 5000, check = 100): Promise<HTMLElement | boolean> { + /** Function that can be used to wait for a condition before returning. */ + async wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> { return await new Promise((resolve, reject) => { setTimeout(() => reject("TIMEOUT"), timeout); |