diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2019-10-10 21:28:27 -0400 |
---|---|---|
committer | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2019-10-10 21:36:09 -0400 |
commit | 6208d6c93b3921ac8726140869ae71d784569b48 (patch) | |
tree | 1f2dd46af5c567841c9b882343bcf3905d077ae9 | |
parent | 1dc8bf77ca129555f0e56ef0f890105c14ab94ff (diff) |
Add UI for editing audio playback sources
-rw-r--r-- | ext/bg/css/settings.css | 28 | ||||
-rw-r--r-- | ext/bg/js/audio-ui.js | 131 | ||||
-rw-r--r-- | ext/bg/js/settings.js | 17 | ||||
-rw-r--r-- | ext/bg/settings.html | 28 |
4 files changed, 189 insertions, 15 deletions
diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css index 12bbe8a8..6284058a 100644 --- a/ext/bg/css/settings.css +++ b/ext/bg/css/settings.css @@ -89,19 +89,24 @@ text-align-last: center; } -.condition-group>.condition>div:first-child { +.condition-group>.condition>*:first-child, +.audio-source-list>.audio-source>*:first-child { border-bottom-left-radius: 0; } -.condition-group>.condition:nth-child(n+2)>div:first-child { +.condition-group>.condition:nth-child(n+2)>*:first-child, +.audio-source-list>.audio-source:nth-child(n+2)>*:first-child { border-top-left-radius: 0; } -.condition-group>.condition:nth-child(n+2)>div:last-child>button { +.condition-group>.condition:nth-child(n+2)>div:last-child>button, +.audio-source-list>.audio-source:nth-child(n+2)>*:last-child>button { border-top-right-radius: 0; } -.condition-group>.condition:nth-last-child(n+2)>div:last-child>button { +.condition-group>.condition:nth-last-child(n+2)>div:last-child>button, +.audio-source-list>.audio-source:nth-last-child(n+2)>*:last-child>button { border-bottom-right-radius: 0; } -.condition-group-options>.condition-add { +.condition-group-options>.condition-add, +.audio-source-options>.audio-source-add { border-top-left-radius: 0; border-top-right-radius: 0; } @@ -110,6 +115,19 @@ display: none; } +.audio-source-list { + counter-reset: audio-source-id; +} +.audio-source-list .audio-source-prefix { + flex: 0 0 auto; + width: 39px; + text-align: center; +} +.audio-source-list .audio-source-prefix:after { + counter-increment: audio-source-id; + content: counter(audio-source-id); +} + #custom-popup-css { width: 100%; min-height: 34px; diff --git a/ext/bg/js/audio-ui.js b/ext/bg/js/audio-ui.js new file mode 100644 index 00000000..381129ac --- /dev/null +++ b/ext/bg/js/audio-ui.js @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2019 Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * 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 <http://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.empty(); + + for (const audioSource of toIterable(audioSources)) { + this.children.push(new AudioSourceUI.AudioSource(this, audioSource, this.children.length)); + } + + this.addButton.on('click', () => this.onAddAudioSource()); + } + + cleanup() { + for (const child of this.children) { + child.cleanup(); + } + + this.addButton.off('click'); + this.container.empty(); + } + + 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').appendTo(parent.container); + this.select = this.container.find('.audio-source-select'); + this.removeButton = this.container.find('.audio-source-remove'); + + this.select.val(audioSource); + + this.select.on('change', () => this.onSelectChanged()); + this.removeButton.on('click', () => this.onRemoveClicked()); + } + + cleanup() { + this.select.off('change'); + this.removeButton.off('click'); + this.container.remove(); + } + + save() { + this.parent.save(); + } + + onSelectChanged() { + this.audioSource = this.select.val(); + this.parent.audioSources[this.index] = this.audioSource; + this.save(); + } + + onRemoveClicked() { + this.parent.remove(this); + this.save(); + } +}; diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 89ba046d..f3b5ff16 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -158,7 +158,7 @@ function formSetupEventListeners() { $('#dict-file-button').click(onDictionaryImportButtonClick); $('#field-templates-reset').click(utilAsync(onAnkiFieldTemplatesReset)); - $('input, select, textarea').not('.anki-model').not('.profile-form *').change(utilAsync(onFormOptionsChanged)); + $('input, select, textarea').not('.anki-model').not('.ignore-form-changes *').change(utilAsync(onFormOptionsChanged)); $('.anki-model').change(utilAsync(onAnkiModelChanged)); } @@ -248,6 +248,7 @@ async function onReady() { showExtensionInformation(); formSetupEventListeners(); + await audioSettingsInitialize(); await profileOptionsSetup(); storageInfoInitialize(); @@ -259,6 +260,20 @@ $(document).ready(utilAsync(onReady)); /* + * Audio + */ + +let audioSourceUI = null; + +async function audioSettingsInitialize() { + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); + audioSourceUI = new AudioSourceUI.Container(options.audio.sources, $('.audio-source-list'), $('.audio-source-add')); + audioSourceUI.save = () => apiOptionsSave(); +} + + +/* * Remote options updates */ diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 168dd040..e4710283 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -14,7 +14,7 @@ <h1>Yomichan Options</h1> </div> - <div class="profile-form"> + <div class="profile-form ignore-form-changes"> <h3>Profiles</h3> <p class="help-block"> @@ -258,14 +258,23 @@ <input type="text" id="audio-custom-source" class="form-control" placeholder="Example: http://localhost/audio.mp3?expression={expression}&reading={reading}"> </div> - <div class="form-group"> - <label for="audio-playback-source">Audio playback source</label> - <select class="form-control" id="audio-playback-source"> - <option value="disabled">Disabled</option> - <option value="jpod101">JapanesePod101</option> - <option value="jpod101-alternate">JapanesePod101 (alternate)</option> - <option value="jisho">Jisho.org</option> - </select> + <div class="form-group ignore-form-changes"> + <label>Audio playback sources</label> + <div class="audio-source-list"></div> + <div class="input-group audio-source-options"> + <button class="btn btn-default audio-source-add" title="Add audio playback source"><span class="glyphicon glyphicon-plus"></span></button> + </div> + + <template id="audio-source-template"><div class="input-group audio-source"> + <div class="input-group-addon audio-source-prefix"></div> + <select class="form-control audio-source-select"> + <option value="jpod101">JapanesePod101</option> + <option value="jpod101-alternate">JapanesePod101 (alternate)</option> + <option value="jisho">Jisho.org</option> + <option value="custom">Custom</option> + </select> + <div class="input-group-btn"><button class="btn btn-danger audio-source-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></div> + </div></template> </div> </div> @@ -587,6 +596,7 @@ <script src="/bg/js/anki.js"></script> <script src="/bg/js/api.js"></script> + <script src="/bg/js/audio-ui.js"></script> <script src="/bg/js/conditions.js"></script> <script src="/bg/js/conditions-ui.js"></script> <script src="/bg/js/dictionary.js"></script> |