Skip to content

Commit

Permalink
Made the Advanced settings a slideout menu (#290)
Browse files Browse the repository at this point in the history
  • Loading branch information
NikkelM authored May 25, 2024
1 parent 7844cec commit b7e7811
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 146 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# Changelog

## v3.1.2
## v3.1.3-beta

<!--Releasenotes start-->
- Cleaned up the popup and moved some settings to a separate menu.
<!--Releasenotes end-->

## 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.
<!--Releasenotes end-->

## v3.1.1

Expand Down
4 changes: 3 additions & 1 deletion src/background.js
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -25,7 +28,6 @@ async function initExtension() {

checkLocalStorageCapacity();
}
await initExtension();

// Make sure we are not using too much local storage
async function checkLocalStorageCapacity() {
Expand Down
43 changes: 41 additions & 2 deletions src/html/htmlUtils.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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({});
Expand Down
125 changes: 69 additions & 56 deletions src/html/popup/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
45 changes: 20 additions & 25 deletions src/html/popup/popupUtils.js
Original file line number Diff line number Diff line change
@@ -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 -----
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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" });

Expand All @@ -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');
Expand All @@ -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');
Expand Down
Loading

0 comments on commit b7e7811

Please sign in to comment.