/**
* @module Thematic
* @description Handles logic for...
*/
/* global browser */
'use strict'
if (typeof process === 'undefined') {
const module = {}
}
module.exports = {
isDefaultTheme,
chooseNext,
getCurrentId,
getDefaultTheme,
buildToolsMenuItem,
buildThemes,
stopRotation,
startRotation,
rotate,
handleMessage,
commands
}
/**
* Takes in an array of all themes installed.
* Gets a default Mozilla. Logs to console if no default theme found.
* @param {Array<Object>} allThemes - the list of all installed themes
* @returns {Object} The default theme of the user
*/
function getDefaultTheme (allThemes) {
let themes = allThemes.filter(info => info.name === 'Default')
if (themes.length > 0) {
console.log(themes[0])
return themes[0]
}
themes = allThemes.filter(info => info.id === 'default-theme@mozilla.org')
if (themes.length > 0) {
console.log(themes[0])
return themes[0]
}
for (const theme of allThemes) {
if (isDefaultTheme(theme)) {
console.log(theme)
return theme
}
}
console.log('No default theme found!')
}
/**
* Determines if a given theme is a built-in Mozilla or Firefox theme.
* @param {Object} theme - The theme object to check.
* @returns {boolean} True if it is a default theme, false otherwise.
*/
function isDefaultTheme (theme) {
return [
'firefox-compact-dark@mozilla.org',
'firefox-compact-light@mozilla.org',
'default-theme@mozilla.org',
'firefox-alpenglow@mozilla.org',
'thunderbird-compact-dark@mozilla.org',
'thunderbird-compact-light@mozilla.org',
'{972ce4c6-7e08-4474-a285-3208198ce6fd}'
].includes(theme.id)
}
/**
* Returns the ID of current theme
* @param {Object} localThemeId User's current theme id, assuming it exists
* @param {Array<Object>} userThemes Array of non-default, user-added themes
* @param {Object} defaultTheme Default Mozilla/Firefox theme
* @returns {string} currentId
*/
function getCurrentId (localThemeId, userThemes, defaultTheme) {
if (Object.keys(localThemeId).length !== 0) {
return localThemeId.currentId
}
if (userThemes.length > 0) {
console.log('Setting currentId to first user theme')
return userThemes[0].id
}
console.log('Setting currentId to default theme')
return defaultTheme.id
}
/**
* Accesses all installed themes and organizes them categorically.
* @returns {Promise<void>}
*/
async function buildThemes () {
const allExtensions = await browser.management.getAll()
const allThemes = allExtensions.filter(info => info.type === 'theme')
// console.log(allThemes)
const localThemeId = await browser.storage.local.get('currentId')
const defaultTheme = getDefaultTheme(allThemes)
const defaultThemes = allThemes.filter(theme => isMozillaTheme(theme))
const userThemes = allThemes.filter(theme => !isMozillaTheme(theme))
const currentId = getCurrentId(localThemeId, userThemes, defaultTheme)
const themes = {
currentId: currentId,
defaultTheme: defaultTheme,
defaultThemes: defaultThemes,
userThemes: userThemes
}
await browser.storage.local.set(themes)
buildToolsMenu(themes)
}
/**
* Determines if a theme is a theme from Mozilla.
* @param {Object} theme Theme to be tested.
* @returns {Boolean} True if Mozilla theme, false if not a Mozilla theme.
*/
function isMozillaTheme (theme) {
return theme.id.endsWith('mozilla.org')
}
// https://stackoverflow.com/questions/49432579/await-is-only-valid-in-async-function
// bleah
async function buildThemesHelper () {
await buildThemes()
}
buildThemesHelper()
/*
async function asyncHelper(fn) {
await fn()
}
asyncHelper(buildThemes)
*/
/**
* Calculates the next theme index based on user preferences (random or sequential).
* @param {number} currentIndex - The index of the currently active theme.
* @param {Object} items - Object containing userThemes array.
* @returns {Promise<number>} The index of the next theme to enable.
*/
async function chooseNext (currentIndex, items) {
const pref = await browser.storage.sync.get('random')
if (pref.random) {
let newIndex = currentIndex
while (newIndex === currentIndex) {
const a = Math.floor(Math.random() * items.userThemes.length)
newIndex = a
}
return newIndex
}
return (currentIndex + 1) % items.userThemes.length
}
/**
* Rotates to the next theme in user's collection.
* @returns {Promise<void>}
*/
async function rotate () {
const items = await browser.storage.local.get()
if (items.userThemes.length < 1) {
console.log('No user themes found!')
return
}
if (typeof items.currentId === 'undefined') {
console.log('No current theme Id found!')
return
}
const previousId = items.currentId
console.log('From: ' + previousId)
const previousIndex = items.userThemes.findIndex((t) => t.id === previousId)
if (previousIndex === -1) {
// this will get resolved below as 1 will be added to this :/
console.log('User theme index not found')
} else {
await browser.management.setEnabled(previousId, false)
}
const currentIndex = await chooseNext(previousIndex, items)
const currentId = items.userThemes[currentIndex].id
console.log('To: ' + currentId)
await browser.storage.local.set({ currentId: currentId })
await browser.management.setEnabled(currentId, true)
}
browser.alarms.onAlarm.addListener(rotate)
/**
* Begins the process for rotating through themes on a timer.
*/
async function startRotation () {
const info = await browser.runtime.getBrowserInfo()
if (info.name !== 'Thunderbird') {
await browser.storage.sync.set({ auto: true })
const a = await browser.storage.sync.get('minutes')
await browser.alarms.create('rotate', { periodInMinutes: a.minutes })
}
}
/**
* Stops the process of rotating through themes.
*/
async function stopRotation () {
const info = await browser.runtime.getBrowserInfo()
if (info.name !== 'Thunderbird') {
await browser.storage.sync.set({ auto: false })
await browser.alarms.clear('rotate')
}
}
browser.storage.sync.get('auto').then((pref) => {
if (pref.auto) {
startRotation().catch((err) => { console.log(err) })
}
})
function handleMessage (request, sender, sendResponse) {
console.log('Message from the popup or options script: ' + request.message)
switch (request.message) {
case 'Start rotation':
startRotation().catch((err) => { console.log(err) })
sendResponse({ response: 'OK' })
break
case 'Stop rotation':
stopRotation().catch((err) => { console.log(err) })
sendResponse({ response: 'OK' })
break
default:
console.log('Unknown message received')
sendResponse({ response: 'Not OK' })
break
}
}
browser.runtime.onMessage.addListener(handleMessage)
// allow Jest's mocking to occur
// https://stackoverflow.com/questions/25649097/nodejs-override-a-function-in-a-module
function jestTest (fn, testfn) {
if (typeof process === 'undefined') {
fn()
} else {
testfn()
}
}
async function jestTestAwait (fn, testfn) {
if (typeof process === 'undefined') {
await fn()
} else {
await testfn()
}
}
async function commands (command) {
// "command" value is defined a la manifest.json
// console.log(command)
switch (command) {
case 'Switch to default theme':
try {
console.log(`${command} was selected!`)
// rm the log
const c = await browser.storage.local.get('defaultTheme')
const defaultTheme = c.defaultTheme
await browser.storage.local.set({ currentId: defaultTheme.id })
browser.management.setEnabled(defaultTheme.id, true)
jestTestAwait(stopRotation, module.exports.stopRotation)
} catch (error) {
console.log(error.message)
}
break
case 'Rotate to next theme':
jestTest(rotate, module.exports.rotate)
break
case 'Toggle autoswitching':
try {
const c = await browser.storage.sync.get('auto')
const auto = c.auto
if (auto) {
jestTestAwait(stopRotation, module.exports.stopRotation)
} else {
jestTestAwait(startRotation, module.exports.startRotation)
}
await browser.storage.sync.set({ auto: !auto })
} catch (error) {
console.log(error.message)
}
break
case 'Change theme category':
console.log(`${command} was selected!`)
break
default:
console.log(`${command} not recognized`)
break
}
}
async function commandsHelper (command) {
await commands(command)
}
browser.commands.onCommand.addListener(commandsHelper)
function buildToolsMenuItem (theme) {
browser.menus.create({
id: theme.id,
type: 'normal',
title: theme.name,
contexts: ['tools_menu']
})
}
async function buildToolsMenu (themes) {
const info = await browser.runtime.getBrowserInfo()
if (info.name === 'Thunderbird') {
return
}
await browser.menus.removeAll()
if (themes.userThemes.length !== 0) {
for (const theme of themes.userThemes) {
buildToolsMenuItem(theme)
}
browser.menus.create({
type: 'separator',
contexts: ['tools_menu']
})
}
for (const theme of themes.defaultThemes) {
buildToolsMenuItem(theme)
}
}
function extensionInstalled (info) {
if (info.type === 'theme') {
buildThemesHelper()
}
}
browser.management.onInstalled.addListener(extensionInstalled)
browser.management.onUninstalled.addListener(extensionInstalled)
browser.menus.onClicked.addListener((info) => {
console.log(info)
const currentId = info.menuItemId
browser.storage.local.set({ currentId: currentId }).then(() => {
browser.management.setEnabled(currentId, true)
}).catch((err) => { console.log(err) })
})