diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2020-05-29 19:56:38 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-05-29 19:56:38 -0400 | 
| commit | c62f980f37007743bed004ff43a82a8d7664dac6 (patch) | |
| tree | 06f4b723bc2ebd2a864d30b0e329d4d672d6dcee | |
| parent | 5f9889fd26f38396aa6ffaa5c669081b02467393 (diff) | |
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
| -rw-r--r-- | ext/bg/js/settings/audio-ui.js | 139 | ||||
| -rw-r--r-- | ext/bg/js/settings/audio.js | 255 | ||||
| -rw-r--r-- | ext/bg/js/settings/main.js | 4 | ||||
| -rw-r--r-- | ext/bg/settings.html | 1 | 
4 files changed, 178 insertions, 221 deletions
| 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 <https://www.gnu.org/licenses/>. - */ - -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 @@          <script src="/bg/js/settings/anki.js"></script>          <script src="/bg/js/settings/anki-templates.js"></script>          <script src="/bg/js/settings/audio.js"></script> -        <script src="/bg/js/settings/audio-ui.js"></script>          <script src="/bg/js/settings/backup.js"></script>          <script src="/bg/js/settings/conditions-ui.js"></script>          <script src="/bg/js/settings/dictionaries.js"></script> |