diff --git a/ext/css/settings.css b/ext/css/settings.css index 6bc377c2..f0070a1f 100644 --- a/ext/css/settings.css +++ b/ext/css/settings.css @@ -1313,65 +1313,6 @@ body.preview-sidebar-visible .fab-container-item.fab-container-item-popup-previe display: none; } -.dictionary-info { - display: flex; - flex-flow: row nowrap; - align-items: center; -} -.dictionary-info-label { - margin-left: 1em; -} - -.dictionary-title { - color: inherit; - transition: color var(--animation-duration) ease-in-out; -} -.dictionary-item[data-enabled=false] .dictionary-title { - color: var(--text-color-light2); -} - -.dictionary-list { - display: flex; - flex-flow: column nowrap; - width: 100%; -} -.dictionary-list>.settings-item, -.dictionary-list>.settings-item+.settings-item { - margin-left: calc(var(--modal-padding-horizontal) * -1); - margin-right: calc(var(--modal-padding-horizontal) * -1); - border-top: var(--thin-border-size) solid var(--separator-color2); -} -.dictionary-details-table { - display: table; - width: 100%; -} -.dictionary-details-entry { - display: table-row; -} -.dictionary-details-entry+.dictionary-details-entry>* { - padding-top: 0.25em; -} -.dictionary-details-entry-label { - display: table-cell; - font-weight: bold; - white-space: nowrap; - padding-right: 0.5em; -} -.dictionary-details-entry-info { - display: table-cell; - white-space: pre-line; -} -.dictionary-counts { - width: 100%; - box-sizing: border-box; - font-size: inherit; - max-height: 10em; - line-height: 1.25; - font-family: 'Courier New', Courier, monospace; - white-space: pre; - overflow: auto; -} - .profile-add-button-container { display: flex; flex-flow: row nowrap; @@ -2096,28 +2037,47 @@ button.hotkey-list-item-enabled-button[data-scope-count='0'] { top: calc(1em * (3 / var(--font-size-no-units))); } -.warning-badge { +.badge { position: relative; width: var(--badge-size); height: var(--badge-size); margin: 0; padding: 0; - background-color: var(--warning-color-light); border-radius: 50%; box-shadow: var(--shadow-vertical); } -.warning-badge:not([hidden]) { +.badge:not([hidden]) { display: block; } -.warning-badge>.icon { +.badge>.icon { display: block; position: absolute; left: 0; top: 0; right: 0; bottom: 0; +} +.badge.badge-small-icon>.icon { + margin: calc(1em / var(--font-size-no-units)); +} +.badge.info-badge { + background-color: var(--accent-color-lighter); +} +.badge.info-badge>.icon { + background-color: var(--accent-color); +} +.badge.warning-badge { + background-color: var(--warning-color-light); +} +.badge.warning-badge>.icon { background-color: var(--warning-color); } +.badge.danger-badge { + background-color: var(--danger-color-lighter); +} +.badge.danger-badge>.icon { + background-color: var(--danger-color); +} .collapsible-dictionary-list { width: 100%; @@ -2160,6 +2120,107 @@ button.hotkey-list-item-enabled-button[data-scope-count='0'] { } +/* Dictionary settings */ +.dictionary-list { + width: 100%; + display: grid; + grid-template-columns: auto 1fr auto auto; + grid-template-rows: auto; + place-items: center start; + margin-top: 0.5em; +} +.dictionary-list[data-count='0']>.dictionary-item-top { + display: none; +} +.dictionary-item-button-height { + height: var(--icon-button-size); +} +.dictionary-item { + display: flex; + flex-flow: row nowrap; + align-items: center; + border-top: var(--thin-border-size) solid var(--separator-color2); +} +.dictionary-item-enabled-toggle-container { + margin-right: 0.5em; +} +.dictionary-item-title-container { + flex: 1 1 auto; + display: flex; + flex-flow: row nowrap; + align-items: center; + margin-right: 0.5em; +} +.dictionary-title { + color: inherit; + transition: color var(--animation-duration) ease-in-out; +} +.dictionary-item[data-enabled=false] .dictionary-title { + color: var(--text-color-light2); +} +input[type=number].dictionary-priority { + margin-top: 0; + margin-right: 0.5em; +} +.dictionary-outdated-button, +.dictionary-integrity-button { + --button-content-color: transparent; + --button-border-color: transparent; + --button-background-color: transparent; + --button-shadow: none; + + --button-hover-content-color: transparent; + --button-hover-border-color: transparent; + --button-hover-background-color: transparent; + --button-hover-shadow: none; + + --button-active-content-color: transparent; + --button-active-border-color: transparent; + --button-active-background-color: transparent; + --button-active-shadow: none; + + --button-disabled-content-color: transparent; + --button-disabled-border-color: transparent; + --button-disabled-background-color: transparent; + --button-disabled-shadow: none; + + --button-padding-vertical: 0; + --button-padding-horizontal: 0; + + margin-left: 0.375em; +} +.dictionary-details-table { + display: table; + width: 100%; +} +.dictionary-details-entry { + display: table-row; +} +.dictionary-details-entry+.dictionary-details-entry>* { + padding-top: 0.25em; +} +.dictionary-details-entry-label { + display: table-cell; + font-weight: bold; + white-space: nowrap; + padding-right: 0.5em; +} +.dictionary-details-entry-info { + display: table-cell; + white-space: pre-line; +} +.dictionary-counts { + width: 100%; + box-sizing: border-box; + font-size: inherit; + max-height: 10em; + line-height: 1.25; + font-family: 'Courier New', Courier, monospace; + white-space: pre; + overflow: auto; +} + + /* Generic layouts */ .margin-above { margin-top: 0.85em; diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index f000aa62..6d358f25 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -20,19 +20,21 @@ */ class DictionaryEntry { - constructor(dictionaryController, node, index, dictionaryInfo) { + constructor(dictionaryController, fragment, index, dictionaryInfo) { this._dictionaryController = dictionaryController; - this._node = node; this._index = index; this._dictionaryInfo = dictionaryInfo; this._eventListeners = new EventListenerCollection(); - this._detailsContainer = null; - this._hasDetails = false; - this._hasCounts = false; - } - - get node() { - return this._node; + this._counts = null; + this._nodes = [...fragment.childNodes]; + this._enabledCheckbox = fragment.querySelector('.dictionary-enabled'); + this._priorityInput = fragment.querySelector('.dictionary-priority'); + this._menuButton = fragment.querySelector('.dictionary-menu-button'); + this._outdatedButton = fragment.querySelector('.dictionary-outdated-button'); + this._integrityButton = fragment.querySelector('.dictionary-integrity-button'); + this._titleNode = fragment.querySelector('.dictionary-title'); + this._versionNode = fragment.querySelector('.dictionary-version'); + this._titleContainer = fragment.querySelector('.dictionary-item-title-container'); } get dictionaryTitle() { @@ -40,98 +42,80 @@ class DictionaryEntry { } prepare() { - const node = this._node; const index = this._index; - const {title, revision, prefixWildcardsSupported, version} = this._dictionaryInfo; + const {title, revision, version} = this._dictionaryInfo; - this._detailsContainer = node.querySelector('.dictionary-details'); - - const enabledCheckbox = node.querySelector('.dictionary-enabled'); - const priorityInput = node.querySelector('.dictionary-priority'); - const menuButton = node.querySelector('.dictionary-menu-button'); - const detailsTable = node.querySelector('.dictionary-details-table'); - const outdatedContainer = node.querySelector('.dictionary-outdated-notification'); - const titleNode = node.querySelector('.dictionary-title'); - const versionNode = node.querySelector('.dictionary-version'); - const wildcardSupportedCheckbox = node.querySelector('.dictionary-prefix-wildcard-searches-supported'); - - const hasDetails = (detailsTable !== null && this._setupDetails(detailsTable)); - this._hasDetails = hasDetails; - - titleNode.textContent = title; - versionNode.textContent = `rev.${revision}`; - if (wildcardSupportedCheckbox !== null) { - wildcardSupportedCheckbox.checked = !!prefixWildcardsSupported; - } - if (outdatedContainer !== null) { - outdatedContainer.hidden = (version >= 3); - } - if (enabledCheckbox !== null) { - enabledCheckbox.dataset.setting = `dictionaries[${index}].enabled`; - this._eventListeners.addEventListener(enabledCheckbox, 'settingChanged', this._onEnabledChanged.bind(this), false); - } - if (priorityInput !== null) { - priorityInput.dataset.setting = `dictionaries[${index}].priority`; - } - if (menuButton !== null) { - this._eventListeners.addEventListener(menuButton, 'menuOpen', this._onMenuOpen.bind(this), false); - this._eventListeners.addEventListener(menuButton, 'menuClose', this._onMenuClose.bind(this), false); - } + this._titleNode.textContent = title; + this._versionNode.textContent = `rev.${revision}`; + this._outdatedButton.hidden = (version >= 3); + this._priorityInput.dataset.setting = `dictionaries[${index}].priority`; + this._enabledCheckbox.dataset.setting = `dictionaries[${index}].enabled`; + this._eventListeners.addEventListener(this._enabledCheckbox, 'settingChanged', this._onEnabledChanged.bind(this), false); + this._eventListeners.addEventListener(this._menuButton, 'menuClose', this._onMenuClose.bind(this), false); + this._eventListeners.addEventListener(this._outdatedButton, 'click', this._onOutdatedButtonClick.bind(this), false); + this._eventListeners.addEventListener(this._integrityButton, 'click', this._onIntegrityButtonClick.bind(this), false); } cleanup() { this._eventListeners.removeAllEventListeners(); - const node = this._node; - if (node.parentNode !== null) { - node.parentNode.removeChild(node); + for (const node of this._nodes) { + if (node.parentNode !== null) { + node.parentNode.removeChild(node); + } } + this._nodes = []; } setCounts(counts) { - const node = this._node.querySelector('.dictionary-counts'); - node.textContent = JSON.stringify({info: this._dictionaryInfo, counts}, null, 4); - node.hidden = false; - this._hasCounts = true; + this._counts = counts; + this._integrityButton.hidden = false; + } + + setEnabled(value) { + this._enabledCheckbox.checked = value; } // Private - _onMenuOpen(e) { - const bodyNode = e.detail.menu.bodyNode; - const showDetails = bodyNode.querySelector('.popup-menu-item[data-menu-action="showDetails"]'); - const hideDetails = bodyNode.querySelector('.popup-menu-item[data-menu-action="hideDetails"]'); - const hasDetails = (this._detailsContainer !== null); - const detailsVisible = (hasDetails && !this._detailsContainer.hidden); - if (showDetails !== null) { - showDetails.hidden = detailsVisible; - showDetails.disabled = !hasDetails; - } - if (hideDetails !== null) { - hideDetails.hidden = !detailsVisible; - hideDetails.disabled = !hasDetails; - } - } - _onMenuClose(e) { switch (e.detail.action) { case 'delete': this._delete(); break; case 'showDetails': - if (this._detailsContainer !== null) { this._detailsContainer.hidden = false; } - break; - case 'hideDetails': - if (this._detailsContainer !== null) { this._detailsContainer.hidden = true; } + this._showDetails(); break; } } _onEnabledChanged(e) { const {detail: {value}} = e; - this._node.dataset.enabled = `${value}`; + this._titleContainer.dataset.enabled = `${value}`; this._dictionaryController.updateDictionariesEnabled(); } + _onOutdatedButtonClick() { + this._showDetails(); + } + + _onIntegrityButtonClick() { + this._showDetails(); + } + + _showDetails() { + const {title, revision, version} = this._dictionaryInfo; + + const modal = this._dictionaryController.modalController.getModal('dictionary-details'); + + modal.node.querySelector('.dictionary-title').textContent = title; + modal.node.querySelector('.dictionary-version').textContent = `rev.${revision}`; + modal.node.querySelector('.dictionary-outdated-notification').hidden = (version >= 3); + modal.node.querySelector('.dictionary-counts').textContent = this._counts !== null ? JSON.stringify(this._counts, null, 4) : ''; + this._setupDetails(modal.node.querySelector('.dictionary-details-table')); + + modal.setVisible(true); + } + _setupDetails(detailsTable) { const targets = [ ['Author', 'author'], @@ -156,6 +140,7 @@ class DictionaryEntry { any = true; } + detailsTable.textContent = ''; detailsTable.appendChild(fragment); return any; } @@ -165,6 +150,57 @@ class DictionaryEntry { } } +class DictionaryExtraInfo { + constructor(parent, totalCounts, remainders, totalRemainder) { + this._parent = parent; + this._totalCounts = totalCounts; + this._remainders = remainders; + this._totalRemainder = totalRemainder; + this._eventListeners = new EventListenerCollection(); + this._nodes = null; + } + + prepare(container) { + const fragment = this._parent.instantiateTemplateFragment('dictionary-extra'); + this._nodes = [...fragment.childNodes]; + + this._setTitle(fragment.querySelector('.dictionary-total-count')); + this._eventListeners.addEventListener(fragment.querySelector('.dictionary-integrity-button'), 'click', this._onIntegrityButtonClick.bind(this), false); + + container.appendChild(fragment); + } + + cleanup() { + this._eventListeners.removeAllEventListeners(); + for (const node of this._nodes) { + if (node.parentNode !== null) { + node.parentNode.removeChild(node); + } + } + this._nodes = []; + } + + // Private + + _onIntegrityButtonClick() { + this._showDetails(); + } + + _showDetails() { + const modal = this._parent.modalController.getModal('dictionary-extra-data'); + + const info = {counts: this._totalCounts, remainders: this._remainders}; + modal.node.querySelector('.dictionary-counts').textContent = JSON.stringify(info, null, 4); + this._setTitle(modal.node.querySelector('.dictionary-total-count')); + + modal.setVisible(true); + } + + _setTitle(node) { + node.textContent = `${this._totalRemainder} item${this._totalRemainder !== 1 ? 's' : ''}`; + } +} + class DictionaryController { constructor(settingsController, modalController, statusFooter) { this._settingsController = settingsController; @@ -176,34 +212,40 @@ class DictionaryController { this._checkingIntegrity = false; this._checkIntegrityButton = null; this._dictionaryEntryContainer = null; - this._integrityExtraInfoContainer = null; this._dictionaryInstallCountNode = null; this._dictionaryEnabledCountNode = null; this._noDictionariesInstalledWarnings = null; this._noDictionariesEnabledWarnings = null; this._deleteDictionaryModal = null; - this._integrityExtraInfoNode = null; + this._allCheckbox = null; + this._extraInfo = null; this._isDeleting = false; } + get modalController() { + return this._modalController; + } + async prepare() { this._checkIntegrityButton = document.querySelector('#dictionary-check-integrity'); this._dictionaryEntryContainer = document.querySelector('#dictionary-list'); - this._integrityExtraInfoContainer = document.querySelector('#dictionary-list-extra'); this._dictionaryInstallCountNode = document.querySelector('#dictionary-install-count'); this._dictionaryEnabledCountNode = document.querySelector('#dictionary-enabled-count'); this._noDictionariesInstalledWarnings = document.querySelectorAll('.no-dictionaries-installed-warning'); this._noDictionariesEnabledWarnings = document.querySelectorAll('.no-dictionaries-enabled-warning'); this._deleteDictionaryModal = this._modalController.getModal('dictionary-confirm-delete'); + this._allCheckbox = document.querySelector('#all-dictionaries-enabled'); yomichan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); - + this._allCheckbox.addEventListener('change', this._onAllCheckboxChange.bind(this), false); document.querySelector('#dictionary-confirm-delete-button').addEventListener('click', this._onDictionaryConfirmDelete.bind(this), false); if (this._checkIntegrityButton !== null) { this._checkIntegrityButton.addEventListener('click', this._onCheckIntegrityButtonClick.bind(this), false); } + this._updateDictionaryEntryCount(); + await this._onDatabaseUpdated(); } @@ -219,6 +261,10 @@ class DictionaryController { return this._settingsController.instantiateTemplate(name); } + instantiateTemplateFragment(name) { + return this._settingsController.instantiateTemplateFragment(name); + } + async updateDictionariesEnabled() { const options = await this._settingsController.getOptions(); this._updateDictionariesEnabledWarnings(options); @@ -303,6 +349,12 @@ class DictionaryController { await this._updateEntries(); } + _onAllCheckboxChange() { + const value = this._allCheckbox.checked; + this._allCheckbox.checked = !value; + this._setAllDictionariesEnabled(value); + } + async _updateEntries() { const dictionaries = this._dictionaries; this._updateMainDictionarySelectOptions(dictionaries); @@ -311,6 +363,7 @@ class DictionaryController { entry.cleanup(); } this._dictionaryEntries = []; + this._updateDictionaryEntryCount(); if (this._dictionaryInstallCountNode !== null) { this._dictionaryInstallCountNode.textContent = `${dictionaries.length}`; @@ -341,29 +394,40 @@ class DictionaryController { } _updateDictionariesEnabledWarnings(options) { - let enabledCount = 0; + const {dictionaries} = options; + let enabledDictionaryCountValid = 0; + let enabledDictionaryCount = 0; + const dictionaryCount = dictionaries.length; if (this._dictionaries !== null) { const enabledDictionaries = new Set(); - for (const {name, enabled} of options.dictionaries) { + for (const {name, enabled} of dictionaries) { if (enabled) { + ++enabledDictionaryCount; enabledDictionaries.add(name); } } for (const {title} of this._dictionaries) { if (enabledDictionaries.has(title)) { - ++enabledCount; + ++enabledDictionaryCountValid; } } } - const hasEnabledDictionary = (enabledCount > 0); + const hasEnabledDictionary = (enabledDictionaryCountValid > 0); for (const node of this._noDictionariesEnabledWarnings) { node.hidden = hasEnabledDictionary; } if (this._dictionaryEnabledCountNode !== null) { - this._dictionaryEnabledCountNode.textContent = `${enabledCount}`; + this._dictionaryEnabledCountNode.textContent = `${enabledDictionaryCountValid}`; + } + + this._allCheckbox.checked = (enabledDictionaryCount >= dictionaryCount); + + const entries = this._dictionaryEntries; + for (let i = 0, ii = Math.min(entries.length, dictionaryCount); i < ii; ++i) { + entries[i].setEnabled(dictionaries[i].enabled); } } @@ -447,43 +511,29 @@ class DictionaryController { totalRemainder += remainders[key]; } - this._cleanupExtra(); + if (this._extraInfo !== null) { + this._extraInfo.cleanup(); + this._extraInfo = null; + } + if (totalRemainder > 0) { - this.extra = this._createExtra(totalCounts, remainders, totalRemainder); + this._extraInfo = new DictionaryExtraInfo(this, totalCounts, remainders, totalRemainder); + this._extraInfo.prepare(this._dictionaryEntryContainer); } } - _createExtra(totalCounts, remainders, totalRemainder) { - const node = this.instantiateTemplate('dictionary-extra'); - this._integrityExtraInfoNode = node; - - node.querySelector('.dictionary-total-count').textContent = `${totalRemainder} item${totalRemainder !== 1 ? 's' : ''}`; - - const n = node.querySelector('.dictionary-counts'); - n.textContent = JSON.stringify({counts: totalCounts, remainders}, null, 4); - n.hidden = false; - - this._integrityExtraInfoContainer.appendChild(node); - } - - _cleanupExtra() { - const node = this._integrityExtraInfoNode; - if (node === null) { return; } - this._integrityExtraInfoNode = null; - - const parent = node.parentNode; - if (parent === null) { return; } - - parent.removeChild(node); - } - _createDictionaryEntry(index, dictionaryInfo) { - const node = this.instantiateTemplate('dictionary'); - this._dictionaryEntryContainer.appendChild(node); + const fragment = this.instantiateTemplateFragment('dictionary'); - const entry = new DictionaryEntry(this, node, index, dictionaryInfo); + const entry = new DictionaryEntry(this, fragment, index, dictionaryInfo); this._dictionaryEntries.push(entry); entry.prepare(); + + const container = this._dictionaryEntryContainer; + const relative = container.querySelector('.dictionary-item-bottom'); + container.insertBefore(fragment, relative); + + this._updateDictionaryEntryCount(); } async _deleteDictionary(dictionaryTitle) { @@ -589,4 +639,25 @@ class DictionaryController { _triggerStorageChanged() { yomichan.trigger('storageChanged'); } + + _updateDictionaryEntryCount() { + this._dictionaryEntryContainer.dataset.count = `${this._dictionaryEntries.length}`; + } + + async _setAllDictionariesEnabled(value) { + const options = await this._settingsController.getOptions(); + const {dictionaries} = options; + + const targets = []; + for (let i = 0, ii = dictionaries.length; i < ii; ++i) { + targets.push({ + action: 'set', + path: `dictionaries[${i}].enabled`, + value + }); + } + await this._settingsController.modifyProfileSettings(targets); + + await this.updateDictionariesEnabled(); + } } diff --git a/ext/settings.html b/ext/settings.html index 872dac4d..eee06a60 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -22,7 +22,7 @@ @@ -2166,86 +2170,102 @@ - - - + + - - -