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,
|
||||
#dict-spinner, #dict-import-progress,
|
||||
.storage-hidden, #storage-spinner {
|
||||
.storage-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -22,14 +22,9 @@
|
||||
* getOptionsFullMutable
|
||||
* getOptionsMutable
|
||||
* settingsSaveOptions
|
||||
* storageEstimate
|
||||
* storageUpdateStats
|
||||
* utilBackgroundIsolate
|
||||
*/
|
||||
|
||||
let dictionaryUI = null;
|
||||
|
||||
|
||||
class SettingsDictionaryListUI {
|
||||
constructor(container, template, extraContainer, extraTemplate) {
|
||||
this.container = container;
|
||||
@ -308,13 +303,13 @@ class SettingsDictionaryEntryUI {
|
||||
|
||||
await api.deleteDictionary(this.dictionaryInfo.title, onProgress);
|
||||
} catch (e) {
|
||||
dictionaryErrorsShow([e]);
|
||||
this.dictionaryErrorsShow([e]);
|
||||
} finally {
|
||||
prevention.end();
|
||||
this.isDeleting = false;
|
||||
progress.hidden = true;
|
||||
|
||||
onDatabaseUpdated();
|
||||
this.onDatabaseUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,340 +383,341 @@ class SettingsDictionaryExtraUI {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function dictSettingsInitialize() {
|
||||
dictionaryUI = new SettingsDictionaryListUI(
|
||||
document.querySelector('#dict-groups'),
|
||||
document.querySelector('#dict-template'),
|
||||
document.querySelector('#dict-groups-extra'),
|
||||
document.querySelector('#dict-extra-template')
|
||||
);
|
||||
dictionaryUI.save = settingsSaveOptions;
|
||||
|
||||
document.querySelector('#dict-purge-button').addEventListener('click', onDictionaryPurgeButtonClick, false);
|
||||
document.querySelector('#dict-purge-confirm').addEventListener('click', onDictionaryPurge, false);
|
||||
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);
|
||||
document.querySelector('#database-enable-prefix-wildcard-searches').addEventListener('change', onDatabaseEnablePrefixWildcardSearchesChanged, false);
|
||||
|
||||
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;
|
||||
}
|
||||
class DictionaryController {
|
||||
constructor(storageController) {
|
||||
this._storageController = storageController;
|
||||
this._dictionaryUI = null;
|
||||
this._dictionaryErrorToStringOverrides = [
|
||||
[
|
||||
'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.'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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 = settingsSaveOptions;
|
||||
|
||||
document.querySelector('#dict-purge-button').addEventListener('click', this._onPurgeButtonClick.bind(this), false);
|
||||
document.querySelector('#dict-purge-confirm').addEventListener('click', this._onPurgeConfirmButtonClick.bind(this), false);
|
||||
document.querySelector('#dict-file-button').addEventListener('click', this._onImportButtonClick.bind(this), false);
|
||||
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 select = e.target;
|
||||
const value = select.value;
|
||||
const optionsContext = getOptionsContext();
|
||||
const options = await getOptionsMutable(optionsContext);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
this._dictionaryUI.setOptionsDictionaries(options.dictionaries);
|
||||
|
||||
const optionsFull = await api.optionsGetFull();
|
||||
document.querySelector('#database-enable-prefix-wildcard-searches').checked = optionsFull.global.database.prefixWildcardsSupported;
|
||||
|
||||
const importDetails = {
|
||||
prefixWildcardsSupported: optionsFull.global.database.prefixWildcardsSupported
|
||||
};
|
||||
await this._updateMainDictionarySelectValue();
|
||||
}
|
||||
|
||||
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})`;
|
||||
// Private
|
||||
|
||||
_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 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]);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
_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();
|
||||
|
||||
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.`);
|
||||
dictionaryErrorsShow(errors2);
|
||||
}
|
||||
this._onDatabaseUpdated();
|
||||
} catch (err) {
|
||||
this._dictionaryErrorsShow([err]);
|
||||
} 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;
|
||||
dictImportInfo.textContent = '';
|
||||
dictFile.val('');
|
||||
dictControls.show();
|
||||
dictProgress.hide();
|
||||
async _onImportFileChange(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();
|
||||
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
|
||||
* AnkiTemplatesController
|
||||
* AudioController
|
||||
* DictionaryController
|
||||
* ProfileController
|
||||
* SettingsBackup
|
||||
* SettingsController
|
||||
* StorageController
|
||||
* api
|
||||
* appearanceInitialize
|
||||
* dictSettingsInitialize
|
||||
* onDictionaryOptionsChanged
|
||||
* storageInfoInitialize
|
||||
* utilBackend
|
||||
* utilBackgroundIsolate
|
||||
*/
|
||||
@ -270,7 +269,9 @@ async function onOptionsUpdated({source}) {
|
||||
if (ankiTemplatesController !== null) {
|
||||
ankiTemplatesController.updateValue();
|
||||
}
|
||||
onDictionaryOptionsChanged();
|
||||
if (dictionaryController !== null) {
|
||||
dictionaryController.optionsChanged();
|
||||
}
|
||||
if (ankiController !== null) {
|
||||
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 ankiTemplatesController = null;
|
||||
let dictionaryController = null;
|
||||
|
||||
async function onReady() {
|
||||
api.forwardLogsToBackend();
|
||||
@ -314,22 +322,25 @@ async function onReady() {
|
||||
const settingsController = new SettingsController();
|
||||
settingsController.prepare();
|
||||
|
||||
setupEnvironmentInfo();
|
||||
showExtensionInformation();
|
||||
|
||||
const storageController = new StorageController();
|
||||
storageController.prepare();
|
||||
|
||||
await settingsPopulateModifierKeys();
|
||||
formSetupEventListeners();
|
||||
appearanceInitialize();
|
||||
new AudioController().prepare();
|
||||
await (new ProfileController()).prepare();
|
||||
await dictSettingsInitialize();
|
||||
dictionaryController = new DictionaryController(storageController);
|
||||
dictionaryController.prepare();
|
||||
ankiController = new AnkiController();
|
||||
ankiController.prepare();
|
||||
ankiTemplatesController = new AnkiTemplatesController(ankiController);
|
||||
ankiTemplatesController.prepare();
|
||||
new SettingsBackup().prepare();
|
||||
|
||||
storageInfoInitialize();
|
||||
|
||||
yomichan.on('optionsUpdated', onOptionsUpdated);
|
||||
}
|
||||
|
||||
|
@ -15,126 +15,117 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* global
|
||||
* api
|
||||
*/
|
||||
|
||||
function storageBytesToLabeledString(size) {
|
||||
const base = 1000;
|
||||
const labels = [' bytes', 'KB', 'MB', 'GB'];
|
||||
let labelIndex = 0;
|
||||
while (size >= base) {
|
||||
size /= base;
|
||||
++labelIndex;
|
||||
class StorageController {
|
||||
constructor() {
|
||||
this._mostRecentStorageEstimate = null;
|
||||
this._storageEstimateFailed = false;
|
||||
this._isUpdating = false;
|
||||
}
|
||||
const label = labelIndex === 0 ? `${size}` : size.toFixed(1);
|
||||
return `${label}${labels[labelIndex]}`;
|
||||
}
|
||||
|
||||
async function storageEstimate() {
|
||||
try {
|
||||
return (storageEstimate.mostRecent = await navigator.storage.estimate());
|
||||
} catch (e) {
|
||||
// NOP
|
||||
prepare() {
|
||||
this._preparePersistentStorage();
|
||||
this.updateStats();
|
||||
document.querySelector('#storage-refresh').addEventListener('click', this.updateStats.bind(this), false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
storageEstimate.mostRecent = null;
|
||||
|
||||
async function isStoragePeristent() {
|
||||
try {
|
||||
return await navigator.storage.persisted();
|
||||
} catch (e) {
|
||||
// NOP
|
||||
}
|
||||
return false;
|
||||
}
|
||||
async updateStats() {
|
||||
try {
|
||||
this._isUpdating = true;
|
||||
|
||||
async function storageInfoInitialize() {
|
||||
storagePersistInitialize();
|
||||
const {browser, platform} = await api.getEnvironmentInfo();
|
||||
document.documentElement.dataset.browser = browser;
|
||||
document.documentElement.dataset.operatingSystem = platform.os;
|
||||
const estimate = await this._storageEstimate();
|
||||
const valid = (estimate !== null);
|
||||
|
||||
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() {
|
||||
storageUpdateStats.isUpdating = true;
|
||||
|
||||
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);
|
||||
return valid;
|
||||
} finally {
|
||||
this._isUpdating = false;
|
||||
}
|
||||
document.querySelector('#storage-use-finite').classList.toggle('storage-hidden', !finite);
|
||||
document.querySelector('#storage-use-infinite').classList.toggle('storage-hidden', finite);
|
||||
}
|
||||
|
||||
storageUpdateStats.isUpdating = false;
|
||||
return valid;
|
||||
}
|
||||
storageUpdateStats.isUpdating = false;
|
||||
// Private
|
||||
|
||||
async function storageShowInfo() {
|
||||
storageSpinnerShow(true);
|
||||
|
||||
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) {
|
||||
async _preparePersistentStorage() {
|
||||
if (!(navigator.storage && navigator.storage.persist)) {
|
||||
// Not supported
|
||||
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 {
|
||||
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) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
if (result) {
|
||||
persisted = true;
|
||||
checkbox.checked = true;
|
||||
storageShowInfo();
|
||||
} else {
|
||||
$('.storage-persist-fail-warning').removeClass('storage-hidden');
|
||||
}
|
||||
}, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -711,7 +711,6 @@
|
||||
|
||||
<div id="storage-info">
|
||||
<div>
|
||||
<img src="/mixed/img/spinner.gif" class="pull-right" id="storage-spinner" />
|
||||
<h3>Storage</h3>
|
||||
</div>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user