Change how the options are handled
Now the options are parsed by a single function in the Options class,
streamlining how they are interpreted in different parts of the code
(background page and the options page).
Bug: translateselectedtext:11
Change-Id: I1696da55c8c8d99683a0f45080b59849f9d4be4f
diff --git a/src/background.js b/src/background.js
index 286ec97..e72574e 100644
--- a/src/background.js
+++ b/src/background.js
@@ -1,18 +1,8 @@
-import {convertLanguages, isoLangs} from './common/consts.js';
+import {isoLangs} from './common/consts.js';
+import Options from './common/options.js';
var array_elements = [], translator_tab = null, translator_window = null;
-function isEmpty(obj) {
- return Object.keys(obj).length === 0;
-}
-
-function inObject(hayStack, el) {
- for (var i of Object.keys(hayStack)) {
- if (hayStack[i] == el) return true;
- }
- return false;
-}
-
function getTranslationUrl(lang, text) {
var params = new URLSearchParams({
sl: 'auto',
@@ -24,263 +14,152 @@
}
function translationClick(info, tab) {
- chrome.storage.sync.get('uniquetab', items => {
- var url = getTranslationUrl(
- array_elements[info.menuItemId]['langCode'], info.selectionText);
- var settings_tab = {url};
- if (translator_tab && items.uniquetab == 'yep') {
- chrome.tabs.update(translator_tab, settings_tab, tab => {
- chrome.tabs.highlight(
- {
- windowId: tab.windowId,
- tabs: tab.index,
- },
- _ => {
- chrome.windows.update(tab.windowId, {
- focused: true,
+ Options.getOptions()
+ .then(options => {
+ var url = getTranslationUrl(
+ array_elements[info.menuItemId]['langCode'], info.selectionText);
+ var settings_tab = {url};
+ if (translator_tab && options.uniqueTab == 'yep') {
+ chrome.tabs.update(translator_tab, settings_tab, tab => {
+ chrome.tabs.highlight(
+ {
+ windowId: tab.windowId,
+ tabs: tab.index,
+ },
+ () => {
+ chrome.windows.update(tab.windowId, {
+ focused: true,
+ });
+ });
+ });
+ } else if (options.uniqueTab == 'popup') {
+ chrome.windows.create(
+ {
+ type: 'popup',
+ url,
+ width: 1000,
+ height: 382,
+ },
+ function(tab) {
+ translator_window = tab.windowId;
+ translator_tab = tab.id;
+ chrome.windows.onRemoved.addListener(function(windowId) {
+ if (windowId == translator_window) {
+ translator_window = null;
+ translator_tab = null;
+ }
+ });
});
- });
- });
- } else if (items.uniquetab == 'panel' || items.uniquetab == 'popup') {
- chrome.windows.create(
- {
- type: 'popup',
- url,
- width: 1000,
- height: 382,
- },
- function(tab) {
+ } else {
+ chrome.tabs.create(settings_tab, function(tab) {
translator_window = tab.windowId;
translator_tab = tab.id;
- chrome.windows.onRemoved.addListener(function(windowId) {
- if (windowId == translator_window) {
+ chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) {
+ if (tabId == translator_tab) {
translator_window = null;
translator_tab = null;
}
});
});
- } else {
- chrome.tabs.create(settings_tab, function(tab) {
- translator_window = tab.windowId;
- translator_tab = tab.id;
- chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) {
- if (tabId == translator_tab) {
- translator_window = null;
- translator_tab = null;
- }
- });
+ }
+ })
+ .catch(err => {
+ console.error('Error retrieving options to handle translation', err);
});
- }
- });
}
-function openOptionsPage() {
- if (chrome.runtime.openOptionsPage) {
- // New way to open options pages, if supported (Chrome 42+).
- chrome.runtime.openOptionsPage();
- } else {
- chrome.tabs.create(
- {
- 'url': 'chrome-extension://' +
- chrome.i18n.getMessage('@@extension_id') + '/options.html',
- 'active': true
- },
- tab => {
- chrome.windows.update(tab.windowId, {focused: true});
- });
- }
-}
+function createMenus(options) {
+ chrome.contextMenus.removeAll();
-function createmenus() {
- chrome.storage.sync.get('translateinto', function(items) {
- chrome.contextMenus.removeAll();
+ let langs = options.targetLangs;
+ let isSingleEntry = Object.values(langs).length == 1;
- var count = 0, singleone = true;
-
- for (var language of Object.keys(items.translateinto)) {
- if (count == 0) {
- count++;
- } else {
- singleone = false;
- break;
- }
- }
-
- if (singleone) {
- for (var language_id of Object.keys(items.translateinto)) {
- var language = items.translateinto[language_id];
- var languagem = isoLangs[language];
- if (languagem === undefined) {
- console.error(language + ' doesn\'t exist!');
- continue;
- }
- var id = chrome.contextMenus.create({
- 'id': 'tr_single_parent',
- 'title': chrome.i18n.getMessage('contextmenu_title2', languagem.name),
- 'contexts': ['selection'],
- });
- array_elements[id] = new Array();
- array_elements[id]['langCode'] = language;
- }
- } else {
- var parentEl = chrome.contextMenus.create({
- 'id': 'parent',
- 'title': chrome.i18n.getMessage('contextmenu_title'),
- 'contexts': ['selection']
- });
- for (var language_id of Object.keys(items.translateinto)) {
- var language = items.translateinto[language_id];
- var languagem = isoLangs[language];
- if (languagem === undefined) {
- console.error(language + ' doesn\'t exist!');
- continue;
- }
- var title = languagem.name + ' (' + languagem.nativeName + ')';
- var id = chrome.contextMenus.create({
- 'id': 'tr_language_' + language,
- 'title': title,
- 'parentId': parentEl,
- 'contexts': ['selection']
- });
- array_elements[id] = new Array();
- array_elements[id]['langCode'] = language;
- }
- chrome.contextMenus.create({
- 'id': 'tr_separator',
- 'type': 'separator',
- 'parentId': parentEl,
- 'contexts': ['selection']
- });
- chrome.contextMenus.create({
- 'id': 'tr_options',
- 'title': chrome.i18n.getMessage('contextmenu_edit'),
- 'parentId': parentEl,
- 'contexts': ['selection']
- });
- }
- });
-}
-
-chrome.runtime.onInstalled.addListener(function(details) {
- chrome.storage.sync.get(null, function(items) {
- if (details.reason == 'install') {
- if (isEmpty(items)) {
- var settings = {'translateinto': {}, 'uniquetab': 'popup'},
- default_language_1 =
- chrome.i18n.getMessage('@@ui_locale').replace('_', '-'),
- default_language_2 =
- chrome.i18n.getMessage('@@ui_locale').split('_')[0];
-
- if (isoLangs[default_language_1] != undefined)
- settings.translateinto['0'] = default_language_1;
- else if (isoLangs[default_language_2] != undefined)
- settings.translateinto['0'] = default_language_2;
-
- chrome.storage.sync.set(settings, function() {
- chrome.notifications.create('install', {
- type: 'basic',
- iconUrl: 'icons/translate-128.png',
- title: chrome.i18n.getMessage('notification_install_title'),
- message: chrome.i18n.getMessage('notification_install_message'),
- isClickable: true
- });
- });
- }
- }
- if (details.reason == 'update') {
- var version = details.previousVersion.split('.');
-
- // Updating from a version previous to v0.6
- if (version[0] == '0' && version[1] < '6') {
- var settings = {
- languages: {},
- uniquetab: '',
- };
- var default_language =
- chrome.i18n.getMessage('@@ui_locale').split('_')[0];
-
- if (isoLangs[default_language] != undefined)
- settings.languages[default_language] = default_language;
-
- chrome.storage.sync.set(settings);
- }
-
- // Updating from a version previous to v0.7
- if (version[0] == '0' && version[1] < '7') {
- items.translateinto = {};
- var i = 0;
- for (var language in items.languages) {
- items.translateinto[i] = items.languages[language];
- i++;
- }
- delete items.languages;
- chrome.storage.sync.set(items);
- }
-
- // Remove non-existent languages or change with correct language code
- if (items.translateinto) {
- var modified = false;
- for (var language_id of Object.keys(items.translateinto)) {
- var language = items.translateinto[language_id];
- if (isoLangs[language] === undefined) {
- if (convertLanguages[language] === undefined) {
- // The language doesn't exist
- console.log(
- 'Deleting ' + language +
- ' from items.translateinto because it doesn\'t exist.');
- delete items.translateinto[language_id];
- } else {
- // The language doesn't exist but a known replacement is known
- var newLanguage = convertLanguages[language];
- console.log('Replacing ' + language + ' with ' + newLanguage);
-
- // If the converted language is already on the list, just remove
- // the wrong language, otherwise convert the language
- if (inObject(items.translateinto, newLanguage))
- delete items.translateinto[language_id];
- else
- items.translateinto[language_id] = newLanguage;
- }
- modified = true;
- }
- }
- if (modified) chrome.storage.sync.set(items);
- } else {
- console.log('items.translateinto doesn\'t exist: let\'s create it.');
- items['translateinto'] = {};
- chrome.storage.sync.set(items);
- }
- }
- });
-});
-
-chrome.storage.onChanged.addListener((changes, areaName) => {
- if (areaName == 'sync') createmenus();
-});
-
-chrome.storage.sync.get(null, items => {
- if (items.translateinto) {
- createmenus();
- } else {
- chrome.contextMenus.removeAll();
- var parent = chrome.contextMenus.create({
- 'id': 'tr_parent',
+ let parentEl;
+ if (!isSingleEntry) {
+ parentEl = chrome.contextMenus.create({
+ 'id': 'parent',
'title': chrome.i18n.getMessage('contextmenu_title'),
'contexts': ['selection']
});
- var id = chrome.contextMenus.create({
+ }
+
+ for (let [index, language] of Object.entries(langs)) {
+ let languageDetails = isoLangs[language];
+ if (languageDetails === undefined) {
+ console.error(language + ' doesn\'t exist!');
+ continue;
+ }
+ let title;
+ if (isSingleEntry) {
+ title =
+ chrome.i18n.getMessage('contextmenu_title2', languageDetails.name);
+ } else {
+ title = languageDetails.name + ' (' + languageDetails.nativeName + ')';
+ }
+ let id = chrome.contextMenus.create({
+ 'id': 'tr_language_' + language,
+ 'title': title,
+ 'parentId': parentEl,
+ 'contexts': ['selection']
+ });
+ array_elements[id] = new Array();
+ array_elements[id]['langCode'] = language;
+ }
+
+ if (!isSingleEntry) {
+ chrome.contextMenus.create({
+ 'id': 'tr_separator',
+ 'type': 'separator',
+ 'parentId': parentEl,
+ 'contexts': ['selection']
+ });
+ chrome.contextMenus.create({
'id': 'tr_options',
'title': chrome.i18n.getMessage('contextmenu_edit'),
- 'parentId': parent,
+ 'parentId': parentEl,
'contexts': ['selection']
});
}
+}
+
+chrome.storage.onChanged.addListener((changes, areaName) => {
+ if (areaName == 'sync') {
+ Options.getOptions(/* readOnly = */ false)
+ .then(options => {
+ createMenus(options);
+ })
+ .catch(err => {
+ console.error(
+ 'Error retrieving options to set up the extension after a change ' +
+ 'in the storage area.',
+ err);
+ });
+ }
});
+Options.getOptions(/* readOnly = */ false)
+ .then(options => {
+ if (options.isFirstRun) {
+ chrome.notifications.create('install', {
+ type: 'basic',
+ iconUrl: 'icons/translate-128.png',
+ title: chrome.i18n.getMessage('notification_install_title'),
+ message: chrome.i18n.getMessage('notification_install_message'),
+ isClickable: true
+ });
+ }
+
+ createMenus(options);
+ })
+ .catch(err => {
+ console.error(
+ 'Error retrieving options to initialize the extension.', err);
+ });
+
chrome.notifications.onClicked.addListener(notification_id => {
switch (notification_id) {
case 'install':
- openOptionsPage();
+ chrome.runtime.openOptionsPage();
break;
}
chrome.notifications.clear(notification_id);
@@ -288,12 +167,12 @@
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId == 'tr_options') {
- openOptionsPage();
+ chrome.runtime.openOptionsPage();
} else {
translationClick(info, tab);
}
});
-chrome.browserAction.onClicked.addListener(_ => {
- openOptionsPage();
+chrome.browserAction.onClicked.addListener(() => {
+ chrome.runtime.openOptionsPage();
});
diff --git a/src/common/options.js b/src/common/options.js
new file mode 100644
index 0000000..bfe668b
--- /dev/null
+++ b/src/common/options.js
@@ -0,0 +1,163 @@
+import {convertLanguages, isoLangs} from './consts.js';
+
+export const TAB_OPTIONS = [
+ // Open in new tab for each translation
+ {
+ value: '',
+ labelMsg: 'options_tabsoption_1',
+ deprecatedValues: [],
+ },
+ // Open in a unique tab
+ {
+ value: 'yep',
+ labelMsg: 'options_tabsoption_2',
+ deprecatedValues: [],
+ },
+ // Open in a popup
+ {
+ value: 'popup',
+ labelMsg: 'options_tabsoption_3',
+ deprecatedValues: ['panel'],
+ },
+];
+
+// Class which can be used to retrieve the user options in order to act
+// accordingly.
+export default class Options {
+ constructor(options, isFirstRun) {
+ this._options = options;
+ this.isFirstRun = isFirstRun;
+ }
+
+ get uniqueTab() {
+ return this._options.uniquetab;
+ }
+
+ get targetLangs() {
+ return this._options.translateinto;
+ }
+
+ // Returns a promise that resolves in an instance of the Object class with the
+ // current options.
+ static getOptions(readOnly = true) {
+ return Options.getOptionsRaw(readOnly).then(res => {
+ return new Options(res.options, res.isFirstRun);
+ });
+ }
+
+ // Returns a promise that resolves to an object containing:
+ // - |options|: normalized options object which can be used to initialize the
+ // Options class, and which contains the current options set up by the user.
+ // - |isFirstRun|: whether the extension is running for the first time and
+ // needs to be set up.
+ //
+ // If the options needed to be normalized/created, they are also saved in the
+ // sync storage area.
+ static getOptionsRaw(readOnly) {
+ return new Promise((res, rej) => {
+ chrome.storage.sync.get(null, items => {
+ if (chrome.runtime.lastError) {
+ return rej(chrome.runtime.lastError);
+ }
+
+ let didTranslateintoChange = false;
+ let didUniquetabChange = false;
+ let returnObject = {};
+
+ // If the extension sync storage area is blank, set this as being the
+ // first run.
+ returnObject.isFirstRun = Object.keys(items).length === 0;
+
+ // Create |translateinto| property if it doesn't exist.
+ if (items.translateinto === undefined) {
+ didTranslateintoChange = true;
+
+ // Upgrade from a version previous to v0.7 if applicable, otherwise
+ // create the property with the default values.
+ if (items.languages !== undefined) {
+ items.translateinto =
+ Object.assign({}, Object.values(items.languages));
+ } else {
+ let uiLocale = chrome.i18n.getMessage('@@ui_locale');
+ let defaultLang1 = uiLocale.replace('_', '-');
+ let defaultLang2 = uiLocale.split('_')[0];
+
+ items.translateinto = {};
+ if (isoLangs[default_language_1] != undefined)
+ items.translateinto['0'] = defaultLang1;
+ else if (isoLangs[default_language_2] != undefined)
+ items.translateinto['0'] = defaultLang2;
+ }
+ }
+
+ // Normalize |translateinto| property: remove non-existent languages or
+ // change them with the correct language code.
+ for (let [index, language] of Object.entries(items.translateinto)) {
+ if (isoLangs[language] === undefined) {
+ didTranslateintoChange = true;
+ if (convertLanguages[language] === undefined) {
+ // The language doesn't exist
+ console.log(
+ 'Deleting ' + language +
+ ' from items.translateinto because it doesn\'t exist.');
+ delete items.translateinto[index];
+ } else {
+ // The language doesn't exist but a known replacement is known
+ let newLanguage = convertLanguages[language];
+ console.log('Replacing ' + language + ' with ' + newLanguage);
+
+ // If the converted language is already on the list, just remove
+ // the wrong language, otherwise convert the language
+ if (Object.values(items.translateinto).includes(newLanguage))
+ delete items.translateinto[index];
+ else
+ items.translateinto[index] = newLanguage;
+ }
+ }
+ }
+
+ // Normalize |uniquetab| property:
+ // - If it is set to a valid value, leave it alone.
+ // - If it is set to a deprecated value, change it to the corresponding
+ // value we use now.
+ // - If it is set to an incorrect value or it isn't set, change it to
+ // the default value.
+ let foundValue = false;
+ for (let opt of TAB_OPTIONS) {
+ if (opt.value == items?.uniquetab) {
+ foundValue = true;
+ break;
+ }
+ if (opt.deprecatedValues.includes(items?.uniquetab)) {
+ foundValue = true;
+ items.uniquetab = opt.value;
+ break;
+ }
+ }
+ if (!foundValue) {
+ items.uniquetab = 'popup';
+ didUniquetabChange = true;
+ }
+
+ // Clean up deprecated properties
+ if (items.languages !== undefined) {
+ delete items.languages;
+ chrome.storage.sync.remove('languages');
+ }
+
+ // Save properties that have changed if we're not in read-only mode
+ if (!readOnly) {
+ if (didTranslateintoChange || didUniquetabChange) {
+ chrome.storage.sync.set({
+ translateinto: items.translateinto,
+ uniquetab: items.uniquetab,
+ });
+ }
+ }
+
+ returnObject.options = items;
+ res(returnObject);
+ });
+ });
+ }
+}
diff --git a/src/options/elements/options-editor/options-editor.js b/src/options/elements/options-editor/options-editor.js
index 9e32dfa..b7eeb52 100644
--- a/src/options/elements/options-editor/options-editor.js
+++ b/src/options/elements/options-editor/options-editor.js
@@ -2,31 +2,11 @@
import {map} from 'lit/directives/map.js';
import {msg} from '../../../common/i18n.js';
+import {TAB_OPTIONS} from '../../../common/options.js';
import {SHARED_STYLES} from '../../shared/shared-styles.js';
import LanguagesEditor from './languages-editor.js';
-const TAB_OPTIONS = [
- // Open in new tab for each translation
- {
- value: '',
- labelMsg: 'options_tabsoption_1',
- deprecatedValues: [],
- },
- // Open in a unique tab
- {
- value: 'yep',
- labelMsg: 'options_tabsoption_2',
- deprecatedValues: [],
- },
- // Open in a popup
- {
- value: 'popup',
- labelMsg: 'options_tabsoption_3',
- deprecatedValues: ['panel'],
- },
-];
-
export class OptionsEditor extends LitElement {
static properties = {
storageData: {type: Object},
@@ -58,7 +38,7 @@
return html`
<p>
<input type="radio" name="uniquetab" id="uniquetab_${i}"
- value="${option?.value}" ?checked="${checked}"
+ value="${option?.value}" .checked="${checked}"
@change="${() => this.changeTabOption(option.value)}">
<label for="uniquetab_${i}">${msg(option.labelMsg)}</label></p>
`;
diff --git a/src/options/options.js b/src/options/options.js
index ec50b6a..7a16ef9 100644
--- a/src/options/options.js
+++ b/src/options/options.js
@@ -1,10 +1,10 @@
import {css, html, LitElement} from 'lit';
import {msg} from '../common/i18n.js';
+import Options from '../common/options.js';
import CreditsDialog from './elements/credits-dialog/credits-dialog.js';
import OptionsEditor from './elements/options-editor/options-editor.js';
-
import {SHARED_STYLES} from './shared/shared-styles.js';
let bodyStyles = document.createElement('style');
@@ -87,17 +87,16 @@
}
updateStorageData() {
- chrome.storage.sync.get(null, items => {
- // If no settings are set
- if (Object.keys(items).length === 0) {
- items = {
- translateinto: {},
- uniquetab: 'popup',
- };
- chrome.storage.sync.set(items);
- }
- this._storageData = items;
- });
+ Options.getOptions(/* readOnly = */ true)
+ .then(options => {
+ this._storageData = {
+ translateinto: options.targetLangs,
+ uniquetab: options.uniqueTab,
+ };
+ })
+ .catch(err => {
+ console.error('Error retrieving user options.', err);
+ });
}
showCredits() {