diff --git a/background.js b/background.js index 8a7bf32..a0a53f1 100644 --- a/background.js +++ b/background.js @@ -1,5 +1,5 @@ /** - * Copyright 2020-2021 Mehmet Baker + * Copyright 2020-2023 Mehmet Baker * * This file is part of dimmer. * @@ -23,70 +23,79 @@ * @typedef {object} TabState * @property {boolean} isDimmed * @property {number} opacity A number [0, 1] - * @property {boolean} [overrideGlobalState] If true, popup option 'Only for this tab' will be checked. + * @property {boolean} [applySettingsToCurrentTab] If true, popup option 'Apply settings to this tab only' will be checked. */ +const defaultSettings = { + applyToAllTabs: true, +}; + +const initialState = { + isDimmed: false, + opacity: 0.7, +}; + /** - * This state represents the state of all tabs that haven't selected 'Only this tab' option. + * This state is the state of tabs that checked 'Apply settings to all tabs' option. */ const globalState = { - isDimmed: false, - opacity: 0.7, + ...initialState, }; /** - * These states represent the states of tabs that checked the 'Only this tab' option. - * Keys are tab ids and values are dim states. + * These states are of tabs that checked the 'Apply settings to this tab only' option. + * Keys are tab ids and values are states. * @type {Map} */ -const overriddenStates = new Map(); +const localStateTabs = new Map(); + +const globalStateTabs = new Set(); /** - * Returns a tab state. If the option 'Only for this tab' is selected for the tab, - * then the overridden state will be returned. Otherwise the global state will be - * returned. + * Returns a tab's state. * @param {number} tabId Tab ID * @returns {TabState} */ function getState(tabId) { - if (overriddenStates.has(tabId)) { + if (localStateTabs.has(tabId)) { return { - ...overriddenStates.get(tabId), - overrideGlobalState: true, + ...localStateTabs.get(tabId), + applySettingsToCurrentTab: true, }; } + return globalState; } /** * Adds/updates the overridden states * @param {number} tabId Tab ID. - * @param {*} params Prop(s) of TabState + * @param {{ isDimmed?: boolean, opacity?: number }} params Prop(s) of TabState */ function extendState(tabId, params = {}) { const state = getState(tabId); - overriddenStates.set(tabId, { + localStateTabs.set(tabId, { ...state, ...params, }); } /** - * Updates the state object in background script. Also updates the states of content + * Updates the state object in the background script. Also updates the states of content * scripts by sending them set-state commands. * @param {number} tabId Tab ID - * @param {string|null} stateProp A property key of TabState + * @param {('isDimmed'|'opacity')|null} stateProp A property key of TabState * @param {boolean|number} [propValue] A prop value of TabState * @returns {Promise} */ async function setState(tabId, stateProp, propValue) { - // If 'Only for this tab' option is selected, update the current tab only - if (overriddenStates.has(tabId)) { + // If 'Apply settings to this tab only' option is selected, update the current tab only + if (localStateTabs.has(tabId)) { const state = getState(tabId); if (stateProp !== null) { state[stateProp] = propValue; - overriddenStates.set(tabId, state); + localStateTabs.set(tabId, state); } await browser.tabs.sendMessage(tabId, { @@ -98,11 +107,12 @@ async function setState(tabId, stateProp, propValue) { opacity: state.opacity, }, }); + return; } - // If 'Only for this tab' is NOT selected, then update all tabs but the overriddens. - + // If 'Apply settings to all tabs' option is checked then update all the other tabs + // that checked 'Apply settings to all tabs' option as well. if (stateProp !== null) { globalState[stateProp] = propValue; } @@ -120,7 +130,7 @@ async function setState(tabId, stateProp, propValue) { }; for (const tab of allTabs) { - const isOverridden = overriddenStates.has(tab.id); + const isOverridden = localStateTabs.has(tab.id); if (!isOverridden) { const promise = browser.tabs.sendMessage(tab.id, command); promises.push(promise); @@ -180,7 +190,18 @@ async function handleMessage(message, sender) { switch (message.command) { case 'query': { - return getState(activeTab.id); + if (!localStateTabs.has(activeTab.id) && !globalStateTabs.has(activeTab.id)) { + if (defaultSettings.applyToAllTabs) { + globalStateTabs.add(activeTab.id); + } else { + localStateTabs.set(activeTab.id, { ...initialState }); + } + } + + return { + state: getState(activeTab.id), + defaultSettings, + }; } case 'dim': { @@ -196,16 +217,36 @@ async function handleMessage(message, sender) { return setState(activeTab.id, 'opacity', message.data.opacity); } - case 'override-global-state': { + case 'apply-settings-to-current-tab-only': { + globalStateTabs.delete(activeTab.id); extendState(activeTab.id); return Promise.resolve(); } - case 'remove-state-override': { - overriddenStates.delete(activeTab.id); + case 'apply-settings-to-all-tabs': { + // get the current state of the tab + const tabState = localStateTabs.get(activeTab.id); + + if (tabState) { + // update global state settings to match the current tab's state + globalState.isDimmed = tabState.isDimmed; + globalState.opacity = tabState.opacity; + } + + // remove the current tab from the ovveriden map so it can get global state changes in the future + localStateTabs.delete(activeTab.id); + globalStateTabs.add(activeTab.id); + + // calling `setState` with `null` will cause all tabs that don't choose 'Apply settings to this tab only' to get + // the latest gloabl state return setState(activeTab.id, null); } + case 'update-default-settings': { + defaultSettings.applyToAllTabs = message.data.applyToAllTabs; + return Promise.resolve({ defaultSettings, message }); + } + default: { return Promise.resolve(); } @@ -213,7 +254,8 @@ async function handleMessage(message, sender) { } function handleTabRemove(tabId) { - overriddenStates.delete(tabId); + localStateTabs.delete(tabId); + globalStateTabs.delete(tabId); } browser.commands.onCommand.addListener(handleCommand); diff --git a/content_scripts/overlay.js b/content_scripts/overlay.js index eb5d7f0..ee4ccd3 100644 --- a/content_scripts/overlay.js +++ b/content_scripts/overlay.js @@ -1,5 +1,5 @@ /** - * Copyright 2020-2021 Mehmet Baker + * Copyright 2020-2023 Mehmet Baker * * This file is part of dimmer. * @@ -76,8 +76,9 @@ switch (message.command) { case 'set-state': { + state.opacity = message.data.opacity; + if (state.isDimmed) { - state.opacity = message.data.opacity; state.container.style.opacity = state.opacity; } @@ -103,8 +104,8 @@ to: 'background', }); - state.isDimmed = !!response.isDimmed; - state.opacity = String(response.opacity) || '0'; + state.isDimmed = !!response.state.isDimmed; + state.opacity = String(response.state.opacity) || '0'; state.container.style.opacity = state.isDimmed ? state.opacity : '0'; setTimeout(() => { diff --git a/package.json b/package.json index b6f8442..7dad73c 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "index.js", "scripts": { "build": "web-ext build --overwrite-dest -i package.json README.md yarn.lock dark-mode demo.gif _config.yml", - "dev": "env-cmd -x web-ext run --verbose -u https://mehmetbaker.dev --firefox-profile=\"\\$DARK_MODE_PROFILE\"", - "dev-dark": "env-cmd -x web-ext run --verbose -u https://mehmetbaker.dev --firefox-profile=\"\\$LIGHT_MODE_PROFILE\"", + "dev": "env-cmd -x --use-shell web-ext run -u https://wikipedia.com --verbose --firefox-profile=\"\\$LIGHT_MODE_PROFILE\"", + "dev-dark": "env-cmd -x web-ext run --verbose -u https://wikipedia.com --firefox-profile=\"\\$DARK_MODE_PROFILE\"", "lint": "eslint content_scripts/*.js popup/*.js background.js", "lint-fix": "eslint --fix content_scripts/*.js popup/*.js background.js", "test": "echo \"Error: no test specified\" && exit 1" diff --git a/popup/back-24.svg b/popup/back-24.svg new file mode 100644 index 0000000..ff32ed9 --- /dev/null +++ b/popup/back-24.svg @@ -0,0 +1,4 @@ + + diff --git a/popup/popup.css b/popup/popup.css index f434c5b..7e79afc 100644 --- a/popup/popup.css +++ b/popup/popup.css @@ -90,3 +90,54 @@ button.default:hover { button.default:hover:active { background-color: rgba(0, 0, 0, 0.1); } + +.panel-formElements-item.checkbox-panel, .panel-formElements-item.checkbox-panel label { + display: block; +} + +.panel-formElements-item.checkbox-panel input { + margin-top: 10px; +} + +div.settings { + background-color: #676774ab; + mask: url(./settings-24.svg) no-repeat center; + width: 24px; + height: 24px; + padding-bottom: 0; + margin-bottom: 0; + position: absolute; + top: 5px; + right: 5px; +} + +div.settings:hover { + cursor: pointer; + background-color: #676774; +} + +div.back { + color: #676774ab; + position: absolute; + top: 5px; + left: 14px; + display: flex; + align-items: center; +} + +div.back-icon { + display: inline-block; + background-color: #676774ab; + mask: url(./back-24.svg) no-repeat center; + width: 24px; + height: 24px; +} + +div.back:hover { + cursor: pointer; + color: #676774; +} + +div.back:hover div.back-icon { + background-color: #676774; +} diff --git a/popup/popup.html b/popup/popup.html index 0bfda8e..19d7ff9 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -1,5 +1,5 @@ +