diff --git a/CHANGELOG.md b/CHANGELOG.md index ab5b25b2..e3ec4bd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,17 @@ # Changelog -## v3.1.2 +## v3.1.3-beta +- Cleaned up the popup and moved some settings to a separate menu. + + +## v3.1.2 + - Fixed the shuffle button not showing up if the user is on a different version of the YouTube UI. - Firefox: If not already granted, the extension will now ask for permissions whenever the popup is opened. - Fixed a bug that would cause the changelog page to show changes from an incorrect version in some cases. - Updated versioning scheme for a better distinction between stable and beta versions. - ## v3.1.1 diff --git a/src/background.js b/src/background.js index ad81aa77..da2af9fd 100644 --- a/src/background.js +++ b/src/background.js @@ -1,9 +1,12 @@ // Background service worker for the extension, which is run ("started") on extension initialization // Handles communication between the extension and the content script as well as Firebase interactions import { configSync, setSyncStorageValue } from "./chromeStorage.js"; +// We need to import utils.js to get the console rerouting function +import { } from "./utils.js"; // ---------- Initialization/Chrome event listeners ---------- const isFirefox = typeof browser !== "undefined"; +await initExtension(); // Check whether a new version was installed async function initExtension() { @@ -25,7 +28,6 @@ async function initExtension() { checkLocalStorageCapacity(); } -await initExtension(); // Make sure we are not using too much local storage async function checkLocalStorageCapacity() { diff --git a/src/html/htmlUtils.js b/src/html/htmlUtils.js index 6bae5778..d03d6253 100644 --- a/src/html/htmlUtils.js +++ b/src/html/htmlUtils.js @@ -1,7 +1,6 @@ // Shared utility functions for the various HTML pages' logic import { shufflingHints } from "../config.js"; -// ---------- Public ---------- // ----- Shuffling Hints ----- export async function buildShufflingHints(domElements) { let currentHint = await displayShufflingHint(domElements.shufflingHintP); @@ -23,7 +22,47 @@ async function displayShufflingHint(displayElement, currentHintIndex = null) { return randomHintIndex; } -// ----- Other utility functions ----- +// ----- Animations ----- +export function animateSlideOut(targetElement) { + // Sliding out + if (!targetElement.classList.contains("active")) { + targetElement.classList.add("active"); + targetElement.style.height = "auto"; + + const targetHeight = targetElement.clientHeight; + targetElement.style.height = "0px"; + + setTimeout(function () { + targetElement.style.height = targetHeight + 'px'; + adjustParentContainerHeight(targetElement, targetHeight); + }, 0); + } else { + // Sliding in + const oldHeight = targetElement.clientHeight; + targetElement.style.height = "0px"; + + adjustParentContainerHeight(targetElement, -oldHeight); + + targetElement.addEventListener( + "transitionend", + function () { + targetElement.classList.remove("active"); + }, { + once: true + }); + } +} + +function adjustParentContainerHeight(childElement, heightChange) { + const parentElement = childElement.parentElement; + + if (parentElement && parentElement.classList.contains("active")) { + const currentParentHeight = parseInt(parentElement.style.height) || 0; + parentElement.style.height = (currentParentHeight + heightChange) + 'px'; + } +} + +// ----- Tab interaction ----- export async function tryFocusingTab(tabUrl) { let mustOpenTab = true; let tabs = await chrome.tabs.query({}); diff --git a/src/html/popup/popup.js b/src/html/popup/popup.js index 6862f392..8a52fb31 100644 --- a/src/html/popup/popup.js +++ b/src/html/popup/popup.js @@ -2,7 +2,7 @@ import { delay } from "../../utils.js"; import { configSync, setSyncStorageValue, removeSyncStorageValue } from "../../chromeStorage.js"; import { manageDependents, manageDbOptOutOption, validateApiKey, setChannelSetting, removeChannelSetting, updateFYIDiv } from "./popupUtils.js"; -import { tryFocusingTab } from "../htmlUtils.js"; +import { tryFocusingTab, animateSlideOut } from "../htmlUtils.js"; // ----- Setup ----- const isPopup = chrome.extension.getViews({ type: "popup" }).length > 0; @@ -64,18 +64,6 @@ function getPopupDomElements() { firefoxPermissionsNeededButton: document.getElementById("firefoxPermissionsNeededButton"), // GLOBAL SETTINGS - // Custom API key: Option toggle - useCustomApiKeyOptionToggle: document.getElementById("useCustomApiKeyOptionToggle"), - // Custom API key: Input - customApiKeyInputDiv: document.getElementById("customApiKeyInputDiv"), - customApiKeyInputField: customApiKeyInputDiv.children.namedItem("customApiKeyInputField"), - customApiKeySubmitButton: customApiKeyInputDiv.children.namedItem("customApiKeySubmitButton"), - customApiKeyInputInfoDiv: customApiKeyInputDiv.children.namedItem("customApiKeyInputInfoDiv"), - customApiKeyInputInfoText: customApiKeyInputInfoDiv.children.namedItem("customApiKeyInputInfoText"), - customApiKeyHowToGetDiv: document.getElementById("customApiKeyHowToGetDiv"), - - // Database sharing: Option toggle - dbSharingOptionToggle: document.getElementById("dbSharingOptionToggle"), // Shuffling: Open in new tab option toggle shuffleOpenInNewTabOptionToggle: document.getElementById("shuffleOpenInNewTabOptionToggle"), // Shuffling: Reuse tab option toggle @@ -111,6 +99,24 @@ function getPopupDomElements() { // Popup shuffle button popupShuffleButton: document.getElementById("popupShuffleButton"), + // ADVANCED SETTINGS + // Advanced settings div + advancedSettingsDiv: document.getElementById("advancedSettingsDiv"), + // Advanced settings expand button + advancedSettingsExpandButton: document.getElementById("advancedSettingsExpandButton"), + + // Custom API key: Option toggle + useCustomApiKeyOptionToggle: document.getElementById("useCustomApiKeyOptionToggle"), + // Custom API key: Input + customApiKeyInputDiv: document.getElementById("customApiKeyInputDiv"), + customApiKeyInputField: customApiKeyInputDiv.children.namedItem("customApiKeyInputField"), + customApiKeySubmitButton: customApiKeyInputDiv.children.namedItem("customApiKeySubmitButton"), + customApiKeyInputInfoDiv: customApiKeyInputDiv.children.namedItem("customApiKeyInputInfoDiv"), + customApiKeyInputInfoText: customApiKeyInputInfoDiv.children.namedItem("customApiKeyInputInfoText"), + + // Database sharing: Option toggle + dbSharingOptionToggle: document.getElementById("dbSharingOptionToggle"), + // FYI - FOR YOUR INFORMATION // FYI div forYourInformationDiv: document.getElementById("forYourInformationDiv"), @@ -151,10 +157,6 @@ async function setPopupDomElementValuesFromConfig(domElements) { // Set the value of the custom API key input field to the value in sync storage domElements.customApiKeyInputField.value = configSync.customYoutubeApiKey ? configSync.customYoutubeApiKey : ""; - if (configSync.useCustomApiKeyOption && configSync.customYoutubeApiKey) { - domElements.customApiKeyHowToGetDiv.classList.add("hidden"); - } - // ----- Shuffling: Open in new tab option toggle ----- domElements.shuffleOpenInNewTabOptionToggle.checked = configSync.shuffleOpenInNewTabOption; @@ -198,45 +200,6 @@ async function setPopupDomElementValuesFromConfig(domElements) { // Set event listeners for DOM elements async function setPopupDomElemenEventListeners(domElements) { - // Custom API key: Option toggle - domElements.useCustomApiKeyOptionToggle.addEventListener("change", async function () { - await setSyncStorageValue("useCustomApiKeyOption", this.checked); - - manageDependents(domElements, domElements.useCustomApiKeyOptionToggle, this.checked); - }); - - // Database sharing: Option toggle - domElements.dbSharingOptionToggle.addEventListener("change", async function () { - await setSyncStorageValue("databaseSharingEnabledOption", this.checked); - - manageDependents(domElements, domElements.dbSharingOptionToggle, this.checked); - }); - - // Custom API key: Input - domElements.customApiKeySubmitButton.addEventListener("click", async function () { - // Make sure the passed API key is valid - const newAPIKey = domElements.customApiKeyInputField.value; - const oldApiKey = configSync.customYoutubeApiKey; - - if (newAPIKey.length > 0 && await validateApiKey(newAPIKey, domElements)) { - await setSyncStorageValue("customYoutubeApiKey", newAPIKey); - } else { - await removeSyncStorageValue("customYoutubeApiKey"); - await setSyncStorageValue("databaseSharingEnabledOption", true); - domElements.customApiKeyInputField.value = ""; - } - - // If the user removed the API key, show a message in the info div - if (oldApiKey != undefined && newAPIKey.length === 0) { - domElements.customApiKeyInputInfoText.innerText = "Custom API key was successfully removed."; - domElements.customApiKeyInputInfoDiv.classList.remove("hidden"); - } - - manageDbOptOutOption(domElements); - - manageDependents(domElements, domElements.customApiKeySubmitButton, null); - }); - // Shuffling: Open in new tab option toggle domElements.shuffleOpenInNewTabOptionToggle.addEventListener("change", async function () { await setSyncStorageValue("shuffleOpenInNewTabOption", this.checked); @@ -420,6 +383,56 @@ async function setPopupDomElemenEventListeners(domElements) { window.close(); }); + // Advanced settings expand button + domElements.advancedSettingsExpandButton.addEventListener("click", function () { + // Update the text before the animation, as the classlist will change after some time only + domElements.advancedSettingsExpandButton.innerText = domElements.advancedSettingsDiv.classList.contains("active") ? "Show Advanced Settings" : "Hide Advanced Settings"; + domElements.advancedSettingsExpandButton.style.fontWeight = "bold"; + + animateSlideOut(domElements.advancedSettingsDiv); + + manageDependents(domElements, domElements.advancedSettingsExpandButton, domElements.advancedSettingsDiv.classList.contains("active")); + }); + + // Custom API key: Option toggle + domElements.useCustomApiKeyOptionToggle.addEventListener("change", async function () { + await setSyncStorageValue("useCustomApiKeyOption", this.checked); + + manageDependents(domElements, domElements.useCustomApiKeyOptionToggle, this.checked); + }); + + // Database sharing: Option toggle + domElements.dbSharingOptionToggle.addEventListener("change", async function () { + await setSyncStorageValue("databaseSharingEnabledOption", this.checked); + + manageDependents(domElements, domElements.dbSharingOptionToggle, this.checked); + }); + + // Custom API key: Input + domElements.customApiKeySubmitButton.addEventListener("click", async function () { + // Make sure the passed API key is valid + const newAPIKey = domElements.customApiKeyInputField.value; + const oldApiKey = configSync.customYoutubeApiKey; + + if (newAPIKey.length > 0 && await validateApiKey(newAPIKey, domElements)) { + await setSyncStorageValue("customYoutubeApiKey", newAPIKey); + } else { + await removeSyncStorageValue("customYoutubeApiKey"); + await setSyncStorageValue("databaseSharingEnabledOption", true); + domElements.customApiKeyInputField.value = ""; + } + + // If the user removed the API key, show a message in the info div + if (oldApiKey != undefined && newAPIKey.length === 0) { + domElements.customApiKeyInputInfoText.innerText = "Custom API key was successfully removed."; + domElements.customApiKeyInputInfoDiv.classList.remove("hidden"); + } + + manageDbOptOutOption(domElements); + + manageDependents(domElements, domElements.customApiKeySubmitButton, null); + }); + // View changelog button domElements.viewChangelogButton.addEventListener("click", async function () { await setSyncStorageValue("lastViewedChangelogVersion", chrome.runtime.getManifest().version); diff --git a/src/html/popup/popupUtils.js b/src/html/popup/popupUtils.js index ab390bb7..970a8e6d 100644 --- a/src/html/popup/popupUtils.js +++ b/src/html/popup/popupUtils.js @@ -1,6 +1,7 @@ // Helper functions for the popup import { getLength } from "../../utils.js"; import { configSync, setSyncStorageValue, getUserQuotaRemainingToday } from "../../chromeStorage.js"; +import { animateSlideOut } from "../htmlUtils.js"; // ---------- Dependency management ---------- // ----- Public ----- @@ -10,20 +11,9 @@ export async function manageDependents(domElements, parent, value) { case domElements.useCustomApiKeyOptionToggle: // For this option, the value is the same as the checked state if (value) { - // Show input field for custom API key - domElements.customApiKeyInputDiv.classList.remove("hidden"); - domElements.customApiKeyInputDiv.classList.remove("hiddenTransition"); - domElements.customApiKeyInputDiv.classList.add("visibleTransition"); // Set the value of the custom API key input field to the value in sync storage domElements.customApiKeyInputField.value = configSync.customYoutubeApiKey ? configSync.customYoutubeApiKey : ""; - // Show the guide on how to get a custom API key if the user has not already provided one - if (!configSync.customYoutubeApiKey) { - domElements.customApiKeyHowToGetDiv.classList.remove("hidden"); - } else { - domElements.customApiKeyHowToGetDiv.classList.add("hidden"); - } - manageDbOptOutOption(domElements); } else { // The user must share data with the database @@ -32,22 +22,12 @@ export async function manageDependents(domElements, parent, value) { await setSyncStorageValue("databaseSharingEnabledOption", true); manageDbOptOutOption(domElements); - - // Hide input field for custom API key - domElements.customApiKeyInputDiv.classList.remove("visibleTransition"); - domElements.customApiKeyInputDiv.classList.add("hiddenTransition"); } + animateSlideOut(domElements.customApiKeyInputDiv); updateFYIDiv(domElements); break; case domElements.customApiKeySubmitButton: - // Show the guide on how to get a custom API key if the user has not already provided one - if (!configSync.customYoutubeApiKey) { - domElements.customApiKeyHowToGetDiv.classList.remove("hidden"); - } else { - domElements.customApiKeyHowToGetDiv.classList.add("hidden"); - } - // This is called after validation of a provided API key // Depending on whether or not it is valid, we need to update the FYI div updateFYIDiv(domElements); @@ -75,6 +55,15 @@ export async function manageDependents(domElements, parent, value) { } break; + case domElements.advancedSettingsExpandButton: + // If true, it means the container is sliding out, so we need to slide out all dependent containers as well + if (value) { + if (configSync.useCustomApiKeyOption) { + animateSlideOut(domElements.customApiKeyInputDiv); + } + } + break; + default: console.log(`No dependents to manage for element: ${parent.id}`); break; @@ -140,6 +129,12 @@ export async function updateFYIDiv(domElements) { // ----- Public ----- // Validates a YouTube API key by sending a short request export async function validateApiKey(customAPIKey, domElements) { + // Make sure the service worker is running + try { + await chrome.runtime.sendMessage({ command: "connectionTest" }); + } catch (error) { + console.log("The background worker was stopped and had to be restarted."); + } // APIKey is actually an array of objects here, despite the naming let { APIKey } = await chrome.runtime.sendMessage({ command: "getDefaultAPIKeys" }); @@ -151,9 +146,9 @@ export async function validateApiKey(customAPIKey, domElements) { // Users should not add default API keys if (defaultAPIKeys.includes(customAPIKey)) { - domElements.customApiKeyInputInfoText.innerText = "This API key is used by the extension. Please enter your own."; + domElements.customApiKeyInputInfoText.innerText = "Error: API key not valid. Please pass a valid API key:"; domElements.customApiKeyInputInfoDiv.classList.remove("hidden"); - + domElements.customApiKeyInputField.classList.add('invalid-input'); setTimeout(() => { domElements.customApiKeyInputField.classList.remove('invalid-input'); @@ -166,7 +161,7 @@ export async function validateApiKey(customAPIKey, domElements) { .then((response) => response.json()); if (apiResponse["error"]) { - domElements.customApiKeyInputInfoText.innerText = "Error: " + apiResponse["error"]["message"]; + domElements.customApiKeyInputInfoText.innerText = "Error: API key not valid. Please pass a valid API key:"; domElements.customApiKeyInputInfoDiv.classList.remove("hidden"); domElements.customApiKeyInputField.classList.add('invalid-input'); diff --git a/static/css/popup.css b/static/css/popup.css index 47aa7b6b..0009df25 100644 --- a/static/css/popup.css +++ b/static/css/popup.css @@ -39,34 +39,17 @@ display: none !important; } -@keyframes slideDown { - 0% { - max-height: 0; - } - 100% { - max-height: 120px; - } -} - -@keyframes slideUp { - 0% { - max-height: 120px; - } - 100% { - max-height: 0; - } +.slideOutContainer { + transition: height 0.9s ease-in-out; + overflow: hidden; } -.hiddenTransition { - animation: slideUp 0.9s forwards; - overflow: hidden; - display: hidden; +.slideOutContainer.active { + height: auto; } -.visibleTransition { - animation: slideDown 0.9s forwards; - overflow: hidden; - display: block; +.slideOutContainer:not(.active) { + display: none; } .displayInline { @@ -202,13 +185,18 @@ margin: 2px 1px; } -/* For the shuffle button, we want some more space around it */ .shuffleButton { margin-top: 8px; margin-bottom: 8px; font-size: 15px; } +.importantButton { + font-size: 16px; + margin-top: 10px; + margin-bottom: 6px; +} + .randomYoutubeVideoButton:hover { background-color: #444 !important; } @@ -382,4 +370,4 @@ select:hover { */ #randomYoutubeVideoFooter { padding: 8px 0; -} \ No newline at end of file +} diff --git a/static/html/popup.html b/static/html/popup.html index a496dc5a..c7f2376f 100644 --- a/static/html/popup.html +++ b/static/html/popup.html @@ -158,47 +158,47 @@

Channel settings

-

Advanced Settings

- -
-
- -
+ +
+ +
+
+ +
- -
- + +
+ +
-
- -