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:
toasted-nutbread 2020-05-29 20:25:22 -04:00 committed by GitHub
parent c62f980f37
commit 418e8a57bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 432 additions and 435 deletions

View File

@ -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;
} }

View File

@ -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();
}

View File

@ -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);
} }

View File

@ -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);
} }

View File

@ -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>