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,
#dict-spinner, #dict-import-progress,
.storage-hidden, #storage-spinner {
.storage-hidden {
display: none;
}

View File

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

View File

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

View File

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

View File

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