Convert dictionaries.js and storage.js to classes (#570)
* Convert dictionaries.js to a class * Remove storage spinner * Convert storage.js to a class * Move dataset assignments into main.js
This commit is contained in:
parent
c62f980f37
commit
418e8a57bf
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
#anki-spinner,
|
#anki-spinner,
|
||||||
#dict-spinner, #dict-import-progress,
|
#dict-spinner, #dict-import-progress,
|
||||||
.storage-hidden, #storage-spinner {
|
.storage-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,14 +22,9 @@
|
|||||||
* getOptionsFullMutable
|
* getOptionsFullMutable
|
||||||
* getOptionsMutable
|
* getOptionsMutable
|
||||||
* settingsSaveOptions
|
* settingsSaveOptions
|
||||||
* storageEstimate
|
|
||||||
* storageUpdateStats
|
|
||||||
* utilBackgroundIsolate
|
* utilBackgroundIsolate
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let dictionaryUI = null;
|
|
||||||
|
|
||||||
|
|
||||||
class SettingsDictionaryListUI {
|
class SettingsDictionaryListUI {
|
||||||
constructor(container, template, extraContainer, extraTemplate) {
|
constructor(container, template, extraContainer, extraTemplate) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
@ -308,13 +303,13 @@ class SettingsDictionaryEntryUI {
|
|||||||
|
|
||||||
await api.deleteDictionary(this.dictionaryInfo.title, onProgress);
|
await api.deleteDictionary(this.dictionaryInfo.title, onProgress);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dictionaryErrorsShow([e]);
|
this.dictionaryErrorsShow([e]);
|
||||||
} finally {
|
} finally {
|
||||||
prevention.end();
|
prevention.end();
|
||||||
this.isDeleting = false;
|
this.isDeleting = false;
|
||||||
progress.hidden = true;
|
progress.hidden = true;
|
||||||
|
|
||||||
onDatabaseUpdated();
|
this.onDatabaseUpdated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,340 +383,341 @@ class SettingsDictionaryExtraUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DictionaryController {
|
||||||
async function dictSettingsInitialize() {
|
constructor(storageController) {
|
||||||
dictionaryUI = new SettingsDictionaryListUI(
|
this._storageController = storageController;
|
||||||
document.querySelector('#dict-groups'),
|
this._dictionaryUI = null;
|
||||||
document.querySelector('#dict-template'),
|
this._dictionaryErrorToStringOverrides = [
|
||||||
document.querySelector('#dict-groups-extra'),
|
[
|
||||||
document.querySelector('#dict-extra-template')
|
'A mutation operation was attempted on a database that did not allow mutations.',
|
||||||
);
|
'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.'
|
||||||
dictionaryUI.save = settingsSaveOptions;
|
],
|
||||||
|
[
|
||||||
document.querySelector('#dict-purge-button').addEventListener('click', onDictionaryPurgeButtonClick, false);
|
'The operation failed for reasons unrelated to the database itself and not covered by any other error code.',
|
||||||
document.querySelector('#dict-purge-confirm').addEventListener('click', onDictionaryPurge, false);
|
'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.'
|
||||||
document.querySelector('#dict-file-button').addEventListener('click', onDictionaryImportButtonClick, false);
|
],
|
||||||
document.querySelector('#dict-file').addEventListener('change', onDictionaryImport, false);
|
[
|
||||||
document.querySelector('#dict-main').addEventListener('change', onDictionaryMainChanged, false);
|
'BulkError',
|
||||||
document.querySelector('#database-enable-prefix-wildcard-searches').addEventListener('change', onDatabaseEnablePrefixWildcardSearchesChanged, false);
|
'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.'
|
||||||
|
]
|
||||||
await onDictionaryOptionsChanged();
|
];
|
||||||
await onDatabaseUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onDictionaryOptionsChanged() {
|
|
||||||
if (dictionaryUI === null) { return; }
|
|
||||||
|
|
||||||
const optionsContext = getOptionsContext();
|
|
||||||
const options = await getOptionsMutable(optionsContext);
|
|
||||||
|
|
||||||
dictionaryUI.setOptionsDictionaries(options.dictionaries);
|
|
||||||
|
|
||||||
const optionsFull = await api.optionsGetFull();
|
|
||||||
document.querySelector('#database-enable-prefix-wildcard-searches').checked = optionsFull.global.database.prefixWildcardsSupported;
|
|
||||||
|
|
||||||
await updateMainDictionarySelectValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onDatabaseUpdated() {
|
|
||||||
try {
|
|
||||||
const dictionaries = await api.getDictionaryInfo();
|
|
||||||
dictionaryUI.setDictionaries(dictionaries);
|
|
||||||
|
|
||||||
document.querySelector('#dict-warning').hidden = (dictionaries.length > 0);
|
|
||||||
|
|
||||||
updateMainDictionarySelectOptions(dictionaries);
|
|
||||||
await updateMainDictionarySelectValue();
|
|
||||||
|
|
||||||
const {counts, total} = await api.getDictionaryCounts(dictionaries.map((v) => v.title), true);
|
|
||||||
dictionaryUI.setCounts(counts, total);
|
|
||||||
} catch (e) {
|
|
||||||
dictionaryErrorsShow([e]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function 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 function updateMainDictionarySelectValue() {
|
|
||||||
const optionsContext = getOptionsContext();
|
|
||||||
const options = await api.optionsGet(optionsContext);
|
|
||||||
|
|
||||||
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]');
|
async prepare() {
|
||||||
if (selectValue === null) {
|
this._dictionaryUI = new SettingsDictionaryListUI(
|
||||||
if (missingNodeOption === null) {
|
document.querySelector('#dict-groups'),
|
||||||
missingNodeOption = document.createElement('option');
|
document.querySelector('#dict-template'),
|
||||||
missingNodeOption.className = 'text-muted';
|
document.querySelector('#dict-groups-extra'),
|
||||||
missingNodeOption.value = value;
|
document.querySelector('#dict-extra-template')
|
||||||
missingNodeOption.textContent = `${value} (Not installed)`;
|
);
|
||||||
missingNodeOption.dataset.notInstalled = 'true';
|
this._dictionaryUI.save = settingsSaveOptions;
|
||||||
select.appendChild(missingNodeOption);
|
|
||||||
}
|
document.querySelector('#dict-purge-button').addEventListener('click', this._onPurgeButtonClick.bind(this), false);
|
||||||
} else {
|
document.querySelector('#dict-purge-confirm').addEventListener('click', this._onPurgeConfirmButtonClick.bind(this), false);
|
||||||
if (missingNodeOption !== null) {
|
document.querySelector('#dict-file-button').addEventListener('click', this._onImportButtonClick.bind(this), false);
|
||||||
missingNodeOption.parentNode.removeChild(missingNodeOption);
|
document.querySelector('#dict-file').addEventListener('change', this._onImportFileChange.bind(this), false);
|
||||||
}
|
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);
|
||||||
|
|
||||||
|
await this.optionsChanged();
|
||||||
|
await this._onDatabaseUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
select.value = value;
|
async optionsChanged() {
|
||||||
}
|
if (this._dictionaryUI === null) { return; }
|
||||||
|
|
||||||
async function onDictionaryMainChanged(e) {
|
const optionsContext = getOptionsContext();
|
||||||
const select = e.target;
|
const options = await getOptionsMutable(optionsContext);
|
||||||
const value = select.value;
|
|
||||||
|
|
||||||
const missingNodeOption = select.querySelector('option[data-not-installed=true]');
|
this._dictionaryUI.setOptionsDictionaries(options.dictionaries);
|
||||||
if (missingNodeOption !== null && missingNodeOption.value !== value) {
|
|
||||||
missingNodeOption.parentNode.removeChild(missingNodeOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
const optionsContext = getOptionsContext();
|
|
||||||
const options = await getOptionsMutable(optionsContext);
|
|
||||||
options.general.mainDictionary = value;
|
|
||||||
await settingsSaveOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function dictionaryErrorToString(error) {
|
|
||||||
if (error.toString) {
|
|
||||||
error = error.toString();
|
|
||||||
} else {
|
|
||||||
error = `${error}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [match, subst] of dictionaryErrorToString.overrides) {
|
|
||||||
if (error.includes(match)) {
|
|
||||||
error = subst;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
dictionaryErrorToString.overrides = [
|
|
||||||
[
|
|
||||||
'A mutation operation was attempted on a database that did not allow mutations.',
|
|
||||||
'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'The operation failed for reasons unrelated to the database itself and not covered by any other error code.',
|
|
||||||
'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'BulkError',
|
|
||||||
'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
function dictionaryErrorsShow(errors) {
|
|
||||||
const dialog = document.querySelector('#dict-error');
|
|
||||||
dialog.textContent = '';
|
|
||||||
|
|
||||||
if (errors !== null && errors.length > 0) {
|
|
||||||
const uniqueErrors = new Map();
|
|
||||||
for (let e of errors) {
|
|
||||||
yomichan.logError(e);
|
|
||||||
e = dictionaryErrorToString(e);
|
|
||||||
let count = uniqueErrors.get(e);
|
|
||||||
if (typeof count === 'undefined') {
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
uniqueErrors.set(e, count + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [e, count] of uniqueErrors.entries()) {
|
|
||||||
const div = document.createElement('p');
|
|
||||||
if (count > 1) {
|
|
||||||
div.textContent = `${e} `;
|
|
||||||
const em = document.createElement('em');
|
|
||||||
em.textContent = `(${count})`;
|
|
||||||
div.appendChild(em);
|
|
||||||
} else {
|
|
||||||
div.textContent = `${e}`;
|
|
||||||
}
|
|
||||||
dialog.appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.hidden = false;
|
|
||||||
} else {
|
|
||||||
dialog.hidden = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function dictionarySpinnerShow(show) {
|
|
||||||
const spinner = $('#dict-spinner');
|
|
||||||
if (show) {
|
|
||||||
spinner.show();
|
|
||||||
} else {
|
|
||||||
spinner.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDictionaryImportButtonClick() {
|
|
||||||
const dictFile = document.querySelector('#dict-file');
|
|
||||||
dictFile.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDictionaryPurgeButtonClick(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$('#dict-purge-modal').modal('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onDictionaryPurge(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
$('#dict-purge-modal').modal('hide');
|
|
||||||
|
|
||||||
const dictControls = $('#dict-importer, #dict-groups, #dict-groups-extra, #dict-main-group').hide();
|
|
||||||
const dictProgress = document.querySelector('#dict-purge');
|
|
||||||
dictProgress.hidden = false;
|
|
||||||
|
|
||||||
const prevention = new PageExitPrevention();
|
|
||||||
|
|
||||||
try {
|
|
||||||
prevention.start();
|
|
||||||
dictionaryErrorsShow(null);
|
|
||||||
dictionarySpinnerShow(true);
|
|
||||||
|
|
||||||
await api.purgeDatabase();
|
|
||||||
for (const {options} of toIterable((await getOptionsFullMutable()).profiles)) {
|
|
||||||
options.dictionaries = utilBackgroundIsolate({});
|
|
||||||
options.general.mainDictionary = '';
|
|
||||||
}
|
|
||||||
await settingsSaveOptions();
|
|
||||||
|
|
||||||
onDatabaseUpdated();
|
|
||||||
} catch (err) {
|
|
||||||
dictionaryErrorsShow([err]);
|
|
||||||
} finally {
|
|
||||||
prevention.end();
|
|
||||||
|
|
||||||
dictionarySpinnerShow(false);
|
|
||||||
|
|
||||||
dictControls.show();
|
|
||||||
dictProgress.hidden = true;
|
|
||||||
|
|
||||||
if (storageEstimate.mostRecent !== null) {
|
|
||||||
storageUpdateStats();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onDictionaryImport(e) {
|
|
||||||
const files = [...e.target.files];
|
|
||||||
e.target.value = null;
|
|
||||||
|
|
||||||
const dictFile = $('#dict-file');
|
|
||||||
const dictControls = $('#dict-importer').hide();
|
|
||||||
const dictProgress = $('#dict-import-progress').show();
|
|
||||||
const dictImportInfo = document.querySelector('#dict-import-info');
|
|
||||||
|
|
||||||
const prevention = new PageExitPrevention();
|
|
||||||
|
|
||||||
try {
|
|
||||||
prevention.start();
|
|
||||||
dictionaryErrorsShow(null);
|
|
||||||
dictionarySpinnerShow(true);
|
|
||||||
|
|
||||||
const setProgress = (percent) => dictProgress.find('.progress-bar').css('width', `${percent}%`);
|
|
||||||
const updateProgress = (total, current) => {
|
|
||||||
setProgress(current / total * 100.0);
|
|
||||||
if (storageEstimate.mostRecent !== null && !storageUpdateStats.isUpdating) {
|
|
||||||
storageUpdateStats();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const optionsFull = await api.optionsGetFull();
|
const optionsFull = await api.optionsGetFull();
|
||||||
|
document.querySelector('#database-enable-prefix-wildcard-searches').checked = optionsFull.global.database.prefixWildcardsSupported;
|
||||||
|
|
||||||
const importDetails = {
|
await this._updateMainDictionarySelectValue();
|
||||||
prefixWildcardsSupported: optionsFull.global.database.prefixWildcardsSupported
|
}
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0, ii = files.length; i < ii; ++i) {
|
// Private
|
||||||
setProgress(0.0);
|
|
||||||
if (ii > 1) {
|
_updateMainDictionarySelectOptions(dictionaries) {
|
||||||
dictImportInfo.hidden = false;
|
const select = document.querySelector('#dict-main');
|
||||||
dictImportInfo.textContent = `(${i + 1} of ${ii})`;
|
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 optionsContext = getOptionsContext();
|
||||||
|
const options = await api.optionsGet(optionsContext);
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const archiveContent = await dictReadFile(files[i]);
|
let missingNodeOption = select.querySelector('option[data-not-installed=true]');
|
||||||
const {result, errors} = await api.importDictionaryArchive(archiveContent, importDetails, updateProgress);
|
if (selectValue === null) {
|
||||||
for (const {options} of toIterable((await getOptionsFullMutable()).profiles)) {
|
if (missingNodeOption === null) {
|
||||||
const dictionaryOptions = SettingsDictionaryListUI.createDictionaryOptions();
|
missingNodeOption = document.createElement('option');
|
||||||
dictionaryOptions.enabled = true;
|
missingNodeOption.className = 'text-muted';
|
||||||
options.dictionaries[result.title] = dictionaryOptions;
|
missingNodeOption.value = value;
|
||||||
if (result.sequenced && options.general.mainDictionary === '') {
|
missingNodeOption.textContent = `${value} (Not installed)`;
|
||||||
options.general.mainDictionary = result.title;
|
missingNodeOption.dataset.notInstalled = 'true';
|
||||||
|
select.appendChild(missingNodeOption);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (missingNodeOption !== null) {
|
||||||
|
missingNodeOption.parentNode.removeChild(missingNodeOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dictionaryErrorToString(error) {
|
||||||
|
if (error.toString) {
|
||||||
|
error = error.toString();
|
||||||
|
} else {
|
||||||
|
error = `${error}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [match, subst] of this._dictionaryErrorToStringOverrides) {
|
||||||
|
if (error.includes(match)) {
|
||||||
|
error = subst;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dictionaryErrorsShow(errors) {
|
||||||
|
const dialog = document.querySelector('#dict-error');
|
||||||
|
dialog.textContent = '';
|
||||||
|
|
||||||
|
if (errors !== null && errors.length > 0) {
|
||||||
|
const uniqueErrors = new Map();
|
||||||
|
for (let e of errors) {
|
||||||
|
yomichan.logError(e);
|
||||||
|
e = this._dictionaryErrorToString(e);
|
||||||
|
let count = uniqueErrors.get(e);
|
||||||
|
if (typeof count === 'undefined') {
|
||||||
|
count = 0;
|
||||||
}
|
}
|
||||||
|
uniqueErrors.set(e, count + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const [e, count] of uniqueErrors.entries()) {
|
||||||
|
const div = document.createElement('p');
|
||||||
|
if (count > 1) {
|
||||||
|
div.textContent = `${e} `;
|
||||||
|
const em = document.createElement('em');
|
||||||
|
em.textContent = `(${count})`;
|
||||||
|
div.appendChild(em);
|
||||||
|
} else {
|
||||||
|
div.textContent = `${e}`;
|
||||||
|
}
|
||||||
|
dialog.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.hidden = false;
|
||||||
|
} else {
|
||||||
|
dialog.hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_dictionarySpinnerShow(show) {
|
||||||
|
const spinner = $('#dict-spinner');
|
||||||
|
if (show) {
|
||||||
|
spinner.show();
|
||||||
|
} else {
|
||||||
|
spinner.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_dictReadFile(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = () => reject(reader.error);
|
||||||
|
reader.readAsBinaryString(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
this._dictionaryErrorsShow([e]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onDictionaryMainChanged(e) {
|
||||||
|
const select = e.target;
|
||||||
|
const value = select.value;
|
||||||
|
|
||||||
|
const missingNodeOption = select.querySelector('option[data-not-installed=true]');
|
||||||
|
if (missingNodeOption !== null && missingNodeOption.value !== value) {
|
||||||
|
missingNodeOption.parentNode.removeChild(missingNodeOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionsContext = getOptionsContext();
|
||||||
|
const options = await getOptionsMutable(optionsContext);
|
||||||
|
options.general.mainDictionary = value;
|
||||||
|
await settingsSaveOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onImportButtonClick() {
|
||||||
|
const dictFile = document.querySelector('#dict-file');
|
||||||
|
dictFile.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPurgeButtonClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('#dict-purge-modal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onPurgeConfirmButtonClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
$('#dict-purge-modal').modal('hide');
|
||||||
|
|
||||||
|
const dictControls = $('#dict-importer, #dict-groups, #dict-groups-extra, #dict-main-group').hide();
|
||||||
|
const dictProgress = document.querySelector('#dict-purge');
|
||||||
|
dictProgress.hidden = false;
|
||||||
|
|
||||||
|
const prevention = new PageExitPrevention();
|
||||||
|
|
||||||
|
try {
|
||||||
|
prevention.start();
|
||||||
|
this._dictionaryErrorsShow(null);
|
||||||
|
this._dictionarySpinnerShow(true);
|
||||||
|
|
||||||
|
await api.purgeDatabase();
|
||||||
|
for (const {options} of toIterable((await getOptionsFullMutable()).profiles)) {
|
||||||
|
options.dictionaries = utilBackgroundIsolate({});
|
||||||
|
options.general.mainDictionary = '';
|
||||||
|
}
|
||||||
await settingsSaveOptions();
|
await settingsSaveOptions();
|
||||||
|
|
||||||
if (errors.length > 0) {
|
this._onDatabaseUpdated();
|
||||||
const errors2 = errors.map((error) => jsonToError(error));
|
} catch (err) {
|
||||||
errors2.push(`Dictionary may not have been imported properly: ${errors2.length} error${errors2.length === 1 ? '' : 's'} reported.`);
|
this._dictionaryErrorsShow([err]);
|
||||||
dictionaryErrorsShow(errors2);
|
} finally {
|
||||||
}
|
prevention.end();
|
||||||
|
|
||||||
onDatabaseUpdated();
|
this._dictionarySpinnerShow(false);
|
||||||
|
|
||||||
|
dictControls.show();
|
||||||
|
dictProgress.hidden = true;
|
||||||
|
|
||||||
|
this._storageController.updateStats();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
}
|
||||||
dictionaryErrorsShow([err]);
|
|
||||||
} finally {
|
|
||||||
prevention.end();
|
|
||||||
dictionarySpinnerShow(false);
|
|
||||||
|
|
||||||
dictImportInfo.hidden = false;
|
async _onImportFileChange(e) {
|
||||||
dictImportInfo.textContent = '';
|
const files = [...e.target.files];
|
||||||
dictFile.val('');
|
e.target.value = null;
|
||||||
dictControls.show();
|
|
||||||
dictProgress.hide();
|
const dictFile = $('#dict-file');
|
||||||
|
const dictControls = $('#dict-importer').hide();
|
||||||
|
const dictProgress = $('#dict-import-progress').show();
|
||||||
|
const dictImportInfo = document.querySelector('#dict-import-info');
|
||||||
|
|
||||||
|
const prevention = new PageExitPrevention();
|
||||||
|
|
||||||
|
try {
|
||||||
|
prevention.start();
|
||||||
|
this._dictionaryErrorsShow(null);
|
||||||
|
this._dictionarySpinnerShow(true);
|
||||||
|
|
||||||
|
const setProgress = (percent) => dictProgress.find('.progress-bar').css('width', `${percent}%`);
|
||||||
|
const updateProgress = (total, current) => {
|
||||||
|
setProgress(current / total * 100.0);
|
||||||
|
this._storageController.updateStats();
|
||||||
|
};
|
||||||
|
|
||||||
|
const optionsFull = await api.optionsGetFull();
|
||||||
|
|
||||||
|
const importDetails = {
|
||||||
|
prefixWildcardsSupported: optionsFull.global.database.prefixWildcardsSupported
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0, ii = files.length; i < ii; ++i) {
|
||||||
|
setProgress(0.0);
|
||||||
|
if (ii > 1) {
|
||||||
|
dictImportInfo.hidden = false;
|
||||||
|
dictImportInfo.textContent = `(${i + 1} of ${ii})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const archiveContent = await this._dictReadFile(files[i]);
|
||||||
|
const {result, errors} = await api.importDictionaryArchive(archiveContent, importDetails, updateProgress);
|
||||||
|
for (const {options} of toIterable((await getOptionsFullMutable()).profiles)) {
|
||||||
|
const dictionaryOptions = SettingsDictionaryListUI.createDictionaryOptions();
|
||||||
|
dictionaryOptions.enabled = true;
|
||||||
|
options.dictionaries[result.title] = dictionaryOptions;
|
||||||
|
if (result.sequenced && options.general.mainDictionary === '') {
|
||||||
|
options.general.mainDictionary = result.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await settingsSaveOptions();
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
const errors2 = errors.map((error) => jsonToError(error));
|
||||||
|
errors2.push(`Dictionary may not have been imported properly: ${errors2.length} error${errors2.length === 1 ? '' : 's'} reported.`);
|
||||||
|
this._dictionaryErrorsShow(errors2);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onDatabaseUpdated();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this._dictionaryErrorsShow([err]);
|
||||||
|
} finally {
|
||||||
|
prevention.end();
|
||||||
|
this._dictionarySpinnerShow(false);
|
||||||
|
|
||||||
|
dictImportInfo.hidden = false;
|
||||||
|
dictImportInfo.textContent = '';
|
||||||
|
dictFile.val('');
|
||||||
|
dictControls.show();
|
||||||
|
dictProgress.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onDatabaseEnablePrefixWildcardSearchesChanged(e) {
|
||||||
|
const optionsFull = await getOptionsFullMutable();
|
||||||
|
const v = !!e.target.checked;
|
||||||
|
if (optionsFull.global.database.prefixWildcardsSupported === v) { return; }
|
||||||
|
optionsFull.global.database.prefixWildcardsSupported = !!e.target.checked;
|
||||||
|
await settingsSaveOptions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dictReadFile(file) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = () => resolve(reader.result);
|
|
||||||
reader.onerror = () => reject(reader.error);
|
|
||||||
reader.readAsBinaryString(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function onDatabaseEnablePrefixWildcardSearchesChanged(e) {
|
|
||||||
const optionsFull = await getOptionsFullMutable();
|
|
||||||
const v = !!e.target.checked;
|
|
||||||
if (optionsFull.global.database.prefixWildcardsSupported === v) { return; }
|
|
||||||
optionsFull.global.database.prefixWildcardsSupported = !!e.target.checked;
|
|
||||||
await settingsSaveOptions();
|
|
||||||
}
|
|
||||||
|
@ -19,14 +19,13 @@
|
|||||||
* AnkiController
|
* AnkiController
|
||||||
* AnkiTemplatesController
|
* AnkiTemplatesController
|
||||||
* AudioController
|
* AudioController
|
||||||
|
* DictionaryController
|
||||||
* ProfileController
|
* ProfileController
|
||||||
* SettingsBackup
|
* SettingsBackup
|
||||||
* SettingsController
|
* SettingsController
|
||||||
|
* StorageController
|
||||||
* api
|
* api
|
||||||
* appearanceInitialize
|
* appearanceInitialize
|
||||||
* dictSettingsInitialize
|
|
||||||
* onDictionaryOptionsChanged
|
|
||||||
* storageInfoInitialize
|
|
||||||
* utilBackend
|
* utilBackend
|
||||||
* utilBackgroundIsolate
|
* utilBackgroundIsolate
|
||||||
*/
|
*/
|
||||||
@ -270,7 +269,9 @@ async function onOptionsUpdated({source}) {
|
|||||||
if (ankiTemplatesController !== null) {
|
if (ankiTemplatesController !== null) {
|
||||||
ankiTemplatesController.updateValue();
|
ankiTemplatesController.updateValue();
|
||||||
}
|
}
|
||||||
onDictionaryOptionsChanged();
|
if (dictionaryController !== null) {
|
||||||
|
dictionaryController.optionsChanged();
|
||||||
|
}
|
||||||
if (ankiController !== null) {
|
if (ankiController !== null) {
|
||||||
ankiController.optionsChanged();
|
ankiController.optionsChanged();
|
||||||
}
|
}
|
||||||
@ -304,8 +305,15 @@ async function settingsPopulateModifierKeys() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setupEnvironmentInfo() {
|
||||||
|
const {browser, platform} = await api.getEnvironmentInfo();
|
||||||
|
document.documentElement.dataset.browser = browser;
|
||||||
|
document.documentElement.dataset.operatingSystem = platform.os;
|
||||||
|
}
|
||||||
|
|
||||||
let ankiController = null;
|
let ankiController = null;
|
||||||
let ankiTemplatesController = null;
|
let ankiTemplatesController = null;
|
||||||
|
let dictionaryController = null;
|
||||||
|
|
||||||
async function onReady() {
|
async function onReady() {
|
||||||
api.forwardLogsToBackend();
|
api.forwardLogsToBackend();
|
||||||
@ -314,22 +322,25 @@ async function onReady() {
|
|||||||
const settingsController = new SettingsController();
|
const settingsController = new SettingsController();
|
||||||
settingsController.prepare();
|
settingsController.prepare();
|
||||||
|
|
||||||
|
setupEnvironmentInfo();
|
||||||
showExtensionInformation();
|
showExtensionInformation();
|
||||||
|
|
||||||
|
const storageController = new StorageController();
|
||||||
|
storageController.prepare();
|
||||||
|
|
||||||
await settingsPopulateModifierKeys();
|
await settingsPopulateModifierKeys();
|
||||||
formSetupEventListeners();
|
formSetupEventListeners();
|
||||||
appearanceInitialize();
|
appearanceInitialize();
|
||||||
new AudioController().prepare();
|
new AudioController().prepare();
|
||||||
await (new ProfileController()).prepare();
|
await (new ProfileController()).prepare();
|
||||||
await dictSettingsInitialize();
|
dictionaryController = new DictionaryController(storageController);
|
||||||
|
dictionaryController.prepare();
|
||||||
ankiController = new AnkiController();
|
ankiController = new AnkiController();
|
||||||
ankiController.prepare();
|
ankiController.prepare();
|
||||||
ankiTemplatesController = new AnkiTemplatesController(ankiController);
|
ankiTemplatesController = new AnkiTemplatesController(ankiController);
|
||||||
ankiTemplatesController.prepare();
|
ankiTemplatesController.prepare();
|
||||||
new SettingsBackup().prepare();
|
new SettingsBackup().prepare();
|
||||||
|
|
||||||
storageInfoInitialize();
|
|
||||||
|
|
||||||
yomichan.on('optionsUpdated', onOptionsUpdated);
|
yomichan.on('optionsUpdated', onOptionsUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,126 +15,117 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global
|
class StorageController {
|
||||||
* api
|
constructor() {
|
||||||
*/
|
this._mostRecentStorageEstimate = null;
|
||||||
|
this._storageEstimateFailed = false;
|
||||||
function storageBytesToLabeledString(size) {
|
this._isUpdating = false;
|
||||||
const base = 1000;
|
|
||||||
const labels = [' bytes', 'KB', 'MB', 'GB'];
|
|
||||||
let labelIndex = 0;
|
|
||||||
while (size >= base) {
|
|
||||||
size /= base;
|
|
||||||
++labelIndex;
|
|
||||||
}
|
}
|
||||||
const label = labelIndex === 0 ? `${size}` : size.toFixed(1);
|
|
||||||
return `${label}${labels[labelIndex]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function storageEstimate() {
|
prepare() {
|
||||||
try {
|
this._preparePersistentStorage();
|
||||||
return (storageEstimate.mostRecent = await navigator.storage.estimate());
|
this.updateStats();
|
||||||
} catch (e) {
|
document.querySelector('#storage-refresh').addEventListener('click', this.updateStats.bind(this), false);
|
||||||
// NOP
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
storageEstimate.mostRecent = null;
|
|
||||||
|
|
||||||
async function isStoragePeristent() {
|
async updateStats() {
|
||||||
try {
|
try {
|
||||||
return await navigator.storage.persisted();
|
this._isUpdating = true;
|
||||||
} catch (e) {
|
|
||||||
// NOP
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function storageInfoInitialize() {
|
const estimate = await this._storageEstimate();
|
||||||
storagePersistInitialize();
|
const valid = (estimate !== null);
|
||||||
const {browser, platform} = await api.getEnvironmentInfo();
|
|
||||||
document.documentElement.dataset.browser = browser;
|
|
||||||
document.documentElement.dataset.operatingSystem = platform.os;
|
|
||||||
|
|
||||||
await storageShowInfo();
|
if (valid) {
|
||||||
|
// Firefox reports usage as 0 when persistent storage is enabled.
|
||||||
|
const finite = (estimate.usage > 0 || !(await this._isStoragePeristent()));
|
||||||
|
if (finite) {
|
||||||
|
document.querySelector('#storage-usage').textContent = this._bytesToLabeledString(estimate.usage);
|
||||||
|
document.querySelector('#storage-quota').textContent = this._bytesToLabeledString(estimate.quota);
|
||||||
|
}
|
||||||
|
document.querySelector('#storage-use-finite').classList.toggle('storage-hidden', !finite);
|
||||||
|
document.querySelector('#storage-use-infinite').classList.toggle('storage-hidden', finite);
|
||||||
|
}
|
||||||
|
|
||||||
document.querySelector('#storage-refresh').addEventListener('click', storageShowInfo, false);
|
document.querySelector('#storage-use').classList.toggle('storage-hidden', !valid);
|
||||||
}
|
document.querySelector('#storage-error').classList.toggle('storage-hidden', valid);
|
||||||
|
|
||||||
async function storageUpdateStats() {
|
return valid;
|
||||||
storageUpdateStats.isUpdating = true;
|
} finally {
|
||||||
|
this._isUpdating = false;
|
||||||
const estimate = await storageEstimate();
|
|
||||||
const valid = (estimate !== null);
|
|
||||||
|
|
||||||
if (valid) {
|
|
||||||
// Firefox reports usage as 0 when persistent storage is enabled.
|
|
||||||
const finite = (estimate.usage > 0 || !(await isStoragePeristent()));
|
|
||||||
if (finite) {
|
|
||||||
document.querySelector('#storage-usage').textContent = storageBytesToLabeledString(estimate.usage);
|
|
||||||
document.querySelector('#storage-quota').textContent = storageBytesToLabeledString(estimate.quota);
|
|
||||||
}
|
}
|
||||||
document.querySelector('#storage-use-finite').classList.toggle('storage-hidden', !finite);
|
|
||||||
document.querySelector('#storage-use-infinite').classList.toggle('storage-hidden', finite);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
storageUpdateStats.isUpdating = false;
|
// Private
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
storageUpdateStats.isUpdating = false;
|
|
||||||
|
|
||||||
async function storageShowInfo() {
|
async _preparePersistentStorage() {
|
||||||
storageSpinnerShow(true);
|
if (!(navigator.storage && navigator.storage.persist)) {
|
||||||
|
// Not supported
|
||||||
const valid = await storageUpdateStats();
|
|
||||||
document.querySelector('#storage-use').classList.toggle('storage-hidden', !valid);
|
|
||||||
document.querySelector('#storage-error').classList.toggle('storage-hidden', valid);
|
|
||||||
|
|
||||||
storageSpinnerShow(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function storageSpinnerShow(show) {
|
|
||||||
const spinner = $('#storage-spinner');
|
|
||||||
if (show) {
|
|
||||||
spinner.show();
|
|
||||||
} else {
|
|
||||||
spinner.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function storagePersistInitialize() {
|
|
||||||
if (!(navigator.storage && navigator.storage.persist)) {
|
|
||||||
// Not supported
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = document.querySelector('#storage-persist-info');
|
|
||||||
const button = document.querySelector('#storage-persist-button');
|
|
||||||
const checkbox = document.querySelector('#storage-persist-button-checkbox');
|
|
||||||
|
|
||||||
info.classList.remove('storage-hidden');
|
|
||||||
button.classList.remove('storage-hidden');
|
|
||||||
|
|
||||||
let persisted = await isStoragePeristent();
|
|
||||||
checkbox.checked = persisted;
|
|
||||||
|
|
||||||
button.addEventListener('click', async () => {
|
|
||||||
if (persisted) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let result = false;
|
|
||||||
|
const info = document.querySelector('#storage-persist-info');
|
||||||
|
const button = document.querySelector('#storage-persist-button');
|
||||||
|
const checkbox = document.querySelector('#storage-persist-button-checkbox');
|
||||||
|
|
||||||
|
info.classList.remove('storage-hidden');
|
||||||
|
button.classList.remove('storage-hidden');
|
||||||
|
|
||||||
|
let persisted = await this._isStoragePeristent();
|
||||||
|
checkbox.checked = persisted;
|
||||||
|
|
||||||
|
button.addEventListener('click', async () => {
|
||||||
|
if (persisted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let result = false;
|
||||||
|
try {
|
||||||
|
result = await navigator.storage.persist();
|
||||||
|
} catch (e) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
persisted = true;
|
||||||
|
checkbox.checked = true;
|
||||||
|
this.updateStats();
|
||||||
|
} else {
|
||||||
|
document.querySelector('.storage-persist-fail-warning').classList.remove('storage-hidden');
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _storageEstimate() {
|
||||||
|
if (this._storageEstimateFailed && this._mostRecentStorageEstimate === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
result = await navigator.storage.persist();
|
const value = await navigator.storage.estimate();
|
||||||
|
this._mostRecentStorageEstimate = value;
|
||||||
|
return value;
|
||||||
|
} catch (e) {
|
||||||
|
this._storageEstimateFailed = true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_bytesToLabeledString(size) {
|
||||||
|
const base = 1000;
|
||||||
|
const labels = [' bytes', 'KB', 'MB', 'GB'];
|
||||||
|
let labelIndex = 0;
|
||||||
|
while (size >= base) {
|
||||||
|
size /= base;
|
||||||
|
++labelIndex;
|
||||||
|
}
|
||||||
|
const label = labelIndex === 0 ? `${size}` : size.toFixed(1);
|
||||||
|
return `${label}${labels[labelIndex]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _isStoragePeristent() {
|
||||||
|
try {
|
||||||
|
return await navigator.storage.persisted();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
if (result) {
|
}
|
||||||
persisted = true;
|
|
||||||
checkbox.checked = true;
|
|
||||||
storageShowInfo();
|
|
||||||
} else {
|
|
||||||
$('.storage-persist-fail-warning').removeClass('storage-hidden');
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
}
|
}
|
||||||
|
@ -711,7 +711,6 @@
|
|||||||
|
|
||||||
<div id="storage-info">
|
<div id="storage-info">
|
||||||
<div>
|
<div>
|
||||||
<img src="/mixed/img/spinner.gif" class="pull-right" id="storage-spinner" />
|
|
||||||
<h3>Storage</h3>
|
<h3>Storage</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user