Clean up segment creation code

Noteworthy changes:
- Adds ability to cancel creating a segment
- Makes segment creation fully the responsibility of the content script, with the popup script buttons simply doing RPC
- Adds types to the Utils.wait function
- Fixes segment timestamps backwards if user marks segment end earlier than the start
- Makes the info menu (in-page popup) workaround clearer and easier to remove in the future.
This commit is contained in:
opl- 2020-11-15 02:36:16 +01:00
parent 318d00a9c4
commit 3879cc6de3
10 changed files with 223 additions and 264 deletions

View file

@ -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."
},

View file

@ -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ę."
},

View file

@ -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>

View file

@ -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();

View file

@ -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[],

View file

@ -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);
closeInfoMenuAnd(() => 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;
@ -170,13 +174,9 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
channelWhitelisted = request.value;
sponsorsLookup(sponsorVideoID);
break;
case "changeStartSponsorButton":
changeStartSponsorButton(request.showStartSponsor, request.uploadButtonVisible);
break;
case "submitTimes":
submitSponsorTimes();
closeInfoMenuAnd(() => submitSponsorTimes());
break;
}
}
@ -294,33 +294,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");
@ -1024,8 +1012,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");
@ -1049,9 +1038,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 {
@ -1061,8 +1056,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);
@ -1075,53 +1070,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", startOrEndTimingNewSegment, "PlayerStartIconSponsorBlocker256px.png");
createButton("cancelSponsor", "sponsorCancel", 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 || onInvidious) return;
const buttonsEnabled = !Config.config.hideVideoPlayerControls;
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";
}
/**
@ -1142,30 +1159,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) {
@ -1194,38 +1221,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
@ -1235,7 +1230,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) {
@ -1297,20 +1292,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);
@ -1328,8 +1331,7 @@ function clearSponsorTimes() {
updatePreviewBar();
//set buttons to be correct
changeStartSponsorButton(true, false);
updateEditButtonsOnPlayer();
}
}
@ -1395,18 +1397,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
*/
@ -1417,9 +1407,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);
}
@ -1428,10 +1415,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++) {
@ -1458,17 +1445,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";
@ -1477,13 +1466,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;
@ -1493,13 +1481,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));
}
@ -1554,7 +1543,7 @@ function hotkeyListener(e: KeyboardEvent): void {
}
break;
case startSponsorKey:
startSponsorClicked();
startOrEndTimingNewSegment();
break;
case submitKey:
submitSponsorTimes();

View file

@ -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 {

View file

@ -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
async (response) => {
startSponsorCallback(response);
// 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);
};
const lateUpdate = () => {
startSponsorCallback(response);
removeListener(lateUpdate);
};
Config.configListeners.push(lateUpdate);
// Remove the listener after 200ms in case the changes were propagated by the time we got the response
setTimeout(() => removeListener(lateUpdate), 200);
},
);
});
}
function startSponsorCallback(response) {
const sponsorTimesIndex = sponsorTimes.length - (startTimeChosen ? 1 : 0);
function startSponsorCallback(response: {creatingSegment: boolean}) {
creatingSegment = response.creatingSegment;
if (sponsorTimes[sponsorTimesIndex] == undefined) {
sponsorTimes[sponsorTimesIndex] = {
segment: [],
category: Config.config.defaultCategory,
UUID: null
};
// Only update the segments after a segment was created
if (!creatingSegment) {
sponsorTimes = Config.config.segmentTimes.get(currentVideoID) || [];
}
sponsorTimes[sponsorTimesIndex].segment[startTimeChosen ? 1 : 0] = response.time;
const localStartTimeChosen = startTimeChosen;
Config.config.segmentTimes.set(currentVideoID, sponsorTimes);
//send a message to the client script
if (localStartTimeChosen) {
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{message: "sponsorDataChanged"}
);
});
}
updateStartTimeChosen();
//show submission section
PageElements.submissionSection.style.display = "unset";
showSubmitTimesIfNecessary();
// Update the UI
updateSegmentEditingUI();
}
//display the video times from the array at the top, in a different section
@ -475,34 +462,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();
}
/** 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";
}
//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";
}
}
//make the options div visible
function openOptions() {
chrome.runtime.sendMessage({"message": "openConfig"});

View file

@ -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[]][];
export type UnEncodedSegmentTimes = [string, SponsorTime[]][];

View file

@ -23,8 +23,8 @@ 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);