Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made the Advanced settings a slideout menu #290

Merged
merged 4 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading