Skip to content

Commit

Permalink
Update the button tooltip during the shuffle (#312)
Browse files Browse the repository at this point in the history
  • Loading branch information
NikkelM authored Jul 1, 2024
1 parent c4ac3ca commit 2afbce7
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 33 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
## v3.1.8-beta

<!--Releasenotes start-->
- The button's tooltip may now show additional information on the shuffle status.
- Fixed a bug where clicking the shuffle button while the shuffle was running would start a second shuffle at the same time.
- Fixed a bug where the shuffle button would sometimes not be added to the page if it was opened directly from a new tab.
- Fixed a bug where the playlist created by the extension would sometimes not be renamed correctly.
- Fixed an animation bug when ignoring shorts and shuffling a channel with many videos from a shorts page.
<!--Releasenotes end-->

## v3.1.7
Expand Down Expand Up @@ -299,4 +302,4 @@

## v0.0.1

- Initial release.
- Initial release.
34 changes: 22 additions & 12 deletions src/content.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Content script that is injected into YouTube pages
import { setDOMTextWithDelay, updateSmallButtonStyleForText, getPageTypeFromURL, RandomYoutubeVideoError, delay } from "./utils.js";
import { configSync, setSyncStorageValue } from "./chromeStorage.js";
import { buildShuffleButton, shuffleButton, shuffleButtonTextElement, tryRenameUntitledList } from "./domManipulation.js";
import { buildShuffleButton, shuffleButton, shuffleButtonTextElement, shuffleButtonTooltipElement, tryRenameUntitledList } from "./domManipulation.js";
import { chooseRandomVideo } from "./shuffleVideo.js";

// ---------- Initialization ----------
Expand All @@ -22,6 +22,9 @@ if (videoShuffleButton || channelShuffleButton || shortShuffleButton) {
window.location.reload(true);
}

// To track if the shuffle is already running and prevent bugs if the user clicks the button multiple times
let isShuffling = false;

// After every navigation event, we need to check if this page needs a 'Shuffle' button
document.addEventListener("yt-navigate-finish", startDOMObserver);

Expand Down Expand Up @@ -136,16 +139,23 @@ function resetShuffleButtonText() {
if (shuffleButtonTextElement) {
if (shuffleButtonTextElement.id.includes("large-shuffle-button")) {
shuffleButtonTextElement.innerText = "\xa0Shuffle";
shuffleButtonTooltipElement.innerText = "Shuffle from this channel";
} else if (shuffleButtonTextElement.innerText !== "autorenew") {
updateSmallButtonStyleForText(shuffleButtonTextElement, false);
shuffleButtonTextElement.innerText = "shuffle";
shuffleButtonTooltipElement.innerText = "Shuffle from channel";
}
}
}

// ---------- Shuffle ----------
// Called when the 'Shuffle' button is clicked
async function shuffleVideos() {
if (isShuffling) {
return;
}

isShuffling = true;
resetShuffleButtonText();

// Shorts pages make a copy of the shuffleButtonTextElement to be able to spin it even if the user scrolls to another short, to keep the animation going
Expand Down Expand Up @@ -175,6 +185,7 @@ async function shuffleVideos() {
// Only use this text if the button is the large shuffle button, the small one only has space for an icon
if (shuffleButtonTextElement.id.includes("large-shuffle-button")) {
shuffleButtonTextElement.innerText = "\xa0Shuffling...";
shuffleButtonTooltipElement.innerText = "The shuffle has started, please wait while the extension gets the video data for this channel...";
setDOMTextWithDelay(shuffleButtonTextElement, "\xa0Still on it...", 5000, () => { return ((shuffleButtonTextElement.innerText === "\xa0Shuffling..." || shuffleButtonTextElement.innerText === "\xa0Fetching: 100%") && !hasBeenShuffled); });
if (configSync.shuffleIgnoreShortsOption != "1") {
setDOMTextWithDelay(shuffleButtonTextElement, "\xa0Sorting shorts...", 10000, () => { return ((shuffleButtonTextElement.innerText === "\xa0Still on it..." || shuffleButtonTextElement.innerText === "\xa0Fetching: 100%") && !hasBeenShuffled); });
Expand All @@ -188,6 +199,7 @@ async function shuffleVideos() {
setDOMTextWithDelay(shuffleButtonTextElement, "\xa0Still shuffling...", 20000, () => { return ((shuffleButtonTextElement.innerText === "\xa0Still on it..." || shuffleButtonTextElement.innerText === "\xa0Fetching: 100%") && !hasBeenShuffled); });
}
} else {
shuffleButtonTooltipElement.innerText = "Shuffling...";
let iterationsWaited = 0;

let checkInterval = setInterval(async () => {
Expand All @@ -196,7 +208,7 @@ async function shuffleVideos() {
await delay(400);

// If we have finished the shuffle between the check and the delay, we don't want to change the text
if (hasBeenShuffled) {
if (hasBeenShuffled || (shuffleButtonTextElementCopy.innerText != "100%" && shuffleButtonTextElementCopy.innerText != "shuffle")) {
return;
}

Expand All @@ -205,33 +217,31 @@ async function shuffleVideos() {

let rotation = 0;
let rotateInterval = setInterval(() => {
if (hasBeenShuffled) {
if (hasBeenShuffled || shuffleButtonTextElementCopy.innerText != "autorenew") {
clearInterval(rotateInterval);
return;
}
shuffleButtonTextElementCopy.style.transform = `rotate(${rotation}deg)`;
rotation = (rotation + 5) % 360;
}, 25);
} else if (hasBeenShuffled) {
} else if (hasBeenShuffled && shuffleButtonTextElementCopy.innerText != "autorenew") {
clearInterval(checkInterval);
}
}, 150);
}

await chooseRandomVideo(channelId, false, shuffleButtonTextElement);
await chooseRandomVideo(channelId, false, shuffleButtonTextElement, shuffleButtonTooltipElement);

isShuffling = false;
hasBeenShuffled = true;

// Reset the button text in case we opened the video in a new tab
if (shuffleButtonTextElement.id.includes("large-shuffle-button")) {
shuffleButtonTextElement.innerText = "\xa0Shuffle";
} else {
updateSmallButtonStyleForText(shuffleButtonTextElementCopy, false);
shuffleButtonTextElementCopy.innerText = "shuffle";
}
resetShuffleButtonText();
} catch (error) {
console.error(error);

isShuffling = false;
hasBeenShuffled = true;

if (shuffleButton.id.includes("small-shuffle-button")) {
updateSmallButtonStyleForText(shuffleButtonTextElementCopy, true);
}
Expand Down
4 changes: 3 additions & 1 deletion src/domManipulation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// ----- Public -----
export let shuffleButton;
export let shuffleButtonTextElement;
export let shuffleButtonTooltipElement;

export function buildShuffleButton(pageType, channelId, eventVersion, clickHandler) {
let buttonDivID;
Expand Down Expand Up @@ -217,7 +218,7 @@ function finalizeButton(pageType, channelId, clickHandler, isLargeButton, button
if (isLargeButton) {
buttonTooltip.innerText = "Shuffle from this channel";
} else {
buttonTooltip.innerText = "Shuffle channel";
buttonTooltip.innerText = "Shuffle from channel";
}

// Remove the original button tooltip, it does not have all required attributes
Expand Down Expand Up @@ -261,4 +262,5 @@ function finalizeButton(pageType, channelId, clickHandler, isLargeButton, button
} else {
shuffleButtonTextElement = shuffleButton.children[0].children[0].children[0].children[0].children[0];
}
shuffleButtonTooltipElement = shuffleButton.children[0].children[1].children[0];
}
47 changes: 28 additions & 19 deletions src/shuffleVideo.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let shuffleStartTime = null;

// --------------- Public ---------------
// Chooses a random video uploaded on the current YouTube channel
export async function chooseRandomVideo(channelId, firedFromPopup, progressTextElement) {
export async function chooseRandomVideo(channelId, firedFromPopup, progressTextElement, shuffleButtonTooltipElement = null) {
/* c8 ignore start */
try {
// The service worker will get stopped after 30 seconds
Expand Down Expand Up @@ -78,14 +78,14 @@ export async function chooseRandomVideo(channelId, firedFromPopup, progressTextE
} else {
console.log("Fetching the uploads playlist for this channel from the YouTube API...");
}
({ playlistInfo, userQuotaRemainingToday } = await getPlaylistFromAPI(uploadsPlaylistId, null, userQuotaRemainingToday, progressTextElement));
({ playlistInfo, userQuotaRemainingToday } = await getPlaylistFromAPI(uploadsPlaylistId, null, userQuotaRemainingToday, progressTextElement, shuffleButtonTooltipElement));

shouldUpdateDatabase = true;
} else if (databaseSharing && (playlistInfo["lastUpdatedDBAt"] ?? new Date(0).toISOString()) < addHours(new Date(), -48).toISOString()) {
// If the playlist exists in the database but is outdated, update it from the API.
console.log("Uploads playlist for this channel may be outdated in the database. Updating from the YouTube API...");

({ playlistInfo, userQuotaRemainingToday } = await updatePlaylistFromAPI(playlistInfo, uploadsPlaylistId, null, userQuotaRemainingToday, progressTextElement));
({ playlistInfo, userQuotaRemainingToday } = await updatePlaylistFromAPI(playlistInfo, uploadsPlaylistId, null, userQuotaRemainingToday, progressTextElement, shuffleButtonTooltipElement));

shouldUpdateDatabase = true;
}
Expand All @@ -104,13 +104,13 @@ export async function chooseRandomVideo(channelId, firedFromPopup, progressTextE
// With the current functionality and db rules, this shouldn't happen, except if the user has opted out of database sharing.
if (isEmpty(playlistInfo)) {
console.log(`${databaseSharing ? "Uploads playlist for this channel does not exist in the database. " : "Fetching it from the YouTube API..."}`);
({ playlistInfo, userQuotaRemainingToday } = await getPlaylistFromAPI(uploadsPlaylistId, null, userQuotaRemainingToday, progressTextElement));
({ playlistInfo, userQuotaRemainingToday } = await getPlaylistFromAPI(uploadsPlaylistId, null, userQuotaRemainingToday, progressTextElement, shuffleButtonTooltipElement));

shouldUpdateDatabase = true;
// If the playlist exists in the database but is outdated there as well, update it from the API.
} else if ((playlistInfo["lastUpdatedDBAt"] ?? new Date(0).toISOString()) < addHours(new Date(), -48).toISOString()) {
console.log("Uploads playlist for this channel may be outdated in the database. Updating from the YouTube API...");
({ playlistInfo, userQuotaRemainingToday } = await updatePlaylistFromAPI(playlistInfo, uploadsPlaylistId, null, userQuotaRemainingToday, progressTextElement));
({ playlistInfo, userQuotaRemainingToday } = await updatePlaylistFromAPI(playlistInfo, uploadsPlaylistId, null, userQuotaRemainingToday, progressTextElement, shuffleButtonTooltipElement));

shouldUpdateDatabase = true;
}
Expand All @@ -128,7 +128,7 @@ export async function chooseRandomVideo(channelId, firedFromPopup, progressTextE

let chosenVideos;
var encounteredDeletedVideos;
({ chosenVideos, playlistInfo, shouldUpdateDatabase, encounteredDeletedVideos } = await chooseRandomVideosFromPlaylist(playlistInfo, channelId, shouldUpdateDatabase, progressTextElement));
({ chosenVideos, playlistInfo, shouldUpdateDatabase, encounteredDeletedVideos } = await chooseRandomVideosFromPlaylist(playlistInfo, channelId, shouldUpdateDatabase, progressTextElement, shuffleButtonTooltipElement));

// Save the playlist to the database and locally
playlistInfo = await handlePlaylistDatabaseUpload(playlistInfo, uploadsPlaylistId, shouldUpdateDatabase, databaseSharing, encounteredDeletedVideos);
Expand Down Expand Up @@ -291,7 +291,7 @@ async function uploadPlaylistToDatabase(playlistInfo, videosToDatabase, uploadsP
}

// ---------- YouTube API ----------
async function getPlaylistFromAPI(playlistId, useAPIKeyAtIndex, userQuotaRemainingToday, progressTextElement, disregardUserQuota = false) {
async function getPlaylistFromAPI(playlistId, useAPIKeyAtIndex, userQuotaRemainingToday, progressTextElement, shuffleButtonTooltipElement, disregardUserQuota = false) {
// Get an API key
let { APIKey, isCustomKey, keyIndex } = await getAPIKey(useAPIKeyAtIndex);
// We need to keep track of the original key's index, so we know when we have tried all keys
Expand Down Expand Up @@ -348,7 +348,7 @@ async function getPlaylistFromAPI(playlistId, useAPIKeyAtIndex, userQuotaRemaini
// If there are less than 50 videos, we don't need to show a progress percentage
if (totalResults > 50) {
const percentage = Math.round(resultsFetchedCount / totalResults * 100);
updateProgressTextElement(progressTextElement, `\xa0Fetching: ${percentage}%`, `${percentage}%`);
updateProgressTextElement(progressTextElement, `\xa0Fetching: ${percentage}%`, `${percentage}%`, shuffleButtonTooltipElement, "Fetching videos may take longer if the channel has a lot of uploads or your network speed is slow. Please wait...");
}

// For each video, add an entry in the form of videoId: uploadTime
Expand Down Expand Up @@ -380,7 +380,7 @@ async function getPlaylistFromAPI(playlistId, useAPIKeyAtIndex, userQuotaRemaini
}

// Get snippets from the API as long as new videos are being found
async function updatePlaylistFromAPI(playlistInfo, playlistId, useAPIKeyAtIndex, userQuotaRemainingToday, progressTextElement) {
async function updatePlaylistFromAPI(playlistInfo, playlistId, useAPIKeyAtIndex, userQuotaRemainingToday, progressTextElement, shuffleButtonTooltipElement) {
// Get an API key
let { APIKey, isCustomKey, keyIndex } = await getAPIKey(useAPIKeyAtIndex);
// We need to keep track of the original key's index, so we know when we have tried all keys
Expand Down Expand Up @@ -429,7 +429,7 @@ async function updatePlaylistFromAPI(playlistInfo, playlistId, useAPIKeyAtIndex,
// If there are less than 50 new videos, we don't need to show a progress percentage
if (totalExpectedNewResults > 50) {
const percentage = Math.min(Math.round(resultsFetchedCount / totalExpectedNewResults * 100), 100);
updateProgressTextElement(progressTextElement, `\xa0Fetching: ${percentage}%`, `${percentage}%`);
updateProgressTextElement(progressTextElement, `\xa0Fetching: ${percentage}%`, `${percentage}%`, shuffleButtonTooltipElement, "Fetching videos may take longer if the channel has a lot of uploads or your network speed is slow. Please wait...");
}

// Update the "last video published at" date (only for the most recent video)
Expand All @@ -443,7 +443,7 @@ async function updatePlaylistFromAPI(playlistInfo, playlistId, useAPIKeyAtIndex,
// Make sure that we are not missing any videos in the database
if (totalNumVideosOnChannel > numLocallyKnownVideos) {
console.log(`There are less videos saved in the database than are uploaded on the channel (${numLocallyKnownVideos}/${totalNumVideosOnChannel}), so some videos are missing. Refetching all videos...`);
return await getPlaylistFromAPI(playlistId, keyIndex, userQuotaRemainingToday, progressTextElement, true);
return await getPlaylistFromAPI(playlistId, keyIndex, userQuotaRemainingToday, progressTextElement, shuffleButtonTooltipElement, true);
}

return { playlistInfo, userQuotaRemainingToday };
Expand Down Expand Up @@ -490,7 +490,7 @@ async function updatePlaylistFromAPI(playlistInfo, playlistId, useAPIKeyAtIndex,
const numVideosInDatabase = numLocallyKnownVideos + getLength(playlistInfo["newVideos"]);
if (totalNumVideosOnChannel > numVideosInDatabase) {
console.log(`There are less videos saved in the database than are uploaded on the channel (${numVideosInDatabase}/${totalNumVideosOnChannel}), so some videos are missing. Refetching all videos...`);
return await getPlaylistFromAPI(playlistId, keyIndex, userQuotaRemainingToday, progressTextElement, true);
return await getPlaylistFromAPI(playlistId, keyIndex, userQuotaRemainingToday, progressTextElement, shuffleButtonTooltipElement, true);
}

return { playlistInfo, userQuotaRemainingToday };
Expand Down Expand Up @@ -706,7 +706,7 @@ async function getAPIKey(useAPIKeyAtIndex = null) {
return { APIKey, isCustomKey, keyIndex };
}

async function chooseRandomVideosFromPlaylist(playlistInfo, channelId, shouldUpdateDatabase, progressTextElement) {
async function chooseRandomVideosFromPlaylist(playlistInfo, channelId, shouldUpdateDatabase, progressTextElement, shuffleButtonTooltipElement) {
let activeShuffleFilterOption = configSync.channelSettings[channelId]?.activeOption ?? "allVideosOption";
let activeOptionValue;

Expand Down Expand Up @@ -903,7 +903,7 @@ async function chooseRandomVideosFromPlaylist(playlistInfo, channelId, shouldUpd
if (new Date() - shuffleStartTime > 1000) {
// We display either the percentage of videos processed or the percentage of videos chosen (vs. needed), whichever is higher
const percentage = Math.max(Math.round(chosenVideos.length / numVideosToChoose * 100), Math.round(numVideosProcessed / initialTotalNumVideos * 100));
updateProgressTextElement(progressTextElement, `\xa0Sorting: ${percentage}%`, `${percentage}%`);
updateProgressTextElement(progressTextElement, `\xa0Sorting: ${percentage}%`, `${percentage}%`, shuffleButtonTooltipElement, "The extension is currently separating shorts and videos. Please wait...", "Sorting shorts...");
}
} else {
// We are not ignoring shorts and the video exists
Expand Down Expand Up @@ -1145,18 +1145,27 @@ function validatePlaylistInfo(playlistInfo) {
}
/* c8 ignore stop */

function updateProgressTextElement(progressTextElement, largeButtonText, smallButtonText) {
function updateProgressTextElement(progressTextElement, largeButtonText, smallButtonText, shuffleButtonTooltipElement = null, tooltipText = null, smallButtonTooltipText = null) {
if (progressTextElement.id.includes("large-shuffle-button") || progressTextElement.id == "fetchPercentageNoticeShufflingPage") {
progressTextElement.innerText = largeButtonText;
} else {
// Make it the icon style if an icon is set, otherwise the text style
if (!["shuffle", "close"].includes(smallButtonText)) {
updateSmallButtonStyleForText(progressTextElement, true);
} else {
// Make it the text style if no icon is set, otherwise the icon style
if (["shuffle", "close"].includes(smallButtonText)) {
updateSmallButtonStyleForText(progressTextElement, false);
} else {
updateSmallButtonStyleForText(progressTextElement, true);
}
progressTextElement.innerText = smallButtonText;
}

// Update the tooltip if requested
if (shuffleButtonTooltipElement) {
if (progressTextElement.id.includes("large-shuffle-button")) {
shuffleButtonTooltipElement.innerText = tooltipText;
} else if (smallButtonTooltipText) {
shuffleButtonTooltipElement.innerText = smallButtonTooltipText;
}
}
}

// ---------- Local storage ----------
Expand Down

0 comments on commit 2afbce7

Please sign in to comment.