Dictionary controller refactor (#831)
* Create new DictionaryController * Update input disabling when modifying the database
This commit is contained in:
parent
8d28477562
commit
f997f01742
@ -377,6 +377,11 @@ html:root[data-operating-system=openbsd] [data-hide-for-operating-system~=openbs
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#dict-groups {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
.dict-details-container {
|
.dict-details-container {
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2019-2020 Yomichan Authors
|
* Copyright (C) 2020 Yomichan Authors
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,202 +16,90 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* global
|
/* global
|
||||||
|
* ObjectPropertyAccessor
|
||||||
* api
|
* api
|
||||||
* utilBackgroundIsolate
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class SettingsDictionaryListUI extends EventDispatcher {
|
class DictionaryEntry {
|
||||||
constructor(container, template, extraContainer, extraTemplate) {
|
constructor(dictionaryController, node, dictionaryInfo) {
|
||||||
super();
|
this._dictionaryController = dictionaryController;
|
||||||
this.container = container;
|
this._node = node;
|
||||||
this.template = template;
|
this._dictionaryInfo = dictionaryInfo;
|
||||||
this.extraContainer = extraContainer;
|
this._dictionaryTitle = dictionaryInfo.title;
|
||||||
this.extraTemplate = extraTemplate;
|
this._eventListeners = new EventListenerCollection();
|
||||||
this.optionsDictionaries = null;
|
this._enabledCheckbox = node.querySelector('.dict-enabled');
|
||||||
this.dictionaries = null;
|
this._allowSecondarySearchesCheckbox = node.querySelector('.dict-allow-secondary-searches');
|
||||||
this.dictionaryEntries = [];
|
this._priorityInput = node.querySelector('.dict-priority');
|
||||||
this.extra = null;
|
this._deleteButton = node.querySelector('.dict-delete-button');
|
||||||
|
this._detailsToggleLink = node.querySelector('.dict-details-toggle-link');
|
||||||
document.querySelector('#dict-delete-confirm').addEventListener('click', this.onDictionaryConfirmDelete.bind(this), false);
|
this._detailsContainer = node.querySelector('.dict-details');
|
||||||
|
this._detailsTable = node.querySelector('.dict-details-table');
|
||||||
}
|
}
|
||||||
|
|
||||||
setOptionsDictionaries(optionsDictionaries) {
|
get node() {
|
||||||
this.optionsDictionaries = optionsDictionaries;
|
return this._node;
|
||||||
if (this.dictionaries !== null) {
|
}
|
||||||
this.setDictionaries(this.dictionaries);
|
|
||||||
|
get dictionaryTitle() {
|
||||||
|
return this._dictionaryTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare() {
|
||||||
|
const node = this._node;
|
||||||
|
const dictionaryInfo = this._dictionaryInfo;
|
||||||
|
const {title, revision, prefixWildcardsSupported} = dictionaryInfo;
|
||||||
|
|
||||||
|
if (dictionaryInfo.version < 3) {
|
||||||
|
node.querySelector('.dict-outdated').hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.querySelector('.dict-title').textContent = title;
|
||||||
|
node.querySelector('.dict-revision').textContent = `rev.${revision}`;
|
||||||
|
node.querySelector('.dict-prefix-wildcard-searches-supported').checked = !!prefixWildcardsSupported;
|
||||||
|
|
||||||
|
this._setupDetails(dictionaryInfo);
|
||||||
|
|
||||||
|
this._enabledCheckbox.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', title, 'enabled']);
|
||||||
|
this._allowSecondarySearchesCheckbox.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', title, 'allowSecondarySearches']);
|
||||||
|
this._priorityInput.dataset.setting = ObjectPropertyAccessor.getPathString(['dictionaries', title, 'priority']);
|
||||||
|
|
||||||
|
this._eventListeners.addEventListener(this._deleteButton, 'click', this._onDeleteButtonClicked.bind(this), false);
|
||||||
|
this._eventListeners.addEventListener(this._detailsToggleLink, 'click', this._onDetailsToggleLinkClicked.bind(this), false);
|
||||||
|
this._eventListeners.addEventListener(this._priorityInput, 'settingChanged', this._onPriorityChanged.bind(this), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
this._eventListeners.removeAllEventListeners();
|
||||||
|
const node = this._node;
|
||||||
|
if (node.parentNode !== null) {
|
||||||
|
node.parentNode.removeChild(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setDictionaries(dictionaries) {
|
setCounts(counts) {
|
||||||
for (const dictionaryEntry of this.dictionaryEntries) {
|
const node = this._node.querySelector('.dict-counts');
|
||||||
dictionaryEntry.cleanup();
|
node.textContent = JSON.stringify({info: this._dictionaryInfo, counts}, null, 4);
|
||||||
|
node.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dictionaryEntries = [];
|
// Private
|
||||||
this.dictionaries = toIterable(dictionaries);
|
|
||||||
|
|
||||||
if (this.optionsDictionaries === null) {
|
_onDeleteButtonClicked(e) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let changed = false;
|
|
||||||
for (const dictionaryInfo of this.dictionaries) {
|
|
||||||
if (this.createEntry(dictionaryInfo)) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateDictionaryOrder();
|
|
||||||
|
|
||||||
const titles = this.dictionaryEntries.map((e) => e.dictionaryInfo.title);
|
|
||||||
const removeKeys = Object.keys(this.optionsDictionaries).filter((key) => titles.indexOf(key) < 0);
|
|
||||||
if (removeKeys.length > 0) {
|
|
||||||
for (const key of toIterable(removeKeys)) {
|
|
||||||
delete this.optionsDictionaries[key];
|
|
||||||
}
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createEntry(dictionaryInfo) {
|
|
||||||
const title = dictionaryInfo.title;
|
|
||||||
let changed = false;
|
|
||||||
let optionsDictionary;
|
|
||||||
const optionsDictionaries = this.optionsDictionaries;
|
|
||||||
if (hasOwn(optionsDictionaries, title)) {
|
|
||||||
optionsDictionary = optionsDictionaries[title];
|
|
||||||
} else {
|
|
||||||
optionsDictionary = SettingsDictionaryListUI.createDictionaryOptions();
|
|
||||||
optionsDictionaries[title] = optionsDictionary;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = document.importNode(this.template.content, true).firstChild;
|
|
||||||
|
|
||||||
this.dictionaryEntries.push(new SettingsDictionaryEntryUI(this, dictionaryInfo, content, optionsDictionary));
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static createDictionaryOptions() {
|
|
||||||
return utilBackgroundIsolate({
|
|
||||||
priority: 0,
|
|
||||||
enabled: false,
|
|
||||||
allowSecondarySearches: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
createExtra(totalCounts, remainders, totalRemainder) {
|
|
||||||
const content = document.importNode(this.extraTemplate.content, true).firstChild;
|
|
||||||
this.extraContainer.appendChild(content);
|
|
||||||
return new SettingsDictionaryExtraUI(this, totalCounts, remainders, totalRemainder, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
setCounts(dictionaryCounts, totalCounts) {
|
|
||||||
const remainders = Object.assign({}, totalCounts);
|
|
||||||
const keys = Object.keys(remainders);
|
|
||||||
|
|
||||||
for (let i = 0, ii = Math.min(this.dictionaryEntries.length, dictionaryCounts.length); i < ii; ++i) {
|
|
||||||
const counts = dictionaryCounts[i];
|
|
||||||
this.dictionaryEntries[i].setCounts(counts);
|
|
||||||
|
|
||||||
for (const key of keys) {
|
|
||||||
remainders[key] -= counts[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalRemainder = 0;
|
|
||||||
for (const key of keys) {
|
|
||||||
totalRemainder += remainders[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.extra !== null) {
|
|
||||||
this.extra.cleanup();
|
|
||||||
this.extra = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalRemainder > 0) {
|
|
||||||
this.extra = this.createExtra(totalCounts, remainders, totalRemainder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDictionaryOrder() {
|
|
||||||
const sortInfo = this.dictionaryEntries.map((e, i) => [e, i]);
|
|
||||||
sortInfo.sort((a, b) => {
|
|
||||||
const i = b[0].optionsDictionary.priority - a[0].optionsDictionary.priority;
|
|
||||||
return (i !== 0 ? i : a[1] - b[1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const [e] of sortInfo) {
|
|
||||||
this.container.appendChild(e.content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
|
||||||
// Overwrite
|
|
||||||
}
|
|
||||||
|
|
||||||
preventPageExit() {
|
|
||||||
// Overwrite
|
|
||||||
return {end: () => {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
onDictionaryConfirmDelete(e) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const n = document.querySelector('#dict-delete-modal');
|
this._dictionaryController.deleteDictionary(this._dictionaryTitle);
|
||||||
const title = n.dataset.dict;
|
|
||||||
delete n.dataset.dict;
|
|
||||||
$(n).modal('hide');
|
|
||||||
|
|
||||||
const index = this.dictionaryEntries.findIndex((entry) => entry.dictionaryInfo.title === title);
|
|
||||||
if (index >= 0) {
|
|
||||||
this.dictionaryEntries[index].deleteDictionary();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsDictionaryEntryUI {
|
_onDetailsToggleLinkClicked(e) {
|
||||||
constructor(parent, dictionaryInfo, content, optionsDictionary) {
|
e.preventDefault();
|
||||||
this.parent = parent;
|
this._detailsContainer.hidden = !this._detailsContainer.hidden;
|
||||||
this.dictionaryInfo = dictionaryInfo;
|
|
||||||
this.optionsDictionary = optionsDictionary;
|
|
||||||
this.counts = null;
|
|
||||||
this.eventListeners = new EventListenerCollection();
|
|
||||||
this.isDeleting = false;
|
|
||||||
|
|
||||||
this.content = content;
|
|
||||||
this.enabledCheckbox = this.content.querySelector('.dict-enabled');
|
|
||||||
this.allowSecondarySearchesCheckbox = this.content.querySelector('.dict-allow-secondary-searches');
|
|
||||||
this.priorityInput = this.content.querySelector('.dict-priority');
|
|
||||||
this.deleteButton = this.content.querySelector('.dict-delete-button');
|
|
||||||
this.detailsToggleLink = this.content.querySelector('.dict-details-toggle-link');
|
|
||||||
this.detailsContainer = this.content.querySelector('.dict-details');
|
|
||||||
this.detailsTable = this.content.querySelector('.dict-details-table');
|
|
||||||
|
|
||||||
if (this.dictionaryInfo.version < 3) {
|
|
||||||
this.content.querySelector('.dict-outdated').hidden = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setupDetails(dictionaryInfo);
|
_onPriorityChanged(e) {
|
||||||
|
const {detail: {value}} = e;
|
||||||
this.content.querySelector('.dict-title').textContent = this.dictionaryInfo.title;
|
this._node.style.order = `${-value}`;
|
||||||
this.content.querySelector('.dict-revision').textContent = `rev.${this.dictionaryInfo.revision}`;
|
|
||||||
this.content.querySelector('.dict-prefix-wildcard-searches-supported').checked = !!this.dictionaryInfo.prefixWildcardsSupported;
|
|
||||||
|
|
||||||
this.applyValues();
|
|
||||||
|
|
||||||
this.eventListeners.addEventListener(this.enabledCheckbox, 'change', this.onEnabledChanged.bind(this), false);
|
|
||||||
this.eventListeners.addEventListener(this.allowSecondarySearchesCheckbox, 'change', this.onAllowSecondarySearchesChanged.bind(this), false);
|
|
||||||
this.eventListeners.addEventListener(this.priorityInput, 'change', this.onPriorityChanged.bind(this), false);
|
|
||||||
this.eventListeners.addEventListener(this.deleteButton, 'click', this.onDeleteButtonClicked.bind(this), false);
|
|
||||||
this.eventListeners.addEventListener(this.detailsToggleLink, 'click', this.onDetailsToggleLinkClicked.bind(this), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupDetails(dictionaryInfo) {
|
_setupDetails(dictionaryInfo) {
|
||||||
const targets = [
|
const targets = [
|
||||||
['Author', 'author'],
|
['Author', 'author'],
|
||||||
['URL', 'url'],
|
['URL', 'url'],
|
||||||
@ -219,6 +107,7 @@ class SettingsDictionaryEntryUI {
|
|||||||
['Attribution', 'attribution']
|
['Attribution', 'attribution']
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (const [label, key] of targets) {
|
for (const [label, key] of targets) {
|
||||||
const info = dictionaryInfo[key];
|
const info = dictionaryInfo[key];
|
||||||
@ -238,60 +127,223 @@ class SettingsDictionaryEntryUI {
|
|||||||
n3.textContent = info;
|
n3.textContent = info;
|
||||||
n1.appendChild(n3);
|
n1.appendChild(n3);
|
||||||
|
|
||||||
this.detailsTable.appendChild(n1);
|
fragment.appendChild(n1);
|
||||||
|
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count === 0) {
|
if (count > 0) {
|
||||||
this.detailsContainer.hidden = true;
|
this._detailsTable.appendChild(fragment);
|
||||||
this.detailsToggleLink.hidden = true;
|
} else {
|
||||||
|
this._detailsContainer.hidden = true;
|
||||||
|
this._detailsToggleLink.hidden = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup() {
|
class DictionaryController {
|
||||||
if (this.content !== null) {
|
constructor(settingsController) {
|
||||||
if (this.content.parentNode !== null) {
|
this._settingsController = settingsController;
|
||||||
this.content.parentNode.removeChild(this.content);
|
this._dictionaries = null;
|
||||||
}
|
this._dictionaryEntries = [];
|
||||||
this.content = null;
|
this._databaseStateToken = null;
|
||||||
}
|
this._checkingIntegrity = false;
|
||||||
this.dictionaryInfo = null;
|
this._warningNode = null;
|
||||||
this.eventListeners.removeAllEventListeners();
|
this._mainDictionarySelect = null;
|
||||||
|
this._checkIntegrityButton = null;
|
||||||
|
this._dictionaryEntryContainer = null;
|
||||||
|
this._integrityExtraInfoContainer = null;
|
||||||
|
this._deleteDictionaryModal = null;
|
||||||
|
this._integrityExtraInfoNode = null;
|
||||||
|
this._isDeleting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCounts(counts) {
|
async prepare() {
|
||||||
this.counts = counts;
|
this._warningNode = document.querySelector('#dict-warning');
|
||||||
const node = this.content.querySelector('.dict-counts');
|
this._mainDictionarySelect = document.querySelector('#dict-main');
|
||||||
node.textContent = JSON.stringify({
|
this._checkIntegrityButton = document.querySelector('#dict-check-integrity');
|
||||||
info: this.dictionaryInfo,
|
this._dictionaryEntryContainer = document.querySelector('#dict-groups');
|
||||||
counts
|
this._integrityExtraInfoContainer = document.querySelector('#dict-groups-extra');
|
||||||
}, null, 4);
|
this._deleteDictionaryModal = document.querySelector('#dict-delete-modal');
|
||||||
node.removeAttribute('hidden');
|
|
||||||
|
yomichan.on('databaseUpdated', this._onDatabaseUpdated.bind(this));
|
||||||
|
|
||||||
|
document.querySelector('#dict-delete-confirm').addEventListener('click', this._onDictionaryConfirmDelete.bind(this), false);
|
||||||
|
this._checkIntegrityButton.addEventListener('click', this._onCheckIntegrityButtonClick.bind(this), false);
|
||||||
|
|
||||||
|
await this._onDatabaseUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
deleteDictionary(dictionaryTitle) {
|
||||||
this.parent.save();
|
if (this._isDeleting) { return; }
|
||||||
|
const modal = this._deleteDictionaryModal;
|
||||||
|
modal.dataset.dictionaryTitle = dictionaryTitle;
|
||||||
|
modal.querySelector('#dict-remove-modal-dict-name').textContent = dictionaryTitle;
|
||||||
|
this._setModalVisible(modal, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyValues() {
|
// Private
|
||||||
this.enabledCheckbox.checked = this.optionsDictionary.enabled;
|
|
||||||
this.allowSecondarySearchesCheckbox.checked = this.optionsDictionary.allowSecondarySearches;
|
async _onDatabaseUpdated() {
|
||||||
this.priorityInput.value = `${this.optionsDictionary.priority}`;
|
const token = {};
|
||||||
|
this._databaseStateToken = token;
|
||||||
|
this._dictionaries = null;
|
||||||
|
const dictionaries = await api.getDictionaryInfo();
|
||||||
|
if (this._databaseStateToken !== token) { return; }
|
||||||
|
this._dictionaries = dictionaries;
|
||||||
|
|
||||||
|
this._warningNode.hidden = (dictionaries.length > 0);
|
||||||
|
this._updateMainDictionarySelectOptions(dictionaries);
|
||||||
|
|
||||||
|
for (const entry of this._dictionaryEntries) {
|
||||||
|
entry.cleanup();
|
||||||
|
}
|
||||||
|
this._dictionaryEntries = [];
|
||||||
|
|
||||||
|
for (const dictionary of dictionaries) {
|
||||||
|
this._createDictionaryEntry(dictionary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteDictionary() {
|
_onDictionaryConfirmDelete(e) {
|
||||||
if (this.isDeleting) {
|
e.preventDefault();
|
||||||
return;
|
|
||||||
|
const modal = this._deleteDictionaryModal;
|
||||||
|
this._setModalVisible(modal, false);
|
||||||
|
|
||||||
|
const title = modal.dataset.dictionaryTitle;
|
||||||
|
if (typeof title !== 'string') { return; }
|
||||||
|
delete modal.dataset.dictionaryTitle;
|
||||||
|
|
||||||
|
this._deleteDictionary(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
const progress = this.content.querySelector('.progress');
|
_onCheckIntegrityButtonClick(e) {
|
||||||
progress.hidden = false;
|
e.preventDefault();
|
||||||
const progressBar = this.content.querySelector('.progress-bar');
|
this._checkIntegrity();
|
||||||
this.isDeleting = true;
|
}
|
||||||
|
|
||||||
|
_setModalVisible(node, visible) {
|
||||||
|
$(node).modal(visible ? 'show' : 'hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateMainDictionarySelectOptions(dictionaries) {
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
|
let option = document.createElement('option');
|
||||||
|
option.className = 'text-muted';
|
||||||
|
option.value = '';
|
||||||
|
option.textContent = 'Not selected';
|
||||||
|
fragment.appendChild(option);
|
||||||
|
|
||||||
|
for (const {title, sequenced} of dictionaries) {
|
||||||
|
if (!sequenced) { continue; }
|
||||||
|
option = document.createElement('option');
|
||||||
|
option.value = title;
|
||||||
|
option.textContent = title;
|
||||||
|
fragment.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
const select = this._mainDictionarySelect;
|
||||||
|
select.textContent = ''; // Empty
|
||||||
|
select.appendChild(fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _checkIntegrity() {
|
||||||
|
if (this._dictionaries === null || this._checkingIntegrity || this._isDeleting) { return; }
|
||||||
|
|
||||||
const prevention = this.parent.preventPageExit();
|
|
||||||
try {
|
try {
|
||||||
|
this._checkingIntegrity = true;
|
||||||
|
this._setButtonsEnabled(false);
|
||||||
|
|
||||||
|
const token = this._databaseStateToken;
|
||||||
|
const dictionaryTitles = this._dictionaries.map(({title}) => title);
|
||||||
|
const {counts, total} = await api.getDictionaryCounts(dictionaryTitles, true);
|
||||||
|
if (this._databaseStateToken !== token) { return; }
|
||||||
|
|
||||||
|
for (let i = 0, ii = Math.min(counts.length, this._dictionaryEntries.length); i < ii; ++i) {
|
||||||
|
const entry = this._dictionaryEntries[i];
|
||||||
|
entry.setCounts(counts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._setCounts(counts, total);
|
||||||
|
} finally {
|
||||||
|
this._setButtonsEnabled(true);
|
||||||
|
this._checkingIntegrity = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_setCounts(dictionaryCounts, totalCounts) {
|
||||||
|
const remainders = Object.assign({}, totalCounts);
|
||||||
|
const keys = Object.keys(remainders);
|
||||||
|
|
||||||
|
for (const counts of dictionaryCounts) {
|
||||||
|
for (const key of keys) {
|
||||||
|
remainders[key] -= counts[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalRemainder = 0;
|
||||||
|
for (const key of keys) {
|
||||||
|
totalRemainder += remainders[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
this._cleanupExtra();
|
||||||
|
if (totalRemainder > 0) {
|
||||||
|
this.extra = this._createExtra(totalCounts, remainders, totalRemainder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_createExtra(totalCounts, remainders, totalRemainder) {
|
||||||
|
const node = this._instantiateTemplate('#dict-extra-template');
|
||||||
|
this._integrityExtraInfoNode = node;
|
||||||
|
|
||||||
|
node.querySelector('.dict-total-count').textContent = `${totalRemainder} item${totalRemainder !== 1 ? 's' : ''}`;
|
||||||
|
|
||||||
|
const n = node.querySelector('.dict-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(dictionary) {
|
||||||
|
const node = this._instantiateTemplate('#dict-template');
|
||||||
|
this._dictionaryEntryContainer.appendChild(node);
|
||||||
|
|
||||||
|
const entry = new DictionaryEntry(this, node, dictionary);
|
||||||
|
this._dictionaryEntries.push(entry);
|
||||||
|
entry.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _deleteDictionary(dictionaryTitle) {
|
||||||
|
if (this._isDeleting || this._checkingIntegrity) { return; }
|
||||||
|
|
||||||
|
const index = this._dictionaryEntries.findIndex((entry) => entry.dictionaryTitle === dictionaryTitle);
|
||||||
|
if (index < 0) { return; }
|
||||||
|
|
||||||
|
const entry = this._dictionaryEntries[index];
|
||||||
|
const node = entry.node;
|
||||||
|
const progress = node.querySelector('.progress');
|
||||||
|
const progressBar = node.querySelector('.progress-bar');
|
||||||
|
const prevention = this._settingsController.preventPageExit();
|
||||||
|
try {
|
||||||
|
this._isDeleting = true;
|
||||||
|
this._setButtonsEnabled(false);
|
||||||
|
|
||||||
|
progress.hidden = false;
|
||||||
|
|
||||||
const onProgress = ({processed, count, storeCount, storesProcesed}) => {
|
const onProgress = ({processed, count, storeCount, storesProcesed}) => {
|
||||||
let percent = 0.0;
|
let percent = 0.0;
|
||||||
if (count > 0 && storesProcesed > 0) {
|
if (count > 0 && storesProcesed > 0) {
|
||||||
@ -300,219 +352,27 @@ class SettingsDictionaryEntryUI {
|
|||||||
progressBar.style.width = `${percent}%`;
|
progressBar.style.width = `${percent}%`;
|
||||||
};
|
};
|
||||||
|
|
||||||
await api.deleteDictionary(this.dictionaryInfo.title, onProgress);
|
await api.deleteDictionary(dictionaryTitle, onProgress);
|
||||||
} catch (e) {
|
|
||||||
this.dictionaryErrorsShow([e]);
|
|
||||||
} finally {
|
|
||||||
prevention.end();
|
|
||||||
this.isDeleting = false;
|
|
||||||
progress.hidden = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onEnabledChanged(e) {
|
|
||||||
this.optionsDictionary.enabled = !!e.target.checked;
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
onAllowSecondarySearchesChanged(e) {
|
|
||||||
this.optionsDictionary.allowSecondarySearches = !!e.target.checked;
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
onPriorityChanged(e) {
|
|
||||||
let value = Number.parseFloat(e.target.value);
|
|
||||||
if (Number.isNaN(value)) {
|
|
||||||
value = this.optionsDictionary.priority;
|
|
||||||
} else {
|
|
||||||
this.optionsDictionary.priority = value;
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
e.target.value = `${value}`;
|
|
||||||
|
|
||||||
this.parent.updateDictionaryOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeleteButtonClicked(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (this.isDeleting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = this.dictionaryInfo.title;
|
|
||||||
const n = document.querySelector('#dict-delete-modal');
|
|
||||||
n.dataset.dict = title;
|
|
||||||
document.querySelector('#dict-remove-modal-dict-name').textContent = title;
|
|
||||||
$(n).modal('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
onDetailsToggleLinkClicked(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
this.detailsContainer.hidden = !this.detailsContainer.hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SettingsDictionaryExtraUI {
|
|
||||||
constructor(parent, totalCounts, remainders, totalRemainder, content) {
|
|
||||||
this.parent = parent;
|
|
||||||
this.content = content;
|
|
||||||
|
|
||||||
this.content.querySelector('.dict-total-count').textContent = `${totalRemainder} item${totalRemainder !== 1 ? 's' : ''}`;
|
|
||||||
|
|
||||||
const node = this.content.querySelector('.dict-counts');
|
|
||||||
node.textContent = JSON.stringify({
|
|
||||||
counts: totalCounts,
|
|
||||||
remainders: remainders
|
|
||||||
}, null, 4);
|
|
||||||
node.removeAttribute('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
if (this.content !== null) {
|
|
||||||
if (this.content.parentNode !== null) {
|
|
||||||
this.content.parentNode.removeChild(this.content);
|
|
||||||
}
|
|
||||||
this.content = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DictionaryController {
|
|
||||||
constructor(settingsController) {
|
|
||||||
this._settingsController = settingsController;
|
|
||||||
this._dictionaryUI = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = () => this._settingsController.save();
|
|
||||||
this._dictionaryUI.preventPageExit = this._preventPageExit.bind(this);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));
|
|
||||||
|
|
||||||
yomichan.on('databaseUpdated', this._onDatabaseUpdated.bind(this));
|
|
||||||
|
|
||||||
await this._onOptionsChanged();
|
|
||||||
await this._onDatabaseUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private
|
|
||||||
|
|
||||||
async _onOptionsChanged() {
|
|
||||||
const options = await this._settingsController.getOptionsMutable();
|
|
||||||
|
|
||||||
this._dictionaryUI.setOptionsDictionaries(options.dictionaries);
|
|
||||||
|
|
||||||
const optionsFull = await this._settingsController.getOptionsFull();
|
|
||||||
document.querySelector('#database-enable-prefix-wildcard-searches').checked = optionsFull.global.database.prefixWildcardsSupported;
|
|
||||||
|
|
||||||
await this._updateMainDictionarySelectValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
_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);
|
|
||||||
|
|
||||||
for (const {title, sequenced} of toIterable(dictionaries)) {
|
|
||||||
if (!sequenced) { continue; }
|
|
||||||
|
|
||||||
option = document.createElement('option');
|
|
||||||
option.value = title;
|
|
||||||
option.textContent = title;
|
|
||||||
select.appendChild(option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _updateMainDictionarySelectValue() {
|
|
||||||
const options = await this._settingsController.getOptions();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
} catch (e) {
|
||||||
yomichan.logError(e);
|
yomichan.logError(e);
|
||||||
|
} finally {
|
||||||
|
prevention.end();
|
||||||
|
progress.hidden = true;
|
||||||
|
this._setButtonsEnabled(true);
|
||||||
|
this._isDeleting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDictionaryMainChanged(e) {
|
_setButtonsEnabled(value) {
|
||||||
const select = e.target;
|
value = !value;
|
||||||
const value = select.value;
|
for (const node of document.querySelectorAll('.dictionary-modifying-input')) {
|
||||||
|
node.disabled = value;
|
||||||
const missingNodeOption = select.querySelector('option[data-not-installed=true]');
|
}
|
||||||
if (missingNodeOption !== null && missingNodeOption.value !== value) {
|
|
||||||
missingNodeOption.parentNode.removeChild(missingNodeOption);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = await this._settingsController.getOptionsMutable();
|
_instantiateTemplate(templateSelector) {
|
||||||
options.general.mainDictionary = value;
|
const template = document.querySelector(templateSelector);
|
||||||
await this._settingsController.save();
|
const content = document.importNode(template.content, true);
|
||||||
}
|
return content.firstChild;
|
||||||
|
|
||||||
async _onDatabaseEnablePrefixWildcardSearchesChanged(e) {
|
|
||||||
const optionsFull = await this._settingsController.getOptionsFullMutable();
|
|
||||||
const v = !!e.target.checked;
|
|
||||||
if (optionsFull.global.database.prefixWildcardsSupported === v) { return; }
|
|
||||||
optionsFull.global.database.prefixWildcardsSupported = !!e.target.checked;
|
|
||||||
await this._settingsController.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
_preventPageExit() {
|
|
||||||
return this._settingsController.preventPageExit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,8 +304,9 @@ class DictionaryImportController {
|
|||||||
|
|
||||||
_setButtonsEnabled(value) {
|
_setButtonsEnabled(value) {
|
||||||
value = !value;
|
value = !value;
|
||||||
this._purgeButton.disabled = value;
|
for (const node of document.querySelectorAll('.dictionary-modifying-input')) {
|
||||||
this._importFileButton.disabled = value;
|
node.disabled = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getPreparedDictionaryDatabase() {
|
async _getPreparedDictionaryDatabase() {
|
||||||
|
@ -653,7 +653,7 @@
|
|||||||
|
|
||||||
<div class="form-group" id="dict-main-group">
|
<div class="form-group" id="dict-main-group">
|
||||||
<label for="dict-main">Main dictionary for merged mode</label>
|
<label for="dict-main">Main dictionary for merged mode</label>
|
||||||
<select class="form-control" id="dict-main"></select>
|
<select class="form-control" id="dict-main" data-setting="general.mainDictionary"></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-danger" id="dict-purge" hidden>Dictionary data is being purged, please be patient...</div>
|
<div class="text-danger" id="dict-purge" hidden>Dictionary data is being purged, please be patient...</div>
|
||||||
@ -678,8 +678,9 @@
|
|||||||
for use with this extension and to learn about importing proprietary EPWING dictionaries.
|
for use with this extension and to learn about importing proprietary EPWING dictionaries.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-primary" id="dict-file-button">Import Dictionary</button>
|
<button class="btn btn-primary dictionary-modifying-input" id="dict-file-button">Import Dictionary</button>
|
||||||
<button class="btn btn-danger" id="dict-purge-button">Purge Database</button>
|
<button class="btn btn-danger dictionary-modifying-input" id="dict-purge-button">Purge Database</button>
|
||||||
|
<button class="btn btn-default dictionary-modifying-input" id="dict-check-integrity">Check integrity</button>
|
||||||
</div>
|
</div>
|
||||||
<div hidden><input type="file" id="dict-file" accept=".zip,application/zip" multiple></div>
|
<div hidden><input type="file" id="dict-file" accept=".zip,application/zip" multiple></div>
|
||||||
</div>
|
</div>
|
||||||
@ -727,7 +728,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
<button type="button" class="btn btn-danger" id="dict-delete-confirm">Delete Dictionary</button>
|
<button type="button" class="btn btn-danger dictionary-modifying-input" id="dict-delete-confirm">Delete Dictionary</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -756,7 +757,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="dict-delete-table">
|
<div class="dict-delete-table">
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-default dict-delete-button">Delete Dictionary</button>
|
<button class="btn btn-default dict-delete-button dictionary-modifying-input">Delete Dictionary</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="progress" hidden>
|
<div class="progress" hidden>
|
||||||
|
@ -303,6 +303,9 @@ class DOMDataBinder {
|
|||||||
element.value = value;
|
element.value = value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const event = new CustomEvent('settingChanged', {detail: {value}});
|
||||||
|
element.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getElementValue(element) {
|
_getElementValue(element) {
|
||||||
|
Loading…
Reference in New Issue
Block a user