Refactor options page to use Typescript
Also, I've added and ran eslint, and fixed several minor issues accross
the Typescript codebase.
Bug: translateselectedtext:15
Change-Id: I8cfd67697f9bfb22f6de93b64fd750de66bab863
diff --git a/src/options/elements/credits-dialog/credits-dialog.js b/src/options/elements/credits-dialog/credits-dialog.ts
similarity index 67%
rename from src/options/elements/credits-dialog/credits-dialog.js
rename to src/options/elements/credits-dialog/credits-dialog.ts
index e7c3b10..e9201ee 100644
--- a/src/options/elements/credits-dialog/credits-dialog.js
+++ b/src/options/elements/credits-dialog/credits-dialog.ts
@@ -1,14 +1,15 @@
import {css, html, LitElement} from 'lit';
+import {customElement} from 'lit/decorators.js';
import {map} from 'lit/directives/map.js';
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
import {msg} from '../../../common/i18n';
-import credits from '../../credits.json5';
-import i18nCredits from '../../i18n-credits.json5';
import {DIALOG_STYLES} from '../../shared/dialog-styles';
import {SHARED_STYLES} from '../../shared/shared-styles';
+import {credits, i18nCredits} from '../../tsCredits';
-export class CreditsDialog extends LitElement {
+@customElement('credits-dialog')
+export default class CreditsDialog extends LitElement {
static get styles() {
return [
SHARED_STYLES,
@@ -64,34 +65,29 @@
}
render() {
- let translators = map(i18nCredits, contributor => {
- let languagesArray =
- contributor?.languages?.map?.(lang => lang?.name ?? 'undefined');
- let languages =
+ const translators = map(i18nCredits, (contributor) => {
+ const languagesArray =
+ contributor?.languages?.map?.((lang) => lang?.name ?? 'undefined');
+ const languages =
languagesArray.length > 0 ? ': ' + languagesArray.join(', ') : '';
return html`
- <li>
- <span class="name">${contributor?.name}</span>${languages}
- </li>
+ <li><span class="name">${contributor?.name}</span>${languages}</li>
`;
});
- let homepageMsg = msg('options_credits_homepage');
- let creditsByMsg = msg('options_credits_by');
+ const homepageMsg = msg('options_credits_homepage');
+ const creditsByMsg = msg('options_credits_by');
- let otherCredits = map(credits, c => {
- let url = c.url ? html`
- <a href=${c?.url} target="_blank" class="homepage">
- ${homepageMsg}
- </a>` :
- undefined;
- let license = c.license ? ' - ' + c.license : '';
- let author = c.author ? html`
- <p class="author">
- ${creditsByMsg} ${c.author}${license}
- </p>
- ` :
- undefined;
+ const otherCredits = map(credits, (c) => {
+ const url = c.url ?
+ html` <a href=${c?.url} target="_blank" class="homepage">
+ ${homepageMsg}
+ </a>` :
+ undefined;
+ const license = c.license ? ' - ' + c.license : '';
+ const author = c.author ?
+ html` <p class="author">${creditsByMsg} ${c.author}${license}</p> ` :
+ undefined;
return html`
<div class="entry">
@@ -119,9 +115,7 @@
${translators}
</ul>
</div>
- <div class="content_area">
- ${otherCredits}
- </div>
+ <div class="content_area">${otherCredits}</div>
</div>
<div class="action_buttons">
<button id="credits_ok" @click="${this.closeDialog}">
@@ -133,14 +127,13 @@
}
showDialog() {
- let dialog = this.renderRoot.querySelector('dialog');
+ const dialog = this.renderRoot.querySelector('dialog');
dialog.showModal();
dialog.querySelector('.scrollable').scrollTo(0, 0);
- dialog.querySelector('#credits_ok').focus();
+ (dialog.querySelector('#credits_ok') as HTMLElement).focus();
}
closeDialog() {
this.renderRoot.querySelector('dialog').close();
}
}
-customElements.define('credits-dialog', CreditsDialog);
diff --git a/src/options/elements/options-editor/add-language-dialog.js b/src/options/elements/options-editor/add-language-dialog.ts
similarity index 72%
rename from src/options/elements/options-editor/add-language-dialog.js
rename to src/options/elements/options-editor/add-language-dialog.ts
index 4cfd711..155d4e5 100644
--- a/src/options/elements/options-editor/add-language-dialog.js
+++ b/src/options/elements/options-editor/add-language-dialog.ts
@@ -1,23 +1,30 @@
import {css, html, LitElement} from 'lit';
+import {customElement, property} from 'lit/decorators.js';
-import {isoLangs} from '../../../common/consts';
+import {IsoLang, isoLangs} from '../../../common/consts';
import {msg} from '../../../common/i18n';
+import {TargetLangs} from '../../../common/options';
import {DIALOG_STYLES} from '../../shared/dialog-styles';
import {SHARED_STYLES} from '../../shared/shared-styles';
+interface IsoLangWCode extends IsoLang {
+ code: string;
+}
+
const ALL_LANGUAGES =
Object.entries(isoLangs)
.map(entry => {
- let lang = entry[1];
- lang.code = entry[0];
+ const lang: IsoLangWCode = Object.assign(entry[1], {code: entry[0]});
return lang;
})
.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0));
-export class AddLanguageDialog extends LitElement {
+@customElement('add-language-dialog')
+export default class AddLanguageDialog extends LitElement {
static properties = {
languages: {type: Object},
};
+ @property({type: Object}) languages: TargetLangs;
static get styles() {
return [
@@ -51,8 +58,8 @@
}
render() {
- let languageCodes = Object.values(this.languages ?? {});
- let languages = ALL_LANGUAGES
+ const languageCodes = Object.values(this.languages ?? {});
+ const languages = ALL_LANGUAGES
.filter(lang => {
return !languageCodes.includes(lang.code);
})
@@ -88,7 +95,7 @@
}
showDialog() {
- let dialog = this.renderRoot.querySelector('dialog');
+ const dialog = this.renderRoot.querySelector('dialog');
dialog.showModal();
}
@@ -97,16 +104,15 @@
}
addLanguage() {
- let languageCodes = Object.values(this.languages ?? {});
- let select = this.renderRoot.querySelector('#select_language');
+ const languageCodes = Object.values(this.languages ?? {});
+ const select = this.renderRoot.querySelector('#select_language') as HTMLSelectElement;
- let newLang = select.value;
+ const newLang = select.value;
languageCodes.push(newLang);
- let translateinto = Object.assign({}, languageCodes);
+ const translateinto = Object.assign({}, languageCodes);
chrome.storage.sync.set({translateinto}, () => {
select.selectedIndex = 0;
this.closeDialog();
});
}
}
-customElements.define('add-language-dialog', AddLanguageDialog);
diff --git a/src/options/elements/options-editor/languages-editor.js b/src/options/elements/options-editor/languages-editor.ts
similarity index 83%
rename from src/options/elements/options-editor/languages-editor.js
rename to src/options/elements/options-editor/languages-editor.ts
index 76d9967..64c3f84 100644
--- a/src/options/elements/options-editor/languages-editor.js
+++ b/src/options/elements/options-editor/languages-editor.ts
@@ -1,16 +1,17 @@
+import './add-language-dialog';
+
import {css, html, LitElement} from 'lit';
+import {customElement, property} from 'lit/decorators.js';
import {map} from 'lit/directives/map.js';
import {isoLangs} from '../../../common/consts';
import {msg} from '../../../common/i18n';
+import {TargetLangs} from '../../../common/options';
import {SHARED_STYLES} from '../../shared/shared-styles';
-import AddLanguageDialog from './add-language-dialog';
-
-export class LanguagesEditor extends LitElement {
- static properties = {
- languages: {type: Object},
- };
+@customElement('languages-editor')
+export default class LanguagesEditor extends LitElement {
+ @property({type: Object}) languages: TargetLangs;
static get styles() {
return [
@@ -84,16 +85,10 @@
];
}
- constructor() {
- super();
- this.addEventListener('show-credits-dialog', this.showDialog);
- this.sortable = undefined;
- }
-
render() {
- let languageCodes = Object.values(this.languages ?? {});
- let languageList = map(languageCodes, (lang, i) => {
- let moveBtns = [];
+ const languageCodes = Object.values(this.languages ?? {});
+ const languageList = map(languageCodes, (lang, i) => {
+ const moveBtns = [];
if (i != 0) {
moveBtns.push(html`
<button
@@ -160,29 +155,28 @@
this.renderRoot.querySelector('add-language-dialog').dispatchEvent(e);
}
- save(languageCodes) {
- let translateinto = Object.assign({}, languageCodes);
+ save(languageCodes: string[]) {
+ const translateinto = Object.assign({}, languageCodes);
chrome.storage.sync.set({translateinto});
}
- deleteLanguage(deleteLang) {
- let languageCodes =
+ deleteLanguage(deleteLang: string) {
+ const languageCodes =
Object.values(this.languages ?? {}).filter(lang => lang != deleteLang);
this.save(languageCodes);
}
- swapLanguages(i, j) {
- let languageCodes = Object.values(this.languages ?? {});
+ swapLanguages(i: number, j: number) {
+ const languageCodes = Object.values(this.languages ?? {});
if (i >= languageCodes.length || j >= languageCodes.length || i < 0 ||
j < 0) {
console.error(
'Can\'t swap languages because the indexes are out of the range.');
return;
}
- let tmp = languageCodes[j];
+ const tmp = languageCodes[j];
languageCodes[j] = languageCodes[i];
languageCodes[i] = tmp;
this.save(languageCodes);
}
}
-customElements.define('languages-editor', LanguagesEditor);
diff --git a/src/options/elements/options-editor/options-editor.js b/src/options/elements/options-editor/options-editor.ts
similarity index 67%
rename from src/options/elements/options-editor/options-editor.js
rename to src/options/elements/options-editor/options-editor.ts
index a26072e..2d0422e 100644
--- a/src/options/elements/options-editor/options-editor.js
+++ b/src/options/elements/options-editor/options-editor.ts
@@ -1,16 +1,16 @@
+import './languages-editor';
+
import {css, html, LitElement} from 'lit';
+import {customElement, property} from 'lit/decorators.js';
import {map} from 'lit/directives/map.js';
import {msg} from '../../../common/i18n';
-import {TAB_OPTIONS} from '../../../common/options';
+import {OptionsV0, TAB_OPTIONS, TabOptionValue} from '../../../common/options';
import {SHARED_STYLES} from '../../shared/shared-styles';
-import LanguagesEditor from './languages-editor';
-
-export class OptionsEditor extends LitElement {
- static properties = {
- storageData: {type: Object},
- };
+@customElement('options-editor')
+export default class OptionsEditor extends LitElement {
+ @property({type: Object}) storageData: OptionsV0;
static get styles() {
return [
@@ -24,16 +24,11 @@
];
}
- constructor() {
- super();
- this.addEventListener('show-credits-dialog', this.showDialog);
- }
-
render() {
- let currentTabOption = this.storageData?.uniquetab;
+ const currentTabOption = this.storageData?.uniquetab;
- let otherOptions = map(TAB_OPTIONS, (option, i) => {
- let checked = option.value == currentTabOption ||
+ const otherOptions = map(TAB_OPTIONS, (option, i) => {
+ const checked = option.value == currentTabOption ||
option.deprecatedValues.includes(currentTabOption);
return html`
<p>
@@ -56,10 +51,9 @@
`;
}
- changeTabOption(value) {
+ changeTabOption(value: TabOptionValue) {
chrome.storage.sync.set({uniquetab: value}, function() {
chrome.runtime.sendMessage({action: 'clearTranslatorTab'});
});
}
}
-customElements.define('options-editor', OptionsEditor);
diff --git a/src/options/options.js b/src/options/options.ts
similarity index 76%
rename from src/options/options.js
rename to src/options/options.ts
index 2dd9dca..6cdcec9 100644
--- a/src/options/options.js
+++ b/src/options/options.ts
@@ -1,17 +1,21 @@
+import './elements/credits-dialog/credits-dialog';
+import './elements/options-editor/options-editor';
+
import {css, html, LitElement} from 'lit';
+import {customElement, property} from 'lit/decorators.js';
import {msg} from '../common/i18n';
-import Options from '../common/options';
+import {default as Options, OptionsV0} from '../common/options';
-import CreditsDialog from './elements/credits-dialog/credits-dialog';
-import OptionsEditor from './elements/options-editor/options-editor';
import {SHARED_STYLES} from './shared/shared-styles';
-let bodyStyles = document.createElement('style');
-// #!if browser_target == 'chromium'
-let widthProperty = 'width: 470px;';
-// #!else
-let widthProperty = '';
+const bodyStyles = document.createElement('style');
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore #!if browser_target == 'chromium'
+const widthProperty = 'width: 470px;';
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore #!else
+const widthProperty = '';
// #!endif
bodyStyles.textContent = `
body {
@@ -24,16 +28,15 @@
document.head.append(bodyStyles);
+@customElement('options-page')
export class OptionsPage extends LitElement {
- static properties = {
- _storageData: {type: Object, state: true},
- }
+ @property({type: Object, state: true}) _storageData: OptionsV0;
constructor() {
super();
this._storageData = undefined;
this.updateStorageData();
- chrome.storage.onChanged.addListener((changes, areaName) => {
+ chrome.storage.onChanged.addListener((_changes, areaName) => {
if (areaName == 'sync') this.updateStorageData();
});
}
@@ -105,4 +108,3 @@
this.renderRoot.querySelector('credits-dialog').dispatchEvent(e);
}
}
-customElements.define('options-page', OptionsPage);
diff --git a/src/options/shared/dialog-styles.js b/src/options/shared/dialog-styles.ts
similarity index 100%
rename from src/options/shared/dialog-styles.js
rename to src/options/shared/dialog-styles.ts
diff --git a/src/options/shared/shared-styles.js b/src/options/shared/shared-styles.ts
similarity index 100%
rename from src/options/shared/shared-styles.js
rename to src/options/shared/shared-styles.ts
diff --git a/src/options/tsCredits.ts b/src/options/tsCredits.ts
new file mode 100644
index 0000000..cbbcc4b
--- /dev/null
+++ b/src/options/tsCredits.ts
@@ -0,0 +1,26 @@
+import creditsRaw from './credits.json5';
+import i18nCreditsRaw from './i18n-credits.json5';
+
+interface Credit {
+ name: string;
+ url?: string;
+ author?: string;
+ license?: string;
+}
+
+type Credits = Credit[]
+
+interface CrowdinLang {
+ id: string;
+ name: string;
+}
+
+interface IntCredit {
+ name: string;
+ languages: CrowdinLang[];
+}
+
+type IntCredits = IntCredit[];
+
+export const credits: Credits = creditsRaw;
+export const i18nCredits: IntCredits = i18nCreditsRaw;