From bcbd413e571d772a4438f57138169ad1a6a3b5a8 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 31 Mar 2021 18:17:28 -0400 Subject: Definition collapsing when overflowing (#1575) * Add double-down-chevron.svg * Add options * Update dictionary importers * Update settings * Add support for collapsible definitions * Improve case when there is a very small amount of overflow * Fix incorrect enabled state of newly imported dictionaries --- ext/js/pages/settings/backup-controller.js | 2 +- .../settings/collapsible-dictionary-controller.js | 159 +++++++++++++++++++++ ext/js/pages/settings/dictionary-controller.js | 19 +-- .../pages/settings/dictionary-import-controller.js | 11 +- ext/js/pages/settings/settings-main.js | 4 + 5 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 ext/js/pages/settings/collapsible-dictionary-controller.js (limited to 'ext/js/pages/settings') diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 6f6614b6..933f0e2a 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -404,7 +404,7 @@ class BackupController { const dictionaries = await this._settingsController.getDictionaryInfo(); for (const {options: {dictionaries: optionsDictionaries}} of optionsFull.profiles) { for (const {title} of dictionaries) { - optionsDictionaries[title] = DictionaryController.createDefaultDictionarySettings(); + optionsDictionaries[title] = DictionaryController.createDefaultDictionarySettings(false); } } diff --git a/ext/js/pages/settings/collapsible-dictionary-controller.js b/ext/js/pages/settings/collapsible-dictionary-controller.js new file mode 100644 index 00000000..a3a1df62 --- /dev/null +++ b/ext/js/pages/settings/collapsible-dictionary-controller.js @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2021 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 + * ObjectPropertyAccessor + */ + +class CollapsibleDictionaryController { + constructor(settingsController) { + this._settingsController = settingsController; + this._getDictionaryInfoToken = null; + this._dictionaryInfoMap = new Map(); + this._container = null; + this._selects = []; + this._allSelect = null; + this._eventListeners = new EventListenerCollection(); + } + + async prepare() { + this._container = document.querySelector('#collapsible-dictionary-list'); + + await this._onDatabaseUpdated(); + await this._updateOptions(); + + yomichan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); + this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); + } + + // Private + + async _onDatabaseUpdated() { + const token = {}; + this._getDictionaryInfoToken = token; + const dictionaries = await this._settingsController.getDictionaryInfo(); + if (this._getDictionaryInfoToken !== token) { return; } + this._getDictionaryInfoToken = null; + + this._dictionaryInfoMap.clear(); + for (const entry of dictionaries) { + this._dictionaryInfoMap.set(entry.title, entry); + } + + await this._updateOptions(); + } + + _onOptionsChanged({options}) { + this._eventListeners.removeAllEventListeners(); + this._selects = []; + + const {dictionaries} = options; + + const fragment = document.createDocumentFragment(); + + this._setupAllSelect(fragment, options); + + for (const dictionary of Object.keys(dictionaries)) { + const dictionaryInfo = this._dictionaryInfoMap.get(dictionary); + if (typeof dictionaryInfo === 'undefined') { continue; } + + const select = this._addSelect(fragment, dictionary, `rev.${dictionaryInfo.revision}`); + select.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', dictionary, 'definitionsCollapsible']); + this._eventListeners.addEventListener(select, 'settingChanged', this._onDefinitionsCollapsibleChange.bind(this), false); + + this._selects.push(select); + } + + this._container.textContent = ''; + this._container.appendChild(fragment); + } + + _onDefinitionsCollapsibleChange() { + this._updateAllSelectFresh(); + } + + _onAllSelectChange(e) { + const {value} = e.currentTarget; + if (value === 'varies') { return; } + this._setDefinitionsCollapsibleAll(value); + } + + _setupAllSelect(fragment, options) { + const select = this._addSelect(fragment, 'All', ''); + + const option = document.createElement('option'); + option.value = 'varies'; + option.textContent = 'Varies'; + option.disabled = true; + select.appendChild(option); + + this._eventListeners.addEventListener(select, 'change', this._onAllSelectChange.bind(this), false); + + this._allSelect = select; + this._updateAllSelect(options); + } + + _addSelect(fragment, dictionary, version) { + const node = this._settingsController.instantiateTemplate('collapsible-dictionary-item'); + fragment.appendChild(node); + + const nameNode = node.querySelector('.dictionary-title'); + nameNode.textContent = dictionary; + + const versionNode = node.querySelector('.dictionary-version'); + versionNode.textContent = version; + + return node.querySelector('.definitions-collapsible'); + } + + async _updateOptions() { + const options = await this._settingsController.getOptions(); + this._onOptionsChanged({options}); + } + + async _updateAllSelectFresh() { + this._updateAllSelect(await this._settingsController.getOptions()); + } + + _updateAllSelect(options) { + let value = null; + let varies = false; + for (const {definitionsCollapsible} of Object.values(options.dictionaries)) { + if (value === null) { + value = definitionsCollapsible; + } else if (value !== definitionsCollapsible) { + varies = true; + break; + } + } + + this._allSelect.value = (varies || value === null ? 'varies' : value); + } + + async _setDefinitionsCollapsibleAll(value) { + const options = await this._settingsController.getOptions(); + const targets = []; + for (const dictionary of Object.keys(options.dictionaries)) { + const path = ObjectPropertyAccessor.getPathString(['dictionaries', dictionary, 'definitionsCollapsible']); + targets.push({action: 'set', path, value}); + } + await this._settingsController.modifyProfileSettings(targets); + for (const select of this._selects) { + select.value = value; + } + } +} \ No newline at end of file diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 17abfa13..65edcb67 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -248,6 +248,15 @@ class DictionaryController { this._updateDictionariesEnabledWarnings(options); } + static createDefaultDictionarySettings(enabled) { + return { + priority: 0, + enabled, + allowSecondarySearches: false, + definitionsCollapsible: 'not-collapsible' + }; + } + // Private _onOptionsChanged({options}) { @@ -535,7 +544,7 @@ class DictionaryController { targets.push({ action: 'set', path, - value: DictionaryController.createDefaultDictionarySettings() + value: DictionaryController.createDefaultDictionarySettings(false) }); } } @@ -548,12 +557,4 @@ class DictionaryController { _triggerStorageChanged() { yomichan.trigger('storageChanged'); } - - static createDefaultDictionarySettings() { - return { - enabled: false, - allowSecondarySearches: false, - priority: 0 - }; - } } diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index ce724263..afa45899 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -16,6 +16,7 @@ */ /* global + * DictionaryController * DictionaryDatabase * DictionaryImporter * ObjectPropertyAccessor @@ -212,7 +213,7 @@ class DictionaryImportController { const profileCount = optionsFull.profiles.length; for (let i = 0; i < profileCount; ++i) { const {options} = optionsFull.profiles[i]; - const value = this._createDictionaryOptions(); + const value = DictionaryController.createDefaultDictionarySettings(true); const path1 = ObjectPropertyAccessor.getPathString(['profiles', i, 'options', 'dictionaries', title]); targets.push({action: 'set', path: path1, value}); @@ -291,14 +292,6 @@ class DictionaryImportController { }); } - _createDictionaryOptions() { - return { - priority: 0, - enabled: true, - allowSecondarySearches: false - }; - } - _errorToString(error) { error = (typeof error.toString === 'function' ? error.toString() : `${error}`); diff --git a/ext/js/pages/settings/settings-main.js b/ext/js/pages/settings/settings-main.js index 3618836c..2560685c 100644 --- a/ext/js/pages/settings/settings-main.js +++ b/ext/js/pages/settings/settings-main.js @@ -20,6 +20,7 @@ * AnkiTemplatesController * AudioController * BackupController + * CollapsibleDictionaryController * DictionaryController * DictionaryImportController * DocumentFocusController @@ -146,6 +147,9 @@ async function setupGenericSettingsController(genericSettingController) { const mecabController = new MecabController(); mecabController.prepare(); + const collapsibleDictionaryController = new CollapsibleDictionaryController(settingsController); + collapsibleDictionaryController.prepare(); + await Promise.all(preparePromises); document.documentElement.dataset.loaded = 'true'; -- cgit v1.2.3