From 3089bb7908e42e9101241476f700033df82e685d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 24 May 2020 13:38:48 -0400 Subject: Settings refactor (#541) * Remove debug info * Trigger onOptionsUpdated instead of formWrite when profile changes * Update how Anki field changes are observed * Update how general.enableClipboardPopups setting is changed * Change where ankiTemplatesUpdateValue occurs * Change where onDictionaryOptionsChanged occurs * Remove unused global declarations * Remove stray data attribute --- ext/bg/settings.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'ext/bg/settings.html') diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 3ce91f12..7964ab90 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -138,7 +138,7 @@ -
+
@@ -854,7 +854,7 @@
  • Kanji
  • -
    +
    @@ -1111,8 +1111,6 @@

    -
    
    -
                 
    -- 
    cgit v1.2.3
    
    
    From 8537c8f386b7c04f21e62a6b82b179ec9a123ce1 Mon Sep 17 00:00:00 2001
    From: toasted-nutbread 
    Date: Fri, 29 May 2020 19:45:54 -0400
    Subject: Create class to abstract access, mutation, and events for settings
     (#565)
    
    ---
     ext/bg/js/settings/main.js                |  4 ++
     ext/bg/js/settings/settings-controller.js | 83 +++++++++++++++++++++++++++++++
     ext/bg/settings.html                      |  1 +
     3 files changed, 88 insertions(+)
     create mode 100644 ext/bg/js/settings/settings-controller.js
    
    (limited to 'ext/bg/settings.html')
    
    diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js
    index f96167af..0cb1734e 100644
    --- a/ext/bg/js/settings/main.js
    +++ b/ext/bg/js/settings/main.js
    @@ -17,6 +17,7 @@
     
     /* global
      * SettingsBackup
    + * SettingsController
      * ankiInitialize
      * ankiTemplatesInitialize
      * ankiTemplatesUpdateValue
    @@ -292,6 +293,9 @@ async function onReady() {
         api.forwardLogsToBackend();
         await yomichan.prepare();
     
    +    const settingsController = new SettingsController();
    +    settingsController.prepare();
    +
         showExtensionInformation();
     
         await settingsPopulateModifierKeys();
    diff --git a/ext/bg/js/settings/settings-controller.js b/ext/bg/js/settings/settings-controller.js
    new file mode 100644
    index 00000000..61230226
    --- /dev/null
    +++ b/ext/bg/js/settings/settings-controller.js
    @@ -0,0 +1,83 @@
    +/*
    + * Copyright (C) 2020  Yomichan Authors
    + *
    + * This program is free software: you can redistribute it and/or modify
    + * it under the terms of the GNU General Public License as published by
    + * the Free Software Foundation, either version 3 of the License, or
    + * (at your option) any later version.
    + *
    + * This program is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    + * GNU General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with this program.  If not, see .
    + */
    +
    +/* global
    + * api
    + * utilBackend
    + * utilBackgroundIsolate
    + */
    +
    +class SettingsController extends EventDispatcher {
    +    constructor(profileIndex=0) {
    +        super();
    +        this._profileIndex = profileIndex;
    +        this._source = yomichan.generateId(16);
    +    }
    +
    +    get profileIndex() {
    +        return this._profileIndex;
    +    }
    +
    +    set profileIndex(value) {
    +        if (this._profileIndex === value) { return; }
    +        this._profileIndex = value;
    +        this.trigger('optionsContextChanged');
    +        this._onOptionsUpdatedInternal();
    +    }
    +
    +    prepare() {
    +        yomichan.on('optionsUpdated', this._onOptionsUpdated.bind(this));
    +    }
    +
    +    async save() {
    +        await api.optionsSave(this._source);
    +    }
    +
    +    async getOptions() {
    +        const optionsContext = this.getOptionsContext();
    +        return await api.optionsGet(optionsContext);
    +    }
    +
    +    async getOptionsFull() {
    +        return await api.optionsGetFull();
    +    }
    +
    +    async getOptionsMutable() {
    +        const optionsContext = this.getOptionsContext();
    +        return utilBackend().getOptions(utilBackgroundIsolate(optionsContext));
    +    }
    +
    +    async getOptionsFullMutable() {
    +        return utilBackend().getFullOptions();
    +    }
    +
    +    getOptionsContext() {
    +        return {index: this._profileIndex};
    +    }
    +
    +    // Private
    +
    +    _onOptionsUpdated({source}) {
    +        if (source === this._source) { return; }
    +        this._onOptionsUpdatedInternal();
    +    }
    +
    +    async _onOptionsUpdatedInternal() {
    +        const options = await this.getOptions();
    +        this.trigger('optionsChanged', {options});
    +    }
    +}
    diff --git a/ext/bg/settings.html b/ext/bg/settings.html
    index 7964ab90..c8f5b15c 100644
    --- a/ext/bg/settings.html
    +++ b/ext/bg/settings.html
    @@ -1147,6 +1147,7 @@
             
             
             
    +        
             
     
             
    -- 
    cgit v1.2.3
    
    
    From c62f980f37007743bed004ff43a82a8d7664dac6 Mon Sep 17 00:00:00 2001
    From: toasted-nutbread 
    Date: Fri, 29 May 2020 19:56:38 -0400
    Subject: Audio controller (#569)
    
    * Convert audio.js into a class
    
    * Move audio-ui.js classes into audio.js
    
    * Rename fields
    
    * Merge classes
    
    * Remove audio-ui.js
    ---
     ext/bg/js/settings/audio-ui.js | 139 ----------------------
     ext/bg/js/settings/audio.js    | 255 ++++++++++++++++++++++++++++-------------
     ext/bg/js/settings/main.js     |   4 +-
     ext/bg/settings.html           |   1 -
     4 files changed, 178 insertions(+), 221 deletions(-)
     delete mode 100644 ext/bg/js/settings/audio-ui.js
    
    (limited to 'ext/bg/settings.html')
    
    diff --git a/ext/bg/js/settings/audio-ui.js b/ext/bg/js/settings/audio-ui.js
    deleted file mode 100644
    index 73c64227..00000000
    --- a/ext/bg/js/settings/audio-ui.js
    +++ /dev/null
    @@ -1,139 +0,0 @@
    -/*
    - * Copyright (C) 2019-2020  Yomichan Authors
    - *
    - * This program is free software: you can redistribute it and/or modify
    - * it under the terms of the GNU General Public License as published by
    - * the Free Software Foundation, either version 3 of the License, or
    - * (at your option) any later version.
    - *
    - * This program is distributed in the hope that it will be useful,
    - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    - * GNU General Public License for more details.
    - *
    - * You should have received a copy of the GNU General Public License
    - * along with this program.  If not, see .
    - */
    -
    -class AudioSourceUI {
    -    static instantiateTemplate(templateSelector) {
    -        const template = document.querySelector(templateSelector);
    -        const content = document.importNode(template.content, true);
    -        return content.firstChild;
    -    }
    -}
    -
    -AudioSourceUI.Container = class Container {
    -    constructor(audioSources, container, addButton) {
    -        this.audioSources = audioSources;
    -        this.container = container;
    -        this.addButton = addButton;
    -        this.children = [];
    -
    -        this.container.textContent = '';
    -
    -        for (const audioSource of toIterable(audioSources)) {
    -            this.children.push(new AudioSourceUI.AudioSource(this, audioSource, this.children.length));
    -        }
    -
    -        this._clickListener = this.onAddAudioSource.bind(this);
    -        this.addButton.addEventListener('click', this._clickListener, false);
    -    }
    -
    -    cleanup() {
    -        for (const child of this.children) {
    -            child.cleanup();
    -        }
    -
    -        this.addButton.removeEventListener('click', this._clickListener, false);
    -        this.container.textContent = '';
    -        this._clickListener = null;
    -    }
    -
    -    save() {
    -        // Override
    -    }
    -
    -    remove(child) {
    -        const index = this.children.indexOf(child);
    -        if (index < 0) {
    -            return;
    -        }
    -
    -        child.cleanup();
    -        this.children.splice(index, 1);
    -        this.audioSources.splice(index, 1);
    -
    -        for (let i = index; i < this.children.length; ++i) {
    -            this.children[i].index = i;
    -        }
    -    }
    -
    -    onAddAudioSource() {
    -        const audioSource = this.getUnusedAudioSource();
    -        this.audioSources.push(audioSource);
    -        this.save();
    -        this.children.push(new AudioSourceUI.AudioSource(this, audioSource, this.children.length));
    -    }
    -
    -    getUnusedAudioSource() {
    -        const audioSourcesAvailable = [
    -            'jpod101',
    -            'jpod101-alternate',
    -            'jisho',
    -            'custom'
    -        ];
    -        for (const source of audioSourcesAvailable) {
    -            if (this.audioSources.indexOf(source) < 0) {
    -                return source;
    -            }
    -        }
    -        return audioSourcesAvailable[0];
    -    }
    -};
    -
    -AudioSourceUI.AudioSource = class AudioSource {
    -    constructor(parent, audioSource, index) {
    -        this.parent = parent;
    -        this.audioSource = audioSource;
    -        this.index = index;
    -
    -        this.container = AudioSourceUI.instantiateTemplate('#audio-source-template');
    -        this.select = this.container.querySelector('.audio-source-select');
    -        this.removeButton = this.container.querySelector('.audio-source-remove');
    -
    -        this.select.value = audioSource;
    -
    -        this._selectChangeListener = this.onSelectChanged.bind(this);
    -        this._removeClickListener = this.onRemoveClicked.bind(this);
    -
    -        this.select.addEventListener('change', this._selectChangeListener, false);
    -        this.removeButton.addEventListener('click', this._removeClickListener, false);
    -
    -        parent.container.appendChild(this.container);
    -    }
    -
    -    cleanup() {
    -        this.select.removeEventListener('change', this._selectChangeListener, false);
    -        this.removeButton.removeEventListener('click', this._removeClickListener, false);
    -
    -        if (this.container.parentNode !== null) {
    -            this.container.parentNode.removeChild(this.container);
    -        }
    -    }
    -
    -    save() {
    -        this.parent.save();
    -    }
    -
    -    onSelectChanged() {
    -        this.audioSource = this.select.value;
    -        this.parent.audioSources[this.index] = this.audioSource;
    -        this.save();
    -    }
    -
    -    onRemoveClicked() {
    -        this.parent.remove(this);
    -        this.save();
    -    }
    -};
    diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js
    index ac2d82f3..5c1cb131 100644
    --- a/ext/bg/js/settings/audio.js
    +++ b/ext/bg/js/settings/audio.js
    @@ -16,110 +16,207 @@
      */
     
     /* global
    - * AudioSourceUI
      * AudioSystem
      * getOptionsContext
      * getOptionsMutable
      * settingsSaveOptions
      */
     
    -let audioSourceUI = null;
    -let audioSystem = null;
    -
    -async function audioSettingsInitialize() {
    -    audioSystem = new AudioSystem({
    -        audioUriBuilder: null,
    -        useCache: true
    -    });
    -
    -    const optionsContext = getOptionsContext();
    -    const options = await getOptionsMutable(optionsContext);
    -    audioSourceUI = new AudioSourceUI.Container(
    -        options.audio.sources,
    -        document.querySelector('.audio-source-list'),
    -        document.querySelector('.audio-source-add')
    -    );
    -    audioSourceUI.save = settingsSaveOptions;
    -
    -    textToSpeechInitialize();
    -}
    +class AudioController {
    +    constructor() {
    +        this._audioSystem = null;
    +        this._settingsAudioSources = null;
    +        this._audioSourceContainer = null;
    +        this._audioSourceAddButton = null;
    +        this._audioSourceEntries = [];
    +    }
     
    -function textToSpeechInitialize() {
    -    if (typeof speechSynthesis === 'undefined') { return; }
    +    async prepare() {
    +        this._audioSystem = new AudioSystem({
    +            audioUriBuilder: null,
    +            useCache: true
    +        });
     
    -    speechSynthesis.addEventListener('voiceschanged', updateTextToSpeechVoices, false);
    -    updateTextToSpeechVoices();
    +        const optionsContext = getOptionsContext();
    +        const options = await getOptionsMutable(optionsContext);
     
    -    document.querySelector('#text-to-speech-voice').addEventListener('change', onTextToSpeechVoiceChange, false);
    -    document.querySelector('#text-to-speech-voice-test').addEventListener('click', textToSpeechTest, false);
    -}
    +        this._settingsAudioSources = options.audio.sources;
    +        this._audioSourceContainer = document.querySelector('.audio-source-list');
    +        this._audioSourceAddButton = document.querySelector('.audio-source-add');
    +        this._audioSourceContainer.textContent = '';
    +
    +        this._audioSourceAddButton.addEventListener('click', this._onAddAudioSource.bind(this), false);
    +
    +        for (const audioSource of toIterable(this._settingsAudioSources)) {
    +            this._createAudioSourceEntry(audioSource);
    +        }
     
    -function updateTextToSpeechVoices() {
    -    const voices = Array.prototype.map.call(speechSynthesis.getVoices(), (voice, index) => ({voice, index}));
    -    voices.sort(textToSpeechVoiceCompare);
    +        this._prepareTextToSpeech();
    +    }
    +
    +    // Private
     
    -    document.querySelector('#text-to-speech-voice-container').hidden = (voices.length === 0);
    +    async _save() {
    +        await settingsSaveOptions();
    +    }
     
    -    const fragment = document.createDocumentFragment();
    +    _prepareTextToSpeech() {
    +        if (typeof speechSynthesis === 'undefined') { return; }
     
    -    let option = document.createElement('option');
    -    option.value = '';
    -    option.textContent = 'None';
    -    fragment.appendChild(option);
    +        speechSynthesis.addEventListener('voiceschanged', this._updateTextToSpeechVoices.bind(this), false);
    +        this._updateTextToSpeechVoices();
    +
    +        document.querySelector('#text-to-speech-voice').addEventListener('change', this._onTextToSpeechVoiceChange.bind(this), false);
    +        document.querySelector('#text-to-speech-voice-test').addEventListener('click', this._testTextToSpeech.bind(this), false);
    +    }
     
    -    for (const {voice} of voices) {
    -        option = document.createElement('option');
    -        option.value = voice.voiceURI;
    -        option.textContent = `${voice.name} (${voice.lang})`;
    +    _updateTextToSpeechVoices() {
    +        const voices = Array.prototype.map.call(speechSynthesis.getVoices(), (voice, index) => ({voice, index}));
    +        voices.sort(this._textToSpeechVoiceCompare.bind(this));
    +
    +        document.querySelector('#text-to-speech-voice-container').hidden = (voices.length === 0);
    +
    +        const fragment = document.createDocumentFragment();
    +
    +        let option = document.createElement('option');
    +        option.value = '';
    +        option.textContent = 'None';
             fragment.appendChild(option);
    +
    +        for (const {voice} of voices) {
    +            option = document.createElement('option');
    +            option.value = voice.voiceURI;
    +            option.textContent = `${voice.name} (${voice.lang})`;
    +            fragment.appendChild(option);
    +        }
    +
    +        const select = document.querySelector('#text-to-speech-voice');
    +        select.textContent = '';
    +        select.appendChild(fragment);
    +        select.value = select.dataset.value;
         }
     
    -    const select = document.querySelector('#text-to-speech-voice');
    -    select.textContent = '';
    -    select.appendChild(fragment);
    -    select.value = select.dataset.value;
    -}
    +    _textToSpeechVoiceCompare(a, b) {
    +        const aIsJapanese = this._languageTagIsJapanese(a.voice.lang);
    +        const bIsJapanese = this._languageTagIsJapanese(b.voice.lang);
    +        if (aIsJapanese) {
    +            if (!bIsJapanese) { return -1; }
    +        } else {
    +            if (bIsJapanese) { return 1; }
    +        }
    +
    +        const aIsDefault = a.voice.default;
    +        const bIsDefault = b.voice.default;
    +        if (aIsDefault) {
    +            if (!bIsDefault) { return -1; }
    +        } else {
    +            if (bIsDefault) { return 1; }
    +        }
    +
    +        return a.index - b.index;
    +    }
     
    -function languageTagIsJapanese(languageTag) {
    -    return (
    -        languageTag.startsWith('ja-') ||
    -        languageTag.startsWith('jpn-')
    -    );
    -}
    +    _languageTagIsJapanese(languageTag) {
    +        return (
    +            languageTag.startsWith('ja-') ||
    +            languageTag.startsWith('jpn-')
    +        );
    +    }
     
    -function textToSpeechVoiceCompare(a, b) {
    -    const aIsJapanese = languageTagIsJapanese(a.voice.lang);
    -    const bIsJapanese = languageTagIsJapanese(b.voice.lang);
    -    if (aIsJapanese) {
    -        if (!bIsJapanese) { return -1; }
    -    } else {
    -        if (bIsJapanese) { return 1; }
    +    _testTextToSpeech() {
    +        try {
    +            const text = document.querySelector('#text-to-speech-voice-test').dataset.speechText || '';
    +            const voiceUri = document.querySelector('#text-to-speech-voice').value;
    +
    +            const audio = this._audioSystem.createTextToSpeechAudio(text, voiceUri);
    +            audio.volume = 1.0;
    +            audio.play();
    +        } catch (e) {
    +            // NOP
    +        }
         }
     
    -    const aIsDefault = a.voice.default;
    -    const bIsDefault = b.voice.default;
    -    if (aIsDefault) {
    -        if (!bIsDefault) { return -1; }
    -    } else {
    -        if (bIsDefault) { return 1; }
    +    _instantiateTemplate(templateSelector) {
    +        const template = document.querySelector(templateSelector);
    +        const content = document.importNode(template.content, true);
    +        return content.firstChild;
         }
     
    -    return a.index - b.index;
    -}
    +    _getUnusedAudioSource() {
    +        const audioSourcesAvailable = [
    +            'jpod101',
    +            'jpod101-alternate',
    +            'jisho',
    +            'custom'
    +        ];
    +        for (const source of audioSourcesAvailable) {
    +            if (this._settingsAudioSources.indexOf(source) < 0) {
    +                return source;
    +            }
    +        }
    +        return audioSourcesAvailable[0];
    +    }
    +
    +    _createAudioSourceEntry(value) {
    +        const eventListeners = new EventListenerCollection();
    +        const container = this._instantiateTemplate('#audio-source-template');
    +        const select = container.querySelector('.audio-source-select');
    +        const removeButton = container.querySelector('.audio-source-remove');
    +
    +        select.value = value;
     
    -function textToSpeechTest() {
    -    try {
    -        const text = document.querySelector('#text-to-speech-voice-test').dataset.speechText || '';
    -        const voiceUri = document.querySelector('#text-to-speech-voice').value;
    +        const entry = {
    +            container,
    +            eventListeners
    +        };
     
    -        const audio = audioSystem.createTextToSpeechAudio(text, voiceUri);
    -        audio.volume = 1.0;
    -        audio.play();
    -    } catch (e) {
    -        // NOP
    +        eventListeners.addEventListener(select, 'change', this._onAudioSourceSelectChange.bind(this, entry), false);
    +        eventListeners.addEventListener(removeButton, 'click', this._onAudioSourceRemoveClicked.bind(this, entry), false);
    +
    +        this._audioSourceContainer.appendChild(container);
    +        this._audioSourceEntries.push(entry);
    +    }
    +
    +    _removeAudioSourceEntry(entry) {
    +        const index = this._audioSourceEntries.indexOf(entry);
    +        if (index < 0) { return; }
    +
    +        const {container, eventListeners} = entry;
    +        if (container.parentNode !== null) {
    +            container.parentNode.removeChild(container);
    +        }
    +        eventListeners.removeAllEventListeners();
    +
    +        this._audioSourceEntries.splice(index, 1);
    +        this._settingsAudioSources.splice(index, 1);
    +
    +        for (let i = index, ii = this._audioSourceEntries.length; i < ii; ++i) {
    +            this._audioSourceEntries[i].index = i;
    +        }
    +    }
    +
    +    _onTextToSpeechVoiceChange(e) {
    +        e.currentTarget.dataset.value = e.currentTarget.value;
    +    }
    +
    +    _onAddAudioSource() {
    +        const audioSource = this._getUnusedAudioSource();
    +        this._settingsAudioSources.push(audioSource);
    +        this._createAudioSourceEntry(audioSource);
    +        this._save();
         }
    -}
     
    -function onTextToSpeechVoiceChange(e) {
    -    e.currentTarget.dataset.value = e.currentTarget.value;
    +    _onAudioSourceSelectChange(entry, event) {
    +        const index = this._audioSourceEntries.indexOf(entry);
    +        if (index < 0) { return; }
    +
    +        const value = event.currentTarget.value;
    +        this._settingsAudioSources[index] = value;
    +        this._save();
    +    }
    +
    +    _onAudioSourceRemoveClicked(entry) {
    +        this._removeAudioSourceEntry(entry);
    +        this._save();
    +    }
     }
    diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js
    index 872f8f05..dddaef6c 100644
    --- a/ext/bg/js/settings/main.js
    +++ b/ext/bg/js/settings/main.js
    @@ -18,12 +18,12 @@
     /* global
      * AnkiController
      * AnkiTemplatesController
    + * AudioController
      * ProfileController
      * SettingsBackup
      * SettingsController
      * api
      * appearanceInitialize
    - * audioSettingsInitialize
      * dictSettingsInitialize
      * onDictionaryOptionsChanged
      * storageInfoInitialize
    @@ -319,7 +319,7 @@ async function onReady() {
         await settingsPopulateModifierKeys();
         formSetupEventListeners();
         appearanceInitialize();
    -    await audioSettingsInitialize();
    +    new AudioController().prepare();
         await (new ProfileController()).prepare();
         await dictSettingsInitialize();
         ankiController = new AnkiController();
    diff --git a/ext/bg/settings.html b/ext/bg/settings.html
    index c8f5b15c..5c7fde41 100644
    --- a/ext/bg/settings.html
    +++ b/ext/bg/settings.html
    @@ -1141,7 +1141,6 @@
             
             
             
    -        
             
             
             
    -- 
    cgit v1.2.3
    
    
    From 418e8a57bf7ea1def3e7b83270742d466e98e8cf Mon Sep 17 00:00:00 2001
    From: toasted-nutbread 
    Date: Fri, 29 May 2020 20:25:22 -0400
    Subject: Convert dictionaries.js and storage.js to classes (#570)
    
    * Convert dictionaries.js to a class
    
    * Remove storage spinner
    
    * Convert storage.js to a class
    
    * Move dataset assignments into main.js
    ---
     ext/bg/css/settings.css            |   2 +-
     ext/bg/js/settings/dictionaries.js | 544 ++++++++++++++++++-------------------
     ext/bg/js/settings/main.js         |  25 +-
     ext/bg/js/settings/storage.js      | 199 +++++++-------
     ext/bg/settings.html               |   1 -
     5 files changed, 384 insertions(+), 387 deletions(-)
    
    (limited to 'ext/bg/settings.html')
    
    diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css
    index f55082e7..eb11d77e 100644
    --- a/ext/bg/css/settings.css
    +++ b/ext/bg/css/settings.css
    @@ -18,7 +18,7 @@
     
     #anki-spinner,
     #dict-spinner, #dict-import-progress,
    -.storage-hidden, #storage-spinner {
    +.storage-hidden {
         display: none;
     }
     
    diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js
    index 4d307f0f..dd6dd1c1 100644
    --- a/ext/bg/js/settings/dictionaries.js
    +++ b/ext/bg/js/settings/dictionaries.js
    @@ -22,14 +22,9 @@
      * getOptionsFullMutable
      * getOptionsMutable
      * settingsSaveOptions
    - * storageEstimate
    - * storageUpdateStats
      * utilBackgroundIsolate
      */
     
    -let dictionaryUI = null;
    -
    -
     class SettingsDictionaryListUI {
         constructor(container, template, extraContainer, extraTemplate) {
             this.container = container;
    @@ -308,13 +303,13 @@ class SettingsDictionaryEntryUI {
     
                 await api.deleteDictionary(this.dictionaryInfo.title, onProgress);
             } catch (e) {
    -            dictionaryErrorsShow([e]);
    +            this.dictionaryErrorsShow([e]);
             } finally {
                 prevention.end();
                 this.isDeleting = false;
                 progress.hidden = true;
     
    -            onDatabaseUpdated();
    +            this.onDatabaseUpdated();
             }
         }
     
    @@ -388,340 +383,341 @@ class SettingsDictionaryExtraUI {
         }
     }
     
    +class DictionaryController {
    +    constructor(storageController) {
    +        this._storageController = storageController;
    +        this._dictionaryUI = null;
    +        this._dictionaryErrorToStringOverrides = [
    +            [
    +                'A mutation operation was attempted on a database that did not allow mutations.',
    +                'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.'
    +            ],
    +            [
    +                'The operation failed for reasons unrelated to the database itself and not covered by any other error code.',
    +                'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.'
    +            ],
    +            [
    +                'BulkError',
    +                'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.'
    +            ]
    +        ];
    +    }
     
    -async function dictSettingsInitialize() {
    -    dictionaryUI = new SettingsDictionaryListUI(
    -        document.querySelector('#dict-groups'),
    -        document.querySelector('#dict-template'),
    -        document.querySelector('#dict-groups-extra'),
    -        document.querySelector('#dict-extra-template')
    -    );
    -    dictionaryUI.save = settingsSaveOptions;
    -
    -    document.querySelector('#dict-purge-button').addEventListener('click', onDictionaryPurgeButtonClick, false);
    -    document.querySelector('#dict-purge-confirm').addEventListener('click', onDictionaryPurge, false);
    -    document.querySelector('#dict-file-button').addEventListener('click', onDictionaryImportButtonClick, false);
    -    document.querySelector('#dict-file').addEventListener('change', onDictionaryImport, false);
    -    document.querySelector('#dict-main').addEventListener('change', onDictionaryMainChanged, false);
    -    document.querySelector('#database-enable-prefix-wildcard-searches').addEventListener('change', onDatabaseEnablePrefixWildcardSearchesChanged, false);
    -
    -    await onDictionaryOptionsChanged();
    -    await onDatabaseUpdated();
    -}
    -
    -async function onDictionaryOptionsChanged() {
    -    if (dictionaryUI === null) { return; }
    -
    -    const optionsContext = getOptionsContext();
    -    const options = await getOptionsMutable(optionsContext);
    +    async prepare() {
    +        this._dictionaryUI = new SettingsDictionaryListUI(
    +            document.querySelector('#dict-groups'),
    +            document.querySelector('#dict-template'),
    +            document.querySelector('#dict-groups-extra'),
    +            document.querySelector('#dict-extra-template')
    +        );
    +        this._dictionaryUI.save = settingsSaveOptions;
     
    -    dictionaryUI.setOptionsDictionaries(options.dictionaries);
    +        document.querySelector('#dict-purge-button').addEventListener('click', this._onPurgeButtonClick.bind(this), false);
    +        document.querySelector('#dict-purge-confirm').addEventListener('click', this._onPurgeConfirmButtonClick.bind(this), false);
    +        document.querySelector('#dict-file-button').addEventListener('click', this._onImportButtonClick.bind(this), false);
    +        document.querySelector('#dict-file').addEventListener('change', this._onImportFileChange.bind(this), false);
    +        document.querySelector('#dict-main').addEventListener('change', this._onDictionaryMainChanged.bind(this), false);
    +        document.querySelector('#database-enable-prefix-wildcard-searches').addEventListener('change', this._onDatabaseEnablePrefixWildcardSearchesChanged.bind(this), false);
     
    -    const optionsFull = await api.optionsGetFull();
    -    document.querySelector('#database-enable-prefix-wildcard-searches').checked = optionsFull.global.database.prefixWildcardsSupported;
    +        await this.optionsChanged();
    +        await this._onDatabaseUpdated();
    +    }
     
    -    await updateMainDictionarySelectValue();
    -}
    +    async optionsChanged() {
    +        if (this._dictionaryUI === null) { return; }
     
    -async function onDatabaseUpdated() {
    -    try {
    -        const dictionaries = await api.getDictionaryInfo();
    -        dictionaryUI.setDictionaries(dictionaries);
    +        const optionsContext = getOptionsContext();
    +        const options = await getOptionsMutable(optionsContext);
     
    -        document.querySelector('#dict-warning').hidden = (dictionaries.length > 0);
    +        this._dictionaryUI.setOptionsDictionaries(options.dictionaries);
     
    -        updateMainDictionarySelectOptions(dictionaries);
    -        await updateMainDictionarySelectValue();
    +        const optionsFull = await api.optionsGetFull();
    +        document.querySelector('#database-enable-prefix-wildcard-searches').checked = optionsFull.global.database.prefixWildcardsSupported;
     
    -        const {counts, total} = await api.getDictionaryCounts(dictionaries.map((v) => v.title), true);
    -        dictionaryUI.setCounts(counts, total);
    -    } catch (e) {
    -        dictionaryErrorsShow([e]);
    +        await this._updateMainDictionarySelectValue();
         }
    -}
    -
    -function updateMainDictionarySelectOptions(dictionaries) {
    -    const select = document.querySelector('#dict-main');
    -    select.textContent = ''; // Empty
     
    -    let option = document.createElement('option');
    -    option.className = 'text-muted';
    -    option.value = '';
    -    option.textContent = 'Not selected';
    -    select.appendChild(option);
    +    // Private
     
    -    for (const {title, sequenced} of toIterable(dictionaries)) {
    -        if (!sequenced) { continue; }
    +    _updateMainDictionarySelectOptions(dictionaries) {
    +        const select = document.querySelector('#dict-main');
    +        select.textContent = ''; // Empty
     
    -        option = document.createElement('option');
    -        option.value = title;
    -        option.textContent = title;
    +        let option = document.createElement('option');
    +        option.className = 'text-muted';
    +        option.value = '';
    +        option.textContent = 'Not selected';
             select.appendChild(option);
    -    }
    -}
    -
    -async function updateMainDictionarySelectValue() {
    -    const optionsContext = getOptionsContext();
    -    const options = await api.optionsGet(optionsContext);
     
    -    const value = options.general.mainDictionary;
    +        for (const {title, sequenced} of toIterable(dictionaries)) {
    +            if (!sequenced) { continue; }
     
    -    const select = document.querySelector('#dict-main');
    -    let selectValue = null;
    -    for (const child of select.children) {
    -        if (child.nodeName.toUpperCase() === 'OPTION' && child.value === value) {
    -            selectValue = value;
    -            break;
    +            option = document.createElement('option');
    +            option.value = title;
    +            option.textContent = title;
    +            select.appendChild(option);
             }
         }
     
    -    let missingNodeOption = select.querySelector('option[data-not-installed=true]');
    -    if (selectValue === null) {
    -        if (missingNodeOption === null) {
    -            missingNodeOption = document.createElement('option');
    -            missingNodeOption.className = 'text-muted';
    -            missingNodeOption.value = value;
    -            missingNodeOption.textContent = `${value} (Not installed)`;
    -            missingNodeOption.dataset.notInstalled = 'true';
    -            select.appendChild(missingNodeOption);
    +    async _updateMainDictionarySelectValue() {
    +        const optionsContext = getOptionsContext();
    +        const options = await api.optionsGet(optionsContext);
    +
    +        const value = options.general.mainDictionary;
    +
    +        const select = document.querySelector('#dict-main');
    +        let selectValue = null;
    +        for (const child of select.children) {
    +            if (child.nodeName.toUpperCase() === 'OPTION' && child.value === value) {
    +                selectValue = value;
    +                break;
    +            }
             }
    -    } else {
    -        if (missingNodeOption !== null) {
    -            missingNodeOption.parentNode.removeChild(missingNodeOption);
    +
    +        let missingNodeOption = select.querySelector('option[data-not-installed=true]');
    +        if (selectValue === null) {
    +            if (missingNodeOption === null) {
    +                missingNodeOption = document.createElement('option');
    +                missingNodeOption.className = 'text-muted';
    +                missingNodeOption.value = value;
    +                missingNodeOption.textContent = `${value} (Not installed)`;
    +                missingNodeOption.dataset.notInstalled = 'true';
    +                select.appendChild(missingNodeOption);
    +            }
    +        } else {
    +            if (missingNodeOption !== null) {
    +                missingNodeOption.parentNode.removeChild(missingNodeOption);
    +            }
             }
    +
    +        select.value = value;
         }
     
    -    select.value = value;
    -}
    +    _dictionaryErrorToString(error) {
    +        if (error.toString) {
    +            error = error.toString();
    +        } else {
    +            error = `${error}`;
    +        }
     
    -async function onDictionaryMainChanged(e) {
    -    const select = e.target;
    -    const value = select.value;
    +        for (const [match, subst] of this._dictionaryErrorToStringOverrides) {
    +            if (error.includes(match)) {
    +                error = subst;
    +                break;
    +            }
    +        }
     
    -    const missingNodeOption = select.querySelector('option[data-not-installed=true]');
    -    if (missingNodeOption !== null && missingNodeOption.value !== value) {
    -        missingNodeOption.parentNode.removeChild(missingNodeOption);
    +        return error;
         }
     
    -    const optionsContext = getOptionsContext();
    -    const options = await getOptionsMutable(optionsContext);
    -    options.general.mainDictionary = value;
    -    await settingsSaveOptions();
    -}
    +    _dictionaryErrorsShow(errors) {
    +        const dialog = document.querySelector('#dict-error');
    +        dialog.textContent = '';
     
    +        if (errors !== null && errors.length > 0) {
    +            const uniqueErrors = new Map();
    +            for (let e of errors) {
    +                yomichan.logError(e);
    +                e = this._dictionaryErrorToString(e);
    +                let count = uniqueErrors.get(e);
    +                if (typeof count === 'undefined') {
    +                    count = 0;
    +                }
    +                uniqueErrors.set(e, count + 1);
    +            }
     
    -function dictionaryErrorToString(error) {
    -    if (error.toString) {
    -        error = error.toString();
    -    } else {
    -        error = `${error}`;
    -    }
    +            for (const [e, count] of uniqueErrors.entries()) {
    +                const div = document.createElement('p');
    +                if (count > 1) {
    +                    div.textContent = `${e} `;
    +                    const em = document.createElement('em');
    +                    em.textContent = `(${count})`;
    +                    div.appendChild(em);
    +                } else {
    +                    div.textContent = `${e}`;
    +                }
    +                dialog.appendChild(div);
    +            }
     
    -    for (const [match, subst] of dictionaryErrorToString.overrides) {
    -        if (error.includes(match)) {
    -            error = subst;
    -            break;
    +            dialog.hidden = false;
    +        } else {
    +            dialog.hidden = true;
             }
         }
     
    -    return error;
    -}
    -dictionaryErrorToString.overrides = [
    -    [
    -        'A mutation operation was attempted on a database that did not allow mutations.',
    -        'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.'
    -    ],
    -    [
    -        'The operation failed for reasons unrelated to the database itself and not covered by any other error code.',
    -        'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.'
    -    ],
    -    [
    -        'BulkError',
    -        'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.'
    -    ]
    -];
    -
    -function dictionaryErrorsShow(errors) {
    -    const dialog = document.querySelector('#dict-error');
    -    dialog.textContent = '';
    -
    -    if (errors !== null && errors.length > 0) {
    -        const uniqueErrors = new Map();
    -        for (let e of errors) {
    -            yomichan.logError(e);
    -            e = dictionaryErrorToString(e);
    -            let count = uniqueErrors.get(e);
    -            if (typeof count === 'undefined') {
    -                count = 0;
    -            }
    -            uniqueErrors.set(e, count + 1);
    -        }
    -
    -        for (const [e, count] of uniqueErrors.entries()) {
    -            const div = document.createElement('p');
    -            if (count > 1) {
    -                div.textContent = `${e} `;
    -                const em = document.createElement('em');
    -                em.textContent = `(${count})`;
    -                div.appendChild(em);
    -            } else {
    -                div.textContent = `${e}`;
    -            }
    -            dialog.appendChild(div);
    +    _dictionarySpinnerShow(show) {
    +        const spinner = $('#dict-spinner');
    +        if (show) {
    +            spinner.show();
    +        } else {
    +            spinner.hide();
             }
    +    }
     
    -        dialog.hidden = false;
    -    } else {
    -        dialog.hidden = true;
    +    _dictReadFile(file) {
    +        return new Promise((resolve, reject) => {
    +            const reader = new FileReader();
    +            reader.onload = () => resolve(reader.result);
    +            reader.onerror = () => reject(reader.error);
    +            reader.readAsBinaryString(file);
    +        });
    +    }
    +
    +    async _onDatabaseUpdated() {
    +        try {
    +            const dictionaries = await api.getDictionaryInfo();
    +            this._dictionaryUI.setDictionaries(dictionaries);
    +
    +            document.querySelector('#dict-warning').hidden = (dictionaries.length > 0);
    +
    +            this._updateMainDictionarySelectOptions(dictionaries);
    +            await this._updateMainDictionarySelectValue();
    +
    +            const {counts, total} = await api.getDictionaryCounts(dictionaries.map((v) => v.title), true);
    +            this._dictionaryUI.setCounts(counts, total);
    +        } catch (e) {
    +            this._dictionaryErrorsShow([e]);
    +        }
         }
    -}
     
    +    async _onDictionaryMainChanged(e) {
    +        const select = e.target;
    +        const value = select.value;
    +
    +        const missingNodeOption = select.querySelector('option[data-not-installed=true]');
    +        if (missingNodeOption !== null && missingNodeOption.value !== value) {
    +            missingNodeOption.parentNode.removeChild(missingNodeOption);
    +        }
     
    -function dictionarySpinnerShow(show) {
    -    const spinner = $('#dict-spinner');
    -    if (show) {
    -        spinner.show();
    -    } else {
    -        spinner.hide();
    +        const optionsContext = getOptionsContext();
    +        const options = await getOptionsMutable(optionsContext);
    +        options.general.mainDictionary = value;
    +        await settingsSaveOptions();
         }
    -}
     
    -function onDictionaryImportButtonClick() {
    -    const dictFile = document.querySelector('#dict-file');
    -    dictFile.click();
    -}
    +    _onImportButtonClick() {
    +        const dictFile = document.querySelector('#dict-file');
    +        dictFile.click();
    +    }
     
    -function onDictionaryPurgeButtonClick(e) {
    -    e.preventDefault();
    -    $('#dict-purge-modal').modal('show');
    -}
    +    _onPurgeButtonClick(e) {
    +        e.preventDefault();
    +        $('#dict-purge-modal').modal('show');
    +    }
     
    -async function onDictionaryPurge(e) {
    -    e.preventDefault();
    +    async _onPurgeConfirmButtonClick(e) {
    +        e.preventDefault();
     
    -    $('#dict-purge-modal').modal('hide');
    +        $('#dict-purge-modal').modal('hide');
     
    -    const dictControls = $('#dict-importer, #dict-groups, #dict-groups-extra, #dict-main-group').hide();
    -    const dictProgress = document.querySelector('#dict-purge');
    -    dictProgress.hidden = false;
    +        const dictControls = $('#dict-importer, #dict-groups, #dict-groups-extra, #dict-main-group').hide();
    +        const dictProgress = document.querySelector('#dict-purge');
    +        dictProgress.hidden = false;
     
    -    const prevention = new PageExitPrevention();
    +        const prevention = new PageExitPrevention();
     
    -    try {
    -        prevention.start();
    -        dictionaryErrorsShow(null);
    -        dictionarySpinnerShow(true);
    +        try {
    +            prevention.start();
    +            this._dictionaryErrorsShow(null);
    +            this._dictionarySpinnerShow(true);
     
    -        await api.purgeDatabase();
    -        for (const {options} of toIterable((await getOptionsFullMutable()).profiles)) {
    -            options.dictionaries = utilBackgroundIsolate({});
    -            options.general.mainDictionary = '';
    -        }
    -        await settingsSaveOptions();
    +            await api.purgeDatabase();
    +            for (const {options} of toIterable((await getOptionsFullMutable()).profiles)) {
    +                options.dictionaries = utilBackgroundIsolate({});
    +                options.general.mainDictionary = '';
    +            }
    +            await settingsSaveOptions();
     
    -        onDatabaseUpdated();
    -    } catch (err) {
    -        dictionaryErrorsShow([err]);
    -    } finally {
    -        prevention.end();
    +            this._onDatabaseUpdated();
    +        } catch (err) {
    +            this._dictionaryErrorsShow([err]);
    +        } finally {
    +            prevention.end();
     
    -        dictionarySpinnerShow(false);
    +            this._dictionarySpinnerShow(false);
     
    -        dictControls.show();
    -        dictProgress.hidden = true;
    +            dictControls.show();
    +            dictProgress.hidden = true;
     
    -        if (storageEstimate.mostRecent !== null) {
    -            storageUpdateStats();
    +            this._storageController.updateStats();
             }
         }
    -}
     
    -async function onDictionaryImport(e) {
    -    const files = [...e.target.files];
    -    e.target.value = null;
    +    async _onImportFileChange(e) {
    +        const files = [...e.target.files];
    +        e.target.value = null;
     
    -    const dictFile = $('#dict-file');
    -    const dictControls = $('#dict-importer').hide();
    -    const dictProgress = $('#dict-import-progress').show();
    -    const dictImportInfo = document.querySelector('#dict-import-info');
    +        const dictFile = $('#dict-file');
    +        const dictControls = $('#dict-importer').hide();
    +        const dictProgress = $('#dict-import-progress').show();
    +        const dictImportInfo = document.querySelector('#dict-import-info');
     
    -    const prevention = new PageExitPrevention();
    +        const prevention = new PageExitPrevention();
     
    -    try {
    -        prevention.start();
    -        dictionaryErrorsShow(null);
    -        dictionarySpinnerShow(true);
    +        try {
    +            prevention.start();
    +            this._dictionaryErrorsShow(null);
    +            this._dictionarySpinnerShow(true);
     
    -        const setProgress = (percent) => dictProgress.find('.progress-bar').css('width', `${percent}%`);
    -        const updateProgress = (total, current) => {
    -            setProgress(current / total * 100.0);
    -            if (storageEstimate.mostRecent !== null && !storageUpdateStats.isUpdating) {
    -                storageUpdateStats();
    -            }
    -        };
    +            const setProgress = (percent) => dictProgress.find('.progress-bar').css('width', `${percent}%`);
    +            const updateProgress = (total, current) => {
    +                setProgress(current / total * 100.0);
    +                this._storageController.updateStats();
    +            };
     
    -        const optionsFull = await api.optionsGetFull();
    +            const optionsFull = await api.optionsGetFull();
     
    -        const importDetails = {
    -            prefixWildcardsSupported: optionsFull.global.database.prefixWildcardsSupported
    -        };
    +            const importDetails = {
    +                prefixWildcardsSupported: optionsFull.global.database.prefixWildcardsSupported
    +            };
     
    -        for (let i = 0, ii = files.length; i < ii; ++i) {
    -            setProgress(0.0);
    -            if (ii > 1) {
    -                dictImportInfo.hidden = false;
    -                dictImportInfo.textContent = `(${i + 1} of ${ii})`;
    -            }
    +            for (let i = 0, ii = files.length; i < ii; ++i) {
    +                setProgress(0.0);
    +                if (ii > 1) {
    +                    dictImportInfo.hidden = false;
    +                    dictImportInfo.textContent = `(${i + 1} of ${ii})`;
    +                }
     
    -            const archiveContent = await dictReadFile(files[i]);
    -            const {result, errors} = await api.importDictionaryArchive(archiveContent, importDetails, updateProgress);
    -            for (const {options} of toIterable((await getOptionsFullMutable()).profiles)) {
    -                const dictionaryOptions = SettingsDictionaryListUI.createDictionaryOptions();
    -                dictionaryOptions.enabled = true;
    -                options.dictionaries[result.title] = dictionaryOptions;
    -                if (result.sequenced && options.general.mainDictionary === '') {
    -                    options.general.mainDictionary = result.title;
    +                const archiveContent = await this._dictReadFile(files[i]);
    +                const {result, errors} = await api.importDictionaryArchive(archiveContent, importDetails, updateProgress);
    +                for (const {options} of toIterable((await getOptionsFullMutable()).profiles)) {
    +                    const dictionaryOptions = SettingsDictionaryListUI.createDictionaryOptions();
    +                    dictionaryOptions.enabled = true;
    +                    options.dictionaries[result.title] = dictionaryOptions;
    +                    if (result.sequenced && options.general.mainDictionary === '') {
    +                        options.general.mainDictionary = result.title;
    +                    }
                     }
    -            }
     
    -            await settingsSaveOptions();
    +                await settingsSaveOptions();
    +
    +                if (errors.length > 0) {
    +                    const errors2 = errors.map((error) => jsonToError(error));
    +                    errors2.push(`Dictionary may not have been imported properly: ${errors2.length} error${errors2.length === 1 ? '' : 's'} reported.`);
    +                    this._dictionaryErrorsShow(errors2);
    +                }
     
    -            if (errors.length > 0) {
    -                const errors2 = errors.map((error) => jsonToError(error));
    -                errors2.push(`Dictionary may not have been imported properly: ${errors2.length} error${errors2.length === 1 ? '' : 's'} reported.`);
    -                dictionaryErrorsShow(errors2);
    +                this._onDatabaseUpdated();
                 }
    +        } catch (err) {
    +            this._dictionaryErrorsShow([err]);
    +        } finally {
    +            prevention.end();
    +            this._dictionarySpinnerShow(false);
     
    -            onDatabaseUpdated();
    +            dictImportInfo.hidden = false;
    +            dictImportInfo.textContent = '';
    +            dictFile.val('');
    +            dictControls.show();
    +            dictProgress.hide();
             }
    -    } catch (err) {
    -        dictionaryErrorsShow([err]);
    -    } finally {
    -        prevention.end();
    -        dictionarySpinnerShow(false);
    -
    -        dictImportInfo.hidden = false;
    -        dictImportInfo.textContent = '';
    -        dictFile.val('');
    -        dictControls.show();
    -        dictProgress.hide();
         }
    -}
    -
    -function dictReadFile(file) {
    -    return new Promise((resolve, reject) => {
    -        const reader = new FileReader();
    -        reader.onload = () => resolve(reader.result);
    -        reader.onerror = () => reject(reader.error);
    -        reader.readAsBinaryString(file);
    -    });
    -}
     
    -
    -async function onDatabaseEnablePrefixWildcardSearchesChanged(e) {
    -    const optionsFull = await getOptionsFullMutable();
    -    const v = !!e.target.checked;
    -    if (optionsFull.global.database.prefixWildcardsSupported === v) { return; }
    -    optionsFull.global.database.prefixWildcardsSupported = !!e.target.checked;
    -    await settingsSaveOptions();
    +    async _onDatabaseEnablePrefixWildcardSearchesChanged(e) {
    +        const optionsFull = await getOptionsFullMutable();
    +        const v = !!e.target.checked;
    +        if (optionsFull.global.database.prefixWildcardsSupported === v) { return; }
    +        optionsFull.global.database.prefixWildcardsSupported = !!e.target.checked;
    +        await settingsSaveOptions();
    +    }
     }
    diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js
    index dddaef6c..1d387749 100644
    --- a/ext/bg/js/settings/main.js
    +++ b/ext/bg/js/settings/main.js
    @@ -19,14 +19,13 @@
      * AnkiController
      * AnkiTemplatesController
      * AudioController
    + * DictionaryController
      * ProfileController
      * SettingsBackup
      * SettingsController
    + * StorageController
      * api
      * appearanceInitialize
    - * dictSettingsInitialize
    - * onDictionaryOptionsChanged
    - * storageInfoInitialize
      * utilBackend
      * utilBackgroundIsolate
      */
    @@ -270,7 +269,9 @@ async function onOptionsUpdated({source}) {
         if (ankiTemplatesController !== null) {
             ankiTemplatesController.updateValue();
         }
    -    onDictionaryOptionsChanged();
    +    if (dictionaryController !== null) {
    +        dictionaryController.optionsChanged();
    +    }
         if (ankiController !== null) {
             ankiController.optionsChanged();
         }
    @@ -304,8 +305,15 @@ async function settingsPopulateModifierKeys() {
         }
     }
     
    +async function setupEnvironmentInfo() {
    +    const {browser, platform} = await api.getEnvironmentInfo();
    +    document.documentElement.dataset.browser = browser;
    +    document.documentElement.dataset.operatingSystem = platform.os;
    +}
    +
     let ankiController = null;
     let ankiTemplatesController = null;
    +let dictionaryController = null;
     
     async function onReady() {
         api.forwardLogsToBackend();
    @@ -314,22 +322,25 @@ async function onReady() {
         const settingsController = new SettingsController();
         settingsController.prepare();
     
    +    setupEnvironmentInfo();
         showExtensionInformation();
     
    +    const storageController = new StorageController();
    +    storageController.prepare();
    +
         await settingsPopulateModifierKeys();
         formSetupEventListeners();
         appearanceInitialize();
         new AudioController().prepare();
         await (new ProfileController()).prepare();
    -    await dictSettingsInitialize();
    +    dictionaryController = new DictionaryController(storageController);
    +    dictionaryController.prepare();
         ankiController = new AnkiController();
         ankiController.prepare();
         ankiTemplatesController = new AnkiTemplatesController(ankiController);
         ankiTemplatesController.prepare();
         new SettingsBackup().prepare();
     
    -    storageInfoInitialize();
    -
         yomichan.on('optionsUpdated', onOptionsUpdated);
     }
     
    diff --git a/ext/bg/js/settings/storage.js b/ext/bg/js/settings/storage.js
    index 73c93fa1..24c6d7ef 100644
    --- a/ext/bg/js/settings/storage.js
    +++ b/ext/bg/js/settings/storage.js
    @@ -15,126 +15,117 @@
      * along with this program.  If not, see .
      */
     
    -/* global
    - * api
    - */
    -
    -function storageBytesToLabeledString(size) {
    -    const base = 1000;
    -    const labels = [' bytes', 'KB', 'MB', 'GB'];
    -    let labelIndex = 0;
    -    while (size >= base) {
    -        size /= base;
    -        ++labelIndex;
    +class StorageController {
    +    constructor() {
    +        this._mostRecentStorageEstimate = null;
    +        this._storageEstimateFailed = false;
    +        this._isUpdating = false;
         }
    -    const label = labelIndex === 0 ? `${size}` : size.toFixed(1);
    -    return `${label}${labels[labelIndex]}`;
    -}
     
    -async function storageEstimate() {
    -    try {
    -        return (storageEstimate.mostRecent = await navigator.storage.estimate());
    -    } catch (e) {
    -        // NOP
    +    prepare() {
    +        this._preparePersistentStorage();
    +        this.updateStats();
    +        document.querySelector('#storage-refresh').addEventListener('click', this.updateStats.bind(this), false);
         }
    -    return null;
    -}
    -storageEstimate.mostRecent = null;
    -
    -async function isStoragePeristent() {
    -    try {
    -        return await navigator.storage.persisted();
    -    } catch (e) {
    -        // NOP
    -    }
    -    return false;
    -}
    -
    -async function storageInfoInitialize() {
    -    storagePersistInitialize();
    -    const {browser, platform} = await api.getEnvironmentInfo();
    -    document.documentElement.dataset.browser = browser;
    -    document.documentElement.dataset.operatingSystem = platform.os;
    -
    -    await storageShowInfo();
     
    -    document.querySelector('#storage-refresh').addEventListener('click', storageShowInfo, false);
    -}
    -
    -async function storageUpdateStats() {
    -    storageUpdateStats.isUpdating = true;
    -
    -    const estimate = await storageEstimate();
    -    const valid = (estimate !== null);
    -
    -    if (valid) {
    -        // Firefox reports usage as 0 when persistent storage is enabled.
    -        const finite = (estimate.usage > 0 || !(await isStoragePeristent()));
    -        if (finite) {
    -            document.querySelector('#storage-usage').textContent = storageBytesToLabeledString(estimate.usage);
    -            document.querySelector('#storage-quota').textContent = storageBytesToLabeledString(estimate.quota);
    +    async updateStats() {
    +        try {
    +            this._isUpdating = true;
    +
    +            const estimate = await this._storageEstimate();
    +            const valid = (estimate !== null);
    +
    +            if (valid) {
    +                // Firefox reports usage as 0 when persistent storage is enabled.
    +                const finite = (estimate.usage > 0 || !(await this._isStoragePeristent()));
    +                if (finite) {
    +                    document.querySelector('#storage-usage').textContent = this._bytesToLabeledString(estimate.usage);
    +                    document.querySelector('#storage-quota').textContent = this._bytesToLabeledString(estimate.quota);
    +                }
    +                document.querySelector('#storage-use-finite').classList.toggle('storage-hidden', !finite);
    +                document.querySelector('#storage-use-infinite').classList.toggle('storage-hidden', finite);
    +            }
    +
    +            document.querySelector('#storage-use').classList.toggle('storage-hidden', !valid);
    +            document.querySelector('#storage-error').classList.toggle('storage-hidden', valid);
    +
    +            return valid;
    +        } finally {
    +            this._isUpdating = false;
             }
    -        document.querySelector('#storage-use-finite').classList.toggle('storage-hidden', !finite);
    -        document.querySelector('#storage-use-infinite').classList.toggle('storage-hidden', finite);
         }
     
    -    storageUpdateStats.isUpdating = false;
    -    return valid;
    -}
    -storageUpdateStats.isUpdating = false;
    -
    -async function storageShowInfo() {
    -    storageSpinnerShow(true);
    -
    -    const valid = await storageUpdateStats();
    -    document.querySelector('#storage-use').classList.toggle('storage-hidden', !valid);
    -    document.querySelector('#storage-error').classList.toggle('storage-hidden', valid);
    +    // Private
     
    -    storageSpinnerShow(false);
    -}
    +    async _preparePersistentStorage() {
    +        if (!(navigator.storage && navigator.storage.persist)) {
    +            // Not supported
    +            return;
    +        }
     
    -function storageSpinnerShow(show) {
    -    const spinner = $('#storage-spinner');
    -    if (show) {
    -        spinner.show();
    -    } else {
    -        spinner.hide();
    +        const info = document.querySelector('#storage-persist-info');
    +        const button = document.querySelector('#storage-persist-button');
    +        const checkbox = document.querySelector('#storage-persist-button-checkbox');
    +
    +        info.classList.remove('storage-hidden');
    +        button.classList.remove('storage-hidden');
    +
    +        let persisted = await this._isStoragePeristent();
    +        checkbox.checked = persisted;
    +
    +        button.addEventListener('click', async () => {
    +            if (persisted) {
    +                return;
    +            }
    +            let result = false;
    +            try {
    +                result = await navigator.storage.persist();
    +            } catch (e) {
    +                // NOP
    +            }
    +
    +            if (result) {
    +                persisted = true;
    +                checkbox.checked = true;
    +                this.updateStats();
    +            } else {
    +                document.querySelector('.storage-persist-fail-warning').classList.remove('storage-hidden');
    +            }
    +        }, false);
         }
    -}
     
    -async function storagePersistInitialize() {
    -    if (!(navigator.storage && navigator.storage.persist)) {
    -        // Not supported
    -        return;
    +    async _storageEstimate() {
    +        if (this._storageEstimateFailed && this._mostRecentStorageEstimate === null) {
    +            return null;
    +        }
    +        try {
    +            const value = await navigator.storage.estimate();
    +            this._mostRecentStorageEstimate = value;
    +            return value;
    +        } catch (e) {
    +            this._storageEstimateFailed = true;
    +        }
    +        return null;
         }
     
    -    const info = document.querySelector('#storage-persist-info');
    -    const button = document.querySelector('#storage-persist-button');
    -    const checkbox = document.querySelector('#storage-persist-button-checkbox');
    -
    -    info.classList.remove('storage-hidden');
    -    button.classList.remove('storage-hidden');
    -
    -    let persisted = await isStoragePeristent();
    -    checkbox.checked = persisted;
    -
    -    button.addEventListener('click', async () => {
    -        if (persisted) {
    -            return;
    +    _bytesToLabeledString(size) {
    +        const base = 1000;
    +        const labels = [' bytes', 'KB', 'MB', 'GB'];
    +        let labelIndex = 0;
    +        while (size >= base) {
    +            size /= base;
    +            ++labelIndex;
             }
    -        let result = false;
    +        const label = labelIndex === 0 ? `${size}` : size.toFixed(1);
    +        return `${label}${labels[labelIndex]}`;
    +    }
    +
    +    async _isStoragePeristent() {
             try {
    -            result = await navigator.storage.persist();
    +            return await navigator.storage.persisted();
             } catch (e) {
                 // NOP
             }
    -
    -        if (result) {
    -            persisted = true;
    -            checkbox.checked = true;
    -            storageShowInfo();
    -        } else {
    -            $('.storage-persist-fail-warning').removeClass('storage-hidden');
    -        }
    -    }, false);
    +        return false;
    +    }
     }
    diff --git a/ext/bg/settings.html b/ext/bg/settings.html
    index 5c7fde41..4856b0b4 100644
    --- a/ext/bg/settings.html
    +++ b/ext/bg/settings.html
    @@ -711,7 +711,6 @@
     
                 
    -

    Storage

    -- cgit v1.2.3 From 18f376358c4c8eec1e46fe1d9396861a42559918 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 29 May 2020 20:33:40 -0400 Subject: Generic settings controller + clipboard popups controller (#573) * Create GenericSettingController * Create ClipboardPopupsController --- ext/bg/js/settings/clipboard-popups-controller.js | 52 ++++++ ext/bg/js/settings/generic-setting-controller.js | 197 +++++++++++++++++++++ ext/bg/js/settings/main.js | 202 +--------------------- ext/bg/settings.html | 2 + 4 files changed, 260 insertions(+), 193 deletions(-) create mode 100644 ext/bg/js/settings/clipboard-popups-controller.js create mode 100644 ext/bg/js/settings/generic-setting-controller.js (limited to 'ext/bg/settings.html') diff --git a/ext/bg/js/settings/clipboard-popups-controller.js b/ext/bg/js/settings/clipboard-popups-controller.js new file mode 100644 index 00000000..cb9e857f --- /dev/null +++ b/ext/bg/js/settings/clipboard-popups-controller.js @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* globals + * getOptionsContext + * getOptionsMutable + * settingsSaveOptions + */ + +class ClipboardPopupsController { + prepare() { + document.querySelector('#enable-clipboard-popups').addEventListener('change', this._onEnableClipboardPopupsChanged.bind(this), false); + } + + async _onEnableClipboardPopupsChanged(e) { + const optionsContext = getOptionsContext(); + const options = await getOptionsMutable(optionsContext); + + const enableClipboardPopups = e.target.checked; + if (enableClipboardPopups) { + options.general.enableClipboardPopups = await new Promise((resolve) => { + chrome.permissions.request( + {permissions: ['clipboardRead']}, + (granted) => { + if (!granted) { + $('#enable-clipboard-popups').prop('checked', false); + } + resolve(granted); + } + ); + }); + } else { + options.general.enableClipboardPopups = false; + } + + await settingsSaveOptions(); + } +} diff --git a/ext/bg/js/settings/generic-setting-controller.js b/ext/bg/js/settings/generic-setting-controller.js new file mode 100644 index 00000000..4a20bf65 --- /dev/null +++ b/ext/bg/js/settings/generic-setting-controller.js @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2020 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* globals + * getOptionsContext + * getOptionsMutable + * settingsSaveOptions + * utilBackgroundIsolate + */ + +class GenericSettingController { + prepare() { + $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change(this._onFormOptionsChanged.bind(this)); + } + + optionsChanged(options) { + this._formWrite(options); + } + + // Private + + async _formWrite(options) { + $('#enable').prop('checked', options.general.enable); + $('#show-usage-guide').prop('checked', options.general.showGuide); + $('#compact-tags').prop('checked', options.general.compactTags); + $('#compact-glossaries').prop('checked', options.general.compactGlossaries); + $('#result-output-mode').val(options.general.resultOutputMode); + $('#show-debug-info').prop('checked', options.general.debugInfo); + $('#show-advanced-options').prop('checked', options.general.showAdvanced); + $('#max-displayed-results').val(options.general.maxResults); + $('#popup-display-mode').val(options.general.popupDisplayMode); + $('#popup-horizontal-text-position').val(options.general.popupHorizontalTextPosition); + $('#popup-vertical-text-position').val(options.general.popupVerticalTextPosition); + $('#popup-width').val(options.general.popupWidth); + $('#popup-height').val(options.general.popupHeight); + $('#popup-horizontal-offset').val(options.general.popupHorizontalOffset); + $('#popup-vertical-offset').val(options.general.popupVerticalOffset); + $('#popup-horizontal-offset2').val(options.general.popupHorizontalOffset2); + $('#popup-vertical-offset2').val(options.general.popupVerticalOffset2); + $('#popup-scaling-factor').val(options.general.popupScalingFactor); + $('#popup-scale-relative-to-page-zoom').prop('checked', options.general.popupScaleRelativeToPageZoom); + $('#popup-scale-relative-to-visual-viewport').prop('checked', options.general.popupScaleRelativeToVisualViewport); + $('#show-pitch-accent-downstep-notation').prop('checked', options.general.showPitchAccentDownstepNotation); + $('#show-pitch-accent-position-notation').prop('checked', options.general.showPitchAccentPositionNotation); + $('#show-pitch-accent-graph').prop('checked', options.general.showPitchAccentGraph); + $('#show-iframe-popups-in-root-frame').prop('checked', options.general.showIframePopupsInRootFrame); + $('#popup-theme').val(options.general.popupTheme); + $('#popup-outer-theme').val(options.general.popupOuterTheme); + $('#custom-popup-css').val(options.general.customPopupCss); + $('#custom-popup-outer-css').val(options.general.customPopupOuterCss); + + $('#audio-playback-enabled').prop('checked', options.audio.enabled); + $('#auto-play-audio').prop('checked', options.audio.autoPlay); + $('#audio-playback-volume').val(options.audio.volume); + $('#audio-custom-source').val(options.audio.customSourceUrl); + $('#text-to-speech-voice').val(options.audio.textToSpeechVoice).attr('data-value', options.audio.textToSpeechVoice); + + $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); + $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled); + $('#select-matched-text').prop('checked', options.scanning.selectText); + $('#search-alphanumeric').prop('checked', options.scanning.alphanumeric); + $('#auto-hide-results').prop('checked', options.scanning.autoHideResults); + $('#deep-dom-scan').prop('checked', options.scanning.deepDomScan); + $('#enable-search-within-first-popup').prop('checked', options.scanning.enablePopupSearch); + $('#enable-scanning-of-popup-expressions').prop('checked', options.scanning.enableOnPopupExpressions); + $('#enable-scanning-on-search-page').prop('checked', options.scanning.enableOnSearchPage); + $('#enable-search-tags').prop('checked', options.scanning.enableSearchTags); + $('#scan-delay').val(options.scanning.delay); + $('#scan-length').val(options.scanning.length); + $('#scan-modifier-key').val(options.scanning.modifier); + $('#popup-nesting-max-depth').val(options.scanning.popupNestingMaxDepth); + + $('#translation-convert-half-width-characters').val(options.translation.convertHalfWidthCharacters); + $('#translation-convert-numeric-characters').val(options.translation.convertNumericCharacters); + $('#translation-convert-alphabetic-characters').val(options.translation.convertAlphabeticCharacters); + $('#translation-convert-hiragana-to-katakana').val(options.translation.convertHiraganaToKatakana); + $('#translation-convert-katakana-to-hiragana').val(options.translation.convertKatakanaToHiragana); + $('#translation-collapse-emphatic-sequences').val(options.translation.collapseEmphaticSequences); + + $('#parsing-scan-enable').prop('checked', options.parsing.enableScanningParser); + $('#parsing-mecab-enable').prop('checked', options.parsing.enableMecabParser); + $('#parsing-term-spacing').prop('checked', options.parsing.termSpacing); + $('#parsing-reading-mode').val(options.parsing.readingMode); + + $('#anki-enable').prop('checked', options.anki.enable); + $('#card-tags').val(options.anki.tags.join(' ')); + $('#sentence-detection-extent').val(options.anki.sentenceExt); + $('#interface-server').val(options.anki.server); + $('#duplicate-scope').val(options.anki.duplicateScope); + $('#screenshot-format').val(options.anki.screenshot.format); + $('#screenshot-quality').val(options.anki.screenshot.quality); + + this._formUpdateVisibility(options); + } + + async _formRead(options) { + options.general.enable = $('#enable').prop('checked'); + options.general.showGuide = $('#show-usage-guide').prop('checked'); + options.general.compactTags = $('#compact-tags').prop('checked'); + options.general.compactGlossaries = $('#compact-glossaries').prop('checked'); + options.general.resultOutputMode = $('#result-output-mode').val(); + options.general.debugInfo = $('#show-debug-info').prop('checked'); + options.general.showAdvanced = $('#show-advanced-options').prop('checked'); + options.general.maxResults = parseInt($('#max-displayed-results').val(), 10); + options.general.popupDisplayMode = $('#popup-display-mode').val(); + options.general.popupHorizontalTextPosition = $('#popup-horizontal-text-position').val(); + options.general.popupVerticalTextPosition = $('#popup-vertical-text-position').val(); + options.general.popupWidth = parseInt($('#popup-width').val(), 10); + options.general.popupHeight = parseInt($('#popup-height').val(), 10); + options.general.popupHorizontalOffset = parseInt($('#popup-horizontal-offset').val(), 0); + options.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10); + options.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0); + options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10); + options.general.popupScalingFactor = parseFloat($('#popup-scaling-factor').val()); + options.general.popupScaleRelativeToPageZoom = $('#popup-scale-relative-to-page-zoom').prop('checked'); + options.general.popupScaleRelativeToVisualViewport = $('#popup-scale-relative-to-visual-viewport').prop('checked'); + options.general.showPitchAccentDownstepNotation = $('#show-pitch-accent-downstep-notation').prop('checked'); + options.general.showPitchAccentPositionNotation = $('#show-pitch-accent-position-notation').prop('checked'); + options.general.showPitchAccentGraph = $('#show-pitch-accent-graph').prop('checked'); + options.general.showIframePopupsInRootFrame = $('#show-iframe-popups-in-root-frame').prop('checked'); + options.general.popupTheme = $('#popup-theme').val(); + options.general.popupOuterTheme = $('#popup-outer-theme').val(); + options.general.customPopupCss = $('#custom-popup-css').val(); + options.general.customPopupOuterCss = $('#custom-popup-outer-css').val(); + + options.audio.enabled = $('#audio-playback-enabled').prop('checked'); + options.audio.autoPlay = $('#auto-play-audio').prop('checked'); + options.audio.volume = parseFloat($('#audio-playback-volume').val()); + options.audio.customSourceUrl = $('#audio-custom-source').val(); + options.audio.textToSpeechVoice = $('#text-to-speech-voice').val(); + + options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); + options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked'); + options.scanning.selectText = $('#select-matched-text').prop('checked'); + options.scanning.alphanumeric = $('#search-alphanumeric').prop('checked'); + options.scanning.autoHideResults = $('#auto-hide-results').prop('checked'); + options.scanning.deepDomScan = $('#deep-dom-scan').prop('checked'); + options.scanning.enablePopupSearch = $('#enable-search-within-first-popup').prop('checked'); + options.scanning.enableOnPopupExpressions = $('#enable-scanning-of-popup-expressions').prop('checked'); + options.scanning.enableOnSearchPage = $('#enable-scanning-on-search-page').prop('checked'); + options.scanning.enableSearchTags = $('#enable-search-tags').prop('checked'); + options.scanning.delay = parseInt($('#scan-delay').val(), 10); + options.scanning.length = parseInt($('#scan-length').val(), 10); + options.scanning.modifier = $('#scan-modifier-key').val(); + options.scanning.popupNestingMaxDepth = parseInt($('#popup-nesting-max-depth').val(), 10); + + options.translation.convertHalfWidthCharacters = $('#translation-convert-half-width-characters').val(); + options.translation.convertNumericCharacters = $('#translation-convert-numeric-characters').val(); + options.translation.convertAlphabeticCharacters = $('#translation-convert-alphabetic-characters').val(); + options.translation.convertHiraganaToKatakana = $('#translation-convert-hiragana-to-katakana').val(); + options.translation.convertKatakanaToHiragana = $('#translation-convert-katakana-to-hiragana').val(); + options.translation.collapseEmphaticSequences = $('#translation-collapse-emphatic-sequences').val(); + + options.parsing.enableScanningParser = $('#parsing-scan-enable').prop('checked'); + options.parsing.enableMecabParser = $('#parsing-mecab-enable').prop('checked'); + options.parsing.termSpacing = $('#parsing-term-spacing').prop('checked'); + options.parsing.readingMode = $('#parsing-reading-mode').val(); + + options.anki.enable = $('#anki-enable').prop('checked'); + options.anki.tags = utilBackgroundIsolate($('#card-tags').val().split(/[,; ]+/)); + options.anki.sentenceExt = parseInt($('#sentence-detection-extent').val(), 10); + options.anki.server = $('#interface-server').val(); + options.anki.duplicateScope = $('#duplicate-scope').val(); + options.anki.screenshot.format = $('#screenshot-format').val(); + options.anki.screenshot.quality = parseInt($('#screenshot-quality').val(), 10); + } + + async _onFormOptionsChanged() { + const optionsContext = getOptionsContext(); + const options = await getOptionsMutable(optionsContext); + + await this._formRead(options); + await settingsSaveOptions(); + this._formUpdateVisibility(options); + } + + _formUpdateVisibility(options) { + document.documentElement.dataset.optionsAnkiEnable = `${!!options.anki.enable}`; + document.documentElement.dataset.optionsGeneralDebugInfo = `${!!options.general.debugInfo}`; + document.documentElement.dataset.optionsGeneralShowAdvanced = `${!!options.general.showAdvanced}`; + document.documentElement.dataset.optionsGeneralResultOutputMode = `${options.general.resultOutputMode}`; + } +} diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index b84824e6..d6f55bde 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -19,7 +19,9 @@ * AnkiController * AnkiTemplatesController * AudioController + * ClipboardPopupsController * DictionaryController + * GenericSettingController * PopupPreviewController * ProfileController * SettingsBackup @@ -55,197 +57,6 @@ function getOptionsFullMutable() { return utilBackend().getFullOptions(); } -async function formRead(options) { - options.general.enable = $('#enable').prop('checked'); - options.general.showGuide = $('#show-usage-guide').prop('checked'); - options.general.compactTags = $('#compact-tags').prop('checked'); - options.general.compactGlossaries = $('#compact-glossaries').prop('checked'); - options.general.resultOutputMode = $('#result-output-mode').val(); - options.general.debugInfo = $('#show-debug-info').prop('checked'); - options.general.showAdvanced = $('#show-advanced-options').prop('checked'); - options.general.maxResults = parseInt($('#max-displayed-results').val(), 10); - options.general.popupDisplayMode = $('#popup-display-mode').val(); - options.general.popupHorizontalTextPosition = $('#popup-horizontal-text-position').val(); - options.general.popupVerticalTextPosition = $('#popup-vertical-text-position').val(); - options.general.popupWidth = parseInt($('#popup-width').val(), 10); - options.general.popupHeight = parseInt($('#popup-height').val(), 10); - options.general.popupHorizontalOffset = parseInt($('#popup-horizontal-offset').val(), 0); - options.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10); - options.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0); - options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10); - options.general.popupScalingFactor = parseFloat($('#popup-scaling-factor').val()); - options.general.popupScaleRelativeToPageZoom = $('#popup-scale-relative-to-page-zoom').prop('checked'); - options.general.popupScaleRelativeToVisualViewport = $('#popup-scale-relative-to-visual-viewport').prop('checked'); - options.general.showPitchAccentDownstepNotation = $('#show-pitch-accent-downstep-notation').prop('checked'); - options.general.showPitchAccentPositionNotation = $('#show-pitch-accent-position-notation').prop('checked'); - options.general.showPitchAccentGraph = $('#show-pitch-accent-graph').prop('checked'); - options.general.showIframePopupsInRootFrame = $('#show-iframe-popups-in-root-frame').prop('checked'); - options.general.popupTheme = $('#popup-theme').val(); - options.general.popupOuterTheme = $('#popup-outer-theme').val(); - options.general.customPopupCss = $('#custom-popup-css').val(); - options.general.customPopupOuterCss = $('#custom-popup-outer-css').val(); - - options.audio.enabled = $('#audio-playback-enabled').prop('checked'); - options.audio.autoPlay = $('#auto-play-audio').prop('checked'); - options.audio.volume = parseFloat($('#audio-playback-volume').val()); - options.audio.customSourceUrl = $('#audio-custom-source').val(); - options.audio.textToSpeechVoice = $('#text-to-speech-voice').val(); - - options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); - options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked'); - options.scanning.selectText = $('#select-matched-text').prop('checked'); - options.scanning.alphanumeric = $('#search-alphanumeric').prop('checked'); - options.scanning.autoHideResults = $('#auto-hide-results').prop('checked'); - options.scanning.deepDomScan = $('#deep-dom-scan').prop('checked'); - options.scanning.enablePopupSearch = $('#enable-search-within-first-popup').prop('checked'); - options.scanning.enableOnPopupExpressions = $('#enable-scanning-of-popup-expressions').prop('checked'); - options.scanning.enableOnSearchPage = $('#enable-scanning-on-search-page').prop('checked'); - options.scanning.enableSearchTags = $('#enable-search-tags').prop('checked'); - options.scanning.delay = parseInt($('#scan-delay').val(), 10); - options.scanning.length = parseInt($('#scan-length').val(), 10); - options.scanning.modifier = $('#scan-modifier-key').val(); - options.scanning.popupNestingMaxDepth = parseInt($('#popup-nesting-max-depth').val(), 10); - - options.translation.convertHalfWidthCharacters = $('#translation-convert-half-width-characters').val(); - options.translation.convertNumericCharacters = $('#translation-convert-numeric-characters').val(); - options.translation.convertAlphabeticCharacters = $('#translation-convert-alphabetic-characters').val(); - options.translation.convertHiraganaToKatakana = $('#translation-convert-hiragana-to-katakana').val(); - options.translation.convertKatakanaToHiragana = $('#translation-convert-katakana-to-hiragana').val(); - options.translation.collapseEmphaticSequences = $('#translation-collapse-emphatic-sequences').val(); - - options.parsing.enableScanningParser = $('#parsing-scan-enable').prop('checked'); - options.parsing.enableMecabParser = $('#parsing-mecab-enable').prop('checked'); - options.parsing.termSpacing = $('#parsing-term-spacing').prop('checked'); - options.parsing.readingMode = $('#parsing-reading-mode').val(); - - options.anki.enable = $('#anki-enable').prop('checked'); - options.anki.tags = utilBackgroundIsolate($('#card-tags').val().split(/[,; ]+/)); - options.anki.sentenceExt = parseInt($('#sentence-detection-extent').val(), 10); - options.anki.server = $('#interface-server').val(); - options.anki.duplicateScope = $('#duplicate-scope').val(); - options.anki.screenshot.format = $('#screenshot-format').val(); - options.anki.screenshot.quality = parseInt($('#screenshot-quality').val(), 10); -} - -async function formWrite(options) { - $('#enable').prop('checked', options.general.enable); - $('#show-usage-guide').prop('checked', options.general.showGuide); - $('#compact-tags').prop('checked', options.general.compactTags); - $('#compact-glossaries').prop('checked', options.general.compactGlossaries); - $('#result-output-mode').val(options.general.resultOutputMode); - $('#show-debug-info').prop('checked', options.general.debugInfo); - $('#show-advanced-options').prop('checked', options.general.showAdvanced); - $('#max-displayed-results').val(options.general.maxResults); - $('#popup-display-mode').val(options.general.popupDisplayMode); - $('#popup-horizontal-text-position').val(options.general.popupHorizontalTextPosition); - $('#popup-vertical-text-position').val(options.general.popupVerticalTextPosition); - $('#popup-width').val(options.general.popupWidth); - $('#popup-height').val(options.general.popupHeight); - $('#popup-horizontal-offset').val(options.general.popupHorizontalOffset); - $('#popup-vertical-offset').val(options.general.popupVerticalOffset); - $('#popup-horizontal-offset2').val(options.general.popupHorizontalOffset2); - $('#popup-vertical-offset2').val(options.general.popupVerticalOffset2); - $('#popup-scaling-factor').val(options.general.popupScalingFactor); - $('#popup-scale-relative-to-page-zoom').prop('checked', options.general.popupScaleRelativeToPageZoom); - $('#popup-scale-relative-to-visual-viewport').prop('checked', options.general.popupScaleRelativeToVisualViewport); - $('#show-pitch-accent-downstep-notation').prop('checked', options.general.showPitchAccentDownstepNotation); - $('#show-pitch-accent-position-notation').prop('checked', options.general.showPitchAccentPositionNotation); - $('#show-pitch-accent-graph').prop('checked', options.general.showPitchAccentGraph); - $('#show-iframe-popups-in-root-frame').prop('checked', options.general.showIframePopupsInRootFrame); - $('#popup-theme').val(options.general.popupTheme); - $('#popup-outer-theme').val(options.general.popupOuterTheme); - $('#custom-popup-css').val(options.general.customPopupCss); - $('#custom-popup-outer-css').val(options.general.customPopupOuterCss); - - $('#audio-playback-enabled').prop('checked', options.audio.enabled); - $('#auto-play-audio').prop('checked', options.audio.autoPlay); - $('#audio-playback-volume').val(options.audio.volume); - $('#audio-custom-source').val(options.audio.customSourceUrl); - $('#text-to-speech-voice').val(options.audio.textToSpeechVoice).attr('data-value', options.audio.textToSpeechVoice); - - $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); - $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled); - $('#select-matched-text').prop('checked', options.scanning.selectText); - $('#search-alphanumeric').prop('checked', options.scanning.alphanumeric); - $('#auto-hide-results').prop('checked', options.scanning.autoHideResults); - $('#deep-dom-scan').prop('checked', options.scanning.deepDomScan); - $('#enable-search-within-first-popup').prop('checked', options.scanning.enablePopupSearch); - $('#enable-scanning-of-popup-expressions').prop('checked', options.scanning.enableOnPopupExpressions); - $('#enable-scanning-on-search-page').prop('checked', options.scanning.enableOnSearchPage); - $('#enable-search-tags').prop('checked', options.scanning.enableSearchTags); - $('#scan-delay').val(options.scanning.delay); - $('#scan-length').val(options.scanning.length); - $('#scan-modifier-key').val(options.scanning.modifier); - $('#popup-nesting-max-depth').val(options.scanning.popupNestingMaxDepth); - - $('#translation-convert-half-width-characters').val(options.translation.convertHalfWidthCharacters); - $('#translation-convert-numeric-characters').val(options.translation.convertNumericCharacters); - $('#translation-convert-alphabetic-characters').val(options.translation.convertAlphabeticCharacters); - $('#translation-convert-hiragana-to-katakana').val(options.translation.convertHiraganaToKatakana); - $('#translation-convert-katakana-to-hiragana').val(options.translation.convertKatakanaToHiragana); - $('#translation-collapse-emphatic-sequences').val(options.translation.collapseEmphaticSequences); - - $('#parsing-scan-enable').prop('checked', options.parsing.enableScanningParser); - $('#parsing-mecab-enable').prop('checked', options.parsing.enableMecabParser); - $('#parsing-term-spacing').prop('checked', options.parsing.termSpacing); - $('#parsing-reading-mode').val(options.parsing.readingMode); - - $('#anki-enable').prop('checked', options.anki.enable); - $('#card-tags').val(options.anki.tags.join(' ')); - $('#sentence-detection-extent').val(options.anki.sentenceExt); - $('#interface-server').val(options.anki.server); - $('#duplicate-scope').val(options.anki.duplicateScope); - $('#screenshot-format').val(options.anki.screenshot.format); - $('#screenshot-quality').val(options.anki.screenshot.quality); - - formUpdateVisibility(options); -} - -function formSetupEventListeners() { - document.querySelector('#enable-clipboard-popups').addEventListener('change', onEnableClipboardPopupsChanged, false); - $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change(onFormOptionsChanged); -} - -function formUpdateVisibility(options) { - document.documentElement.dataset.optionsAnkiEnable = `${!!options.anki.enable}`; - document.documentElement.dataset.optionsGeneralDebugInfo = `${!!options.general.debugInfo}`; - document.documentElement.dataset.optionsGeneralShowAdvanced = `${!!options.general.showAdvanced}`; - document.documentElement.dataset.optionsGeneralResultOutputMode = `${options.general.resultOutputMode}`; -} - -async function onFormOptionsChanged() { - const optionsContext = getOptionsContext(); - const options = await getOptionsMutable(optionsContext); - - await formRead(options); - await settingsSaveOptions(); - formUpdateVisibility(options); -} - -async function onEnableClipboardPopupsChanged(e) { - const optionsContext = getOptionsContext(); - const options = await getOptionsMutable(optionsContext); - - const enableClipboardPopups = e.target.checked; - if (enableClipboardPopups) { - options.general.enableClipboardPopups = await new Promise((resolve) => { - chrome.permissions.request( - {permissions: ['clipboardRead']}, - (granted) => { - if (!granted) { - $('#enable-clipboard-popups').prop('checked', false); - } - resolve(granted); - } - ); - }); - } else { - options.general.enableClipboardPopups = false; - } - - await settingsSaveOptions(); -} - function settingsGetSource() { return new Promise((resolve) => { @@ -276,7 +87,9 @@ async function onOptionsUpdated({source}) { ankiController.optionsChanged(); } - await formWrite(options); + if (genericSettingController !== null) { + genericSettingController.optionsChanged(options); + } } @@ -314,6 +127,7 @@ async function setupEnvironmentInfo() { let ankiController = null; let ankiTemplatesController = null; let dictionaryController = null; +let genericSettingController = null; async function onReady() { api.forwardLogsToBackend(); @@ -329,7 +143,9 @@ async function onReady() { storageController.prepare(); await settingsPopulateModifierKeys(); - formSetupEventListeners(); + genericSettingController = new GenericSettingController(); + genericSettingController.prepare(); + new ClipboardPopupsController().prepare(); new PopupPreviewController().prepare(); new AudioController().prepare(); await (new ProfileController()).prepare(); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 4856b0b4..bab62519 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -1141,8 +1141,10 @@ + + -- cgit v1.2.3 From 395a0f40965aac62389e2b7eea389d6b1672ae4a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 30 May 2020 16:20:31 -0400 Subject: Update GenericSettingController to use DOMSettingsBinder (#578) --- ext/bg/js/settings/generic-setting-controller.js | 177 +++-------------------- ext/bg/js/settings/settings-controller.js | 4 + ext/bg/settings.html | 134 ++++++++--------- 3 files changed, 94 insertions(+), 221 deletions(-) (limited to 'ext/bg/settings.html') diff --git a/ext/bg/js/settings/generic-setting-controller.js b/ext/bg/js/settings/generic-setting-controller.js index d7d40c5d..aa3118e5 100644 --- a/ext/bg/js/settings/generic-setting-controller.js +++ b/ext/bg/js/settings/generic-setting-controller.js @@ -16,182 +16,47 @@ */ /* globals + * DOMSettingsBinder * utilBackgroundIsolate */ class GenericSettingController { constructor(settingsController) { this._settingsController = settingsController; + this._settingsBinder = null; } async prepare() { - $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change(this._onFormOptionsChanged.bind(this)); + this._settingsBinder = new DOMSettingsBinder({ + getOptionsContext: () => this._settingsController.getOptionsContext(), + source: this._settingsController.source, + transforms: [ + ['setDocumentAttribute', this._setDocumentAttribute.bind(this)], + ['splitTags', this._splitTags.bind(this)], + ['joinTags', this._joinTags.bind(this)] + ] + }); + this._settingsBinder.observe(document.body); this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); - - const options = await this._settingsController.getOptions(); - this._onOptionsChanged({options}); } // Private - _onOptionsChanged({options}) { - $('#enable').prop('checked', options.general.enable); - $('#show-usage-guide').prop('checked', options.general.showGuide); - $('#compact-tags').prop('checked', options.general.compactTags); - $('#compact-glossaries').prop('checked', options.general.compactGlossaries); - $('#result-output-mode').val(options.general.resultOutputMode); - $('#show-debug-info').prop('checked', options.general.debugInfo); - $('#show-advanced-options').prop('checked', options.general.showAdvanced); - $('#max-displayed-results').val(options.general.maxResults); - $('#popup-display-mode').val(options.general.popupDisplayMode); - $('#popup-horizontal-text-position').val(options.general.popupHorizontalTextPosition); - $('#popup-vertical-text-position').val(options.general.popupVerticalTextPosition); - $('#popup-width').val(options.general.popupWidth); - $('#popup-height').val(options.general.popupHeight); - $('#popup-horizontal-offset').val(options.general.popupHorizontalOffset); - $('#popup-vertical-offset').val(options.general.popupVerticalOffset); - $('#popup-horizontal-offset2').val(options.general.popupHorizontalOffset2); - $('#popup-vertical-offset2').val(options.general.popupVerticalOffset2); - $('#popup-scaling-factor').val(options.general.popupScalingFactor); - $('#popup-scale-relative-to-page-zoom').prop('checked', options.general.popupScaleRelativeToPageZoom); - $('#popup-scale-relative-to-visual-viewport').prop('checked', options.general.popupScaleRelativeToVisualViewport); - $('#show-pitch-accent-downstep-notation').prop('checked', options.general.showPitchAccentDownstepNotation); - $('#show-pitch-accent-position-notation').prop('checked', options.general.showPitchAccentPositionNotation); - $('#show-pitch-accent-graph').prop('checked', options.general.showPitchAccentGraph); - $('#show-iframe-popups-in-root-frame').prop('checked', options.general.showIframePopupsInRootFrame); - $('#popup-theme').val(options.general.popupTheme); - $('#popup-outer-theme').val(options.general.popupOuterTheme); - $('#custom-popup-css').val(options.general.customPopupCss); - $('#custom-popup-outer-css').val(options.general.customPopupOuterCss); - - $('#audio-playback-enabled').prop('checked', options.audio.enabled); - $('#auto-play-audio').prop('checked', options.audio.autoPlay); - $('#audio-playback-volume').val(options.audio.volume); - $('#audio-custom-source').val(options.audio.customSourceUrl); - $('#text-to-speech-voice').val(options.audio.textToSpeechVoice).attr('data-value', options.audio.textToSpeechVoice); - - $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); - $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled); - $('#select-matched-text').prop('checked', options.scanning.selectText); - $('#search-alphanumeric').prop('checked', options.scanning.alphanumeric); - $('#auto-hide-results').prop('checked', options.scanning.autoHideResults); - $('#deep-dom-scan').prop('checked', options.scanning.deepDomScan); - $('#enable-search-within-first-popup').prop('checked', options.scanning.enablePopupSearch); - $('#enable-scanning-of-popup-expressions').prop('checked', options.scanning.enableOnPopupExpressions); - $('#enable-scanning-on-search-page').prop('checked', options.scanning.enableOnSearchPage); - $('#enable-search-tags').prop('checked', options.scanning.enableSearchTags); - $('#scan-delay').val(options.scanning.delay); - $('#scan-length').val(options.scanning.length); - $('#scan-modifier-key').val(options.scanning.modifier); - $('#popup-nesting-max-depth').val(options.scanning.popupNestingMaxDepth); - - $('#translation-convert-half-width-characters').val(options.translation.convertHalfWidthCharacters); - $('#translation-convert-numeric-characters').val(options.translation.convertNumericCharacters); - $('#translation-convert-alphabetic-characters').val(options.translation.convertAlphabeticCharacters); - $('#translation-convert-hiragana-to-katakana').val(options.translation.convertHiraganaToKatakana); - $('#translation-convert-katakana-to-hiragana').val(options.translation.convertKatakanaToHiragana); - $('#translation-collapse-emphatic-sequences').val(options.translation.collapseEmphaticSequences); - - $('#parsing-scan-enable').prop('checked', options.parsing.enableScanningParser); - $('#parsing-mecab-enable').prop('checked', options.parsing.enableMecabParser); - $('#parsing-term-spacing').prop('checked', options.parsing.termSpacing); - $('#parsing-reading-mode').val(options.parsing.readingMode); - - $('#anki-enable').prop('checked', options.anki.enable); - $('#card-tags').val(options.anki.tags.join(' ')); - $('#sentence-detection-extent').val(options.anki.sentenceExt); - $('#interface-server').val(options.anki.server); - $('#duplicate-scope').val(options.anki.duplicateScope); - $('#screenshot-format').val(options.anki.screenshot.format); - $('#screenshot-quality').val(options.anki.screenshot.quality); - - this._formUpdateVisibility(options); + _onOptionsChanged() { + this._settingsBinder.refresh(); } - _formRead(options) { - options.general.enable = $('#enable').prop('checked'); - options.general.showGuide = $('#show-usage-guide').prop('checked'); - options.general.compactTags = $('#compact-tags').prop('checked'); - options.general.compactGlossaries = $('#compact-glossaries').prop('checked'); - options.general.resultOutputMode = $('#result-output-mode').val(); - options.general.debugInfo = $('#show-debug-info').prop('checked'); - options.general.showAdvanced = $('#show-advanced-options').prop('checked'); - options.general.maxResults = parseInt($('#max-displayed-results').val(), 10); - options.general.popupDisplayMode = $('#popup-display-mode').val(); - options.general.popupHorizontalTextPosition = $('#popup-horizontal-text-position').val(); - options.general.popupVerticalTextPosition = $('#popup-vertical-text-position').val(); - options.general.popupWidth = parseInt($('#popup-width').val(), 10); - options.general.popupHeight = parseInt($('#popup-height').val(), 10); - options.general.popupHorizontalOffset = parseInt($('#popup-horizontal-offset').val(), 0); - options.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10); - options.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0); - options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10); - options.general.popupScalingFactor = parseFloat($('#popup-scaling-factor').val()); - options.general.popupScaleRelativeToPageZoom = $('#popup-scale-relative-to-page-zoom').prop('checked'); - options.general.popupScaleRelativeToVisualViewport = $('#popup-scale-relative-to-visual-viewport').prop('checked'); - options.general.showPitchAccentDownstepNotation = $('#show-pitch-accent-downstep-notation').prop('checked'); - options.general.showPitchAccentPositionNotation = $('#show-pitch-accent-position-notation').prop('checked'); - options.general.showPitchAccentGraph = $('#show-pitch-accent-graph').prop('checked'); - options.general.showIframePopupsInRootFrame = $('#show-iframe-popups-in-root-frame').prop('checked'); - options.general.popupTheme = $('#popup-theme').val(); - options.general.popupOuterTheme = $('#popup-outer-theme').val(); - options.general.customPopupCss = $('#custom-popup-css').val(); - options.general.customPopupOuterCss = $('#custom-popup-outer-css').val(); - - options.audio.enabled = $('#audio-playback-enabled').prop('checked'); - options.audio.autoPlay = $('#auto-play-audio').prop('checked'); - options.audio.volume = parseFloat($('#audio-playback-volume').val()); - options.audio.customSourceUrl = $('#audio-custom-source').val(); - options.audio.textToSpeechVoice = $('#text-to-speech-voice').val(); - - options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); - options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked'); - options.scanning.selectText = $('#select-matched-text').prop('checked'); - options.scanning.alphanumeric = $('#search-alphanumeric').prop('checked'); - options.scanning.autoHideResults = $('#auto-hide-results').prop('checked'); - options.scanning.deepDomScan = $('#deep-dom-scan').prop('checked'); - options.scanning.enablePopupSearch = $('#enable-search-within-first-popup').prop('checked'); - options.scanning.enableOnPopupExpressions = $('#enable-scanning-of-popup-expressions').prop('checked'); - options.scanning.enableOnSearchPage = $('#enable-scanning-on-search-page').prop('checked'); - options.scanning.enableSearchTags = $('#enable-search-tags').prop('checked'); - options.scanning.delay = parseInt($('#scan-delay').val(), 10); - options.scanning.length = parseInt($('#scan-length').val(), 10); - options.scanning.modifier = $('#scan-modifier-key').val(); - options.scanning.popupNestingMaxDepth = parseInt($('#popup-nesting-max-depth').val(), 10); - - options.translation.convertHalfWidthCharacters = $('#translation-convert-half-width-characters').val(); - options.translation.convertNumericCharacters = $('#translation-convert-numeric-characters').val(); - options.translation.convertAlphabeticCharacters = $('#translation-convert-alphabetic-characters').val(); - options.translation.convertHiraganaToKatakana = $('#translation-convert-hiragana-to-katakana').val(); - options.translation.convertKatakanaToHiragana = $('#translation-convert-katakana-to-hiragana').val(); - options.translation.collapseEmphaticSequences = $('#translation-collapse-emphatic-sequences').val(); - - options.parsing.enableScanningParser = $('#parsing-scan-enable').prop('checked'); - options.parsing.enableMecabParser = $('#parsing-mecab-enable').prop('checked'); - options.parsing.termSpacing = $('#parsing-term-spacing').prop('checked'); - options.parsing.readingMode = $('#parsing-reading-mode').val(); - - options.anki.enable = $('#anki-enable').prop('checked'); - options.anki.tags = utilBackgroundIsolate($('#card-tags').val().split(/[,; ]+/)); - options.anki.sentenceExt = parseInt($('#sentence-detection-extent').val(), 10); - options.anki.server = $('#interface-server').val(); - options.anki.duplicateScope = $('#duplicate-scope').val(); - options.anki.screenshot.format = $('#screenshot-format').val(); - options.anki.screenshot.quality = parseInt($('#screenshot-quality').val(), 10); + _setDocumentAttribute(value, metadata, element) { + document.documentElement.setAttribute(element.dataset.documentAttribute, `${value}`); + return value; } - async _onFormOptionsChanged() { - const options = await this._settingsController.getOptionsMutable(); - this._formRead(options); - this._formUpdateVisibility(options); - await this._settingsController.save(); + _splitTags(value) { + return `${value}`.split(/[,; ]+/).filter((v) => !!v); } - _formUpdateVisibility(options) { - document.documentElement.dataset.optionsAnkiEnable = `${!!options.anki.enable}`; - document.documentElement.dataset.optionsGeneralDebugInfo = `${!!options.general.debugInfo}`; - document.documentElement.dataset.optionsGeneralShowAdvanced = `${!!options.general.showAdvanced}`; - document.documentElement.dataset.optionsGeneralResultOutputMode = `${options.general.resultOutputMode}`; + _joinTags(value) { + return value.join(' '); } } diff --git a/ext/bg/js/settings/settings-controller.js b/ext/bg/js/settings/settings-controller.js index 0d7abaa9..9224aedf 100644 --- a/ext/bg/js/settings/settings-controller.js +++ b/ext/bg/js/settings/settings-controller.js @@ -28,6 +28,10 @@ class SettingsController extends EventDispatcher { this._source = yomichan.generateId(16); } + get source() { + return this._source; + } + get profileIndex() { return this._profileIndex; } diff --git a/ext/bg/settings.html b/ext/bg/settings.html index bab62519..1baeeced 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -135,7 +135,7 @@

    General Options

    - +
    @@ -143,52 +143,52 @@
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - @@ -197,7 +197,7 @@
    - @@ -205,26 +205,26 @@
    - +
    - +
    -
    - @@ -239,11 +239,11 @@
    - +
    - +
    @@ -252,11 +252,11 @@
    - +
    - +
    @@ -265,11 +265,11 @@
    - +
    - +
    @@ -278,14 +278,14 @@
    -
    - @@ -298,11 +298,11 @@
    -
    +
    -
    +
    @@ -324,22 +324,22 @@

    Audio Options

    - +
    - +
    - +