diff options
| author | Darius Jahandarie <djahandarie@gmail.com> | 2023-12-06 03:53:16 +0000 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-12-06 03:53:16 +0000 | 
| commit | bd5bc1a5db29903bc098995cd9262c4576bf76af (patch) | |
| tree | c9214189e0214480fcf6539ad1c6327aef6cbd1c /ext/js/pages/settings/backup-controller.js | |
| parent | fd6bba8a2a869eaf2b2c1fa49001f933fce3c618 (diff) | |
| parent | 23e6fb76319c9ed7c9bcdc3efba39bc5dd38f288 (diff) | |
Merge pull request #339 from toasted-nutbread/type-annotations
Type annotations
Diffstat (limited to 'ext/js/pages/settings/backup-controller.js')
| -rw-r--r-- | ext/js/pages/settings/backup-controller.js | 188 | 
1 files changed, 161 insertions, 27 deletions
diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 2863c505..bf44bb90 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -24,18 +24,37 @@ import {yomitan} from '../../yomitan.js';  import {DictionaryController} from './dictionary-controller.js';  export class BackupController { +    /** +     * @param {import('./settings-controller.js').SettingsController} settingsController +     * @param {?import('./modal-controller.js').ModalController} modalController +     */      constructor(settingsController, modalController) { +        /** @type {import('./settings-controller.js').SettingsController} */          this._settingsController = settingsController; +        /** @type {?import('./modal-controller.js').ModalController} */          this._modalController = modalController; +        /** @type {?import('core').TokenObject} */          this._settingsExportToken = null; +        /** @type {?() => void} */          this._settingsExportRevoke = null; +        /** @type {number} */          this._currentVersion = 0; +        /** @type {?import('./modal.js').Modal} */          this._settingsResetModal = null; +        /** @type {?import('./modal.js').Modal} */          this._settingsImportErrorModal = null; +        /** @type {?import('./modal.js').Modal} */          this._settingsImportWarningModal = null; +        /** @type {?OptionsUtil} */          this._optionsUtil = null; +        /** +         * +         */          this._dictionariesDatabaseName = 'dict'; +        /** +         * +         */          this._settingsExportDatabaseToken = null;          try { @@ -45,6 +64,7 @@ export class BackupController {          }      } +    /** */      async prepare() {          if (this._optionsUtil !== null) {              await this._optionsUtil.prepare(); @@ -69,13 +89,27 @@ export class BackupController {      // Private -    _addNodeEventListener(selector, ...args) { +    /** +     * @param {string} selector +     * @param {string} eventName +     * @param {(event: Event) => void} callback +     * @param {boolean} capture +     */ +    _addNodeEventListener(selector, eventName, callback, capture) {          const node = document.querySelector(selector);          if (node === null) { return; } -        node.addEventListener(...args); +        node.addEventListener(eventName, callback, capture);      } +    /** +     * @param {Date} date +     * @param {string} dateSeparator +     * @param {string} dateTimeSeparator +     * @param {string} timeSeparator +     * @param {number} resolution +     * @returns {string} +     */      _getSettingsExportDateString(date, dateSeparator, dateTimeSeparator, timeSeparator, resolution) {          const values = [              date.getUTCFullYear().toString(), @@ -93,6 +127,10 @@ export class BackupController {          return values.slice(0, resolution * 2 - 1).join('');      } +    /** +     * @param {Date} date +     * @returns {Promise<import('backup-controller').BackupData>} +     */      async _getSettingsExportData(date) {          const optionsFull = await this._settingsController.getOptionsFull();          const environment = await yomitan.api.getEnvironmentInfo(); @@ -120,11 +158,19 @@ export class BackupController {          return data;      } +    /** +     * @param {Blob} blob +     * @param {string} fileName +     */      _saveBlob(blob, fileName) { -        if (typeof navigator === 'object' && typeof navigator.msSaveBlob === 'function') { -            if (navigator.msSaveBlob(blob)) { -                return; -            } +        if ( +            typeof navigator === 'object' && navigator !== null && +            // @ts-expect-error - call for legacy Edge +            typeof navigator.msSaveBlob === 'function' && +            // @ts-expect-error - call for legacy Edge +            navigator.msSaveBlob(blob) +        ) { +            return;          }          const blobUrl = URL.createObjectURL(blob); @@ -146,6 +192,7 @@ export class BackupController {          setTimeout(revoke, 60000);      } +    /** */      async _onSettingsExportClick() {          if (this._settingsExportRevoke !== null) {              this._settingsExportRevoke(); @@ -154,6 +201,7 @@ export class BackupController {          const date = new Date(Date.now()); +        /** @type {?import('core').TokenObject} */          const token = {};          this._settingsExportToken = token;          const data = await this._getSettingsExportData(date); @@ -168,10 +216,14 @@ export class BackupController {          this._saveBlob(blob, fileName);      } +    /** +     * @param {File} file +     * @returns {Promise<ArrayBuffer>} +     */      _readFileArrayBuffer(file) {          return new Promise((resolve, reject) => {              const reader = new FileReader(); -            reader.onload = () => resolve(reader.result); +            reader.onload = () => resolve(/** @type {ArrayBuffer} */ (reader.result));              reader.onerror = () => reject(reader.error);              reader.readAsArrayBuffer(file);          }); @@ -179,19 +231,33 @@ export class BackupController {      // Importing +    /** +     * @param {import('settings').Options} optionsFull +     */      async _settingsImportSetOptionsFull(optionsFull) {          await this._settingsController.setAllSettings(optionsFull);      } +    /** +     * @param {Error} error +     */      _showSettingsImportError(error) {          log.error(error); -        document.querySelector('#settings-import-error-message').textContent = `${error}`; -        this._settingsImportErrorModal.setVisible(true); +        const element = /** @type {HTMLElement} */ (document.querySelector('#settings-import-error-message')); +        element.textContent = `${error}`; +        if (this._settingsImportErrorModal !== null) { +            this._settingsImportErrorModal.setVisible(true); +        }      } +    /** +     * @param {Set<string>} warnings +     * @returns {Promise<import('backup-controller').ShowSettingsImportWarningsResult>} +     */      async _showSettingsImportWarnings(warnings) {          const modal = this._settingsImportWarningModal; -        const buttons = document.querySelectorAll('.settings-import-warning-import-button'); +        if (modal === null) { return {result: false}; } +        const buttons = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.settings-import-warning-import-button'));          const messageContainer = document.querySelector('#settings-import-warning-message');          if (buttons.length === 0 || messageContainer === null) {              return {result: false}; @@ -212,20 +278,30 @@ export class BackupController {          // Wait for modal to close          return new Promise((resolve) => { +            /** +             * @param {MouseEvent} e +             */              const onButtonClick = (e) => { +                const element = /** @type {HTMLElement} */ (e.currentTarget);                  e.preventDefault();                  complete({                      result: true, -                    sanitize: e.currentTarget.dataset.importSanitize === 'true' +                    sanitize: element.dataset.importSanitize === 'true'                  });                  modal.setVisible(false);              }; +            /** +             * @param {import('panel-element').VisibilityChangedEvent} details +             */              const onModalVisibilityChanged = ({visible}) => {                  if (visible) { return; }                  complete({result: false});              };              let completed = false; +            /** +             * @param {import('backup-controller').ShowSettingsImportWarningsResult} result +             */              const complete = (result) => {                  if (completed) { return; }                  completed = true; @@ -246,6 +322,10 @@ export class BackupController {          });      } +    /** +     * @param {string} urlString +     * @returns {boolean} +     */      _isLocalhostUrl(urlString) {          try {              const url = new URL(urlString); @@ -266,6 +346,11 @@ export class BackupController {          return false;      } +    /** +     * @param {import('settings').ProfileOptions} options +     * @param {boolean} dryRun +     * @returns {string[]} +     */      _settingsImportSanitizeProfileOptions(options, dryRun) {          const warnings = []; @@ -308,6 +393,11 @@ export class BackupController {          return warnings;      } +    /** +     * @param {import('settings').Options} optionsFull +     * @param {boolean} dryRun +     * @returns {Set<string>} +     */      _settingsImportSanitizeOptions(optionsFull, dryRun) {          const warnings = new Set(); @@ -328,7 +418,12 @@ export class BackupController {          return warnings;      } +    /** +     * @param {File} file +     */      async _importSettingsFile(file) { +        if (this._optionsUtil === null) { throw new Error('OptionsUtil invalid'); } +          const dataString = ArrayBufferUtil.arrayBufferUtf8Decode(await this._readFileArrayBuffer(file));          const data = JSON.parse(dataString); @@ -383,31 +478,44 @@ export class BackupController {          await this._settingsImportSetOptionsFull(optionsFull);      } +    /** */      _onSettingsImportClick() { -        document.querySelector('#settings-import-file').click(); +        const element = /** @type {HTMLElement} */ (document.querySelector('#settings-import-file')); +        element.click();      } +    /** +     * @param {Event} e +     */      async _onSettingsImportFileChange(e) { -        const files = e.target.files; -        if (files.length === 0) { return; } +        const element = /** @type {HTMLInputElement} */ (e.currentTarget); +        const files = element.files; +        if (files === null || files.length === 0) { return; }          const file = files[0]; -        e.target.value = null; +        element.value = '';          try {              await this._importSettingsFile(file);          } catch (error) { -            this._showSettingsImportError(error); +            this._showSettingsImportError(error instanceof Error ? error : new Error(`${error}`));          }      }      // Resetting +    /** */      _onSettingsResetClick() { +        if (this._settingsResetModal === null) { return; }          this._settingsResetModal.setVisible(true);      } +    /** */      async _onSettingsResetConfirmClick() { -        this._settingsResetModal.setVisible(false); +        if (this._optionsUtil === null) { throw new Error('OptionsUtil invalid'); } + +        if (this._settingsResetModal !== null) { +            this._settingsResetModal.setVisible(false); +        }          // Get default options          const optionsFull = this._optionsUtil.getDefault(); @@ -425,8 +533,12 @@ export class BackupController {      // Exporting Dictionaries Database +    /** +     * @param {string} message +     * @param {boolean} [isWarning] +     */      _databaseExportImportErrorMessage(message, isWarning=false) { -        const errorMessageContainer = document.querySelector('#db-ops-error-report'); +        const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report'));          errorMessageContainer.style.display = 'block';          errorMessageContainer.textContent = message; @@ -439,9 +551,12 @@ export class BackupController {          }      } +    /** +     * @param {{totalRows: number, completedRows: number, done: boolean}} details +     */      _databaseExportProgressCallback({totalRows, completedRows, done}) {          console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); -        const messageContainer = document.querySelector('#db-ops-progress-report'); +        const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report'));          messageContainer.style.display = 'block';          messageContainer.textContent = `Export Progress: ${completedRows} of ${totalRows} rows completed`; @@ -451,6 +566,10 @@ export class BackupController {          }      } +    /** +     * @param {string} databaseName +     * @returns {Promise<Blob>} +     */      async _exportDatabase(databaseName) {          const db = await new Dexie(databaseName).open();          const blob = await db.export({progressCallback: this._databaseExportProgressCallback}); @@ -458,6 +577,9 @@ export class BackupController {          return blob;      } +    /** +     * +     */      async _onSettingsExportDatabaseClick() {          if (this._settingsExportDatabaseToken !== null) {              // An existing import or export is in progress. @@ -465,7 +587,7 @@ export class BackupController {              return;          } -        const errorMessageContainer = document.querySelector('#db-ops-error-report'); +        const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report'));          errorMessageContainer.style.display = 'none';          const date = new Date(Date.now()); @@ -488,9 +610,12 @@ export class BackupController {      // Importing Dictionaries Database +    /** +     * @param {{totalRows: number, completedRows: number, done: boolean}} details +     */      _databaseImportProgressCallback({totalRows, completedRows, done}) {          console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); -        const messageContainer = document.querySelector('#db-ops-progress-report'); +        const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report'));          messageContainer.style.display = 'block';          messageContainer.style.color = '#4169e1';          messageContainer.textContent = `Import Progress: ${completedRows} of ${totalRows} rows completed`; @@ -502,6 +627,10 @@ export class BackupController {          }      } +    /** +     * @param {string} databaseName +     * @param {File} file +     */      async _importDatabase(databaseName, file) {          await yomitan.api.purgeDatabase();          await Dexie.import(file, {progressCallback: this._databaseImportProgressCallback}); @@ -509,10 +638,14 @@ export class BackupController {          yomitan.trigger('storageChanged');      } +    /** */      _onSettingsImportDatabaseClick() { -        document.querySelector('#settings-import-db').click(); +        /** @type {HTMLElement} */ (document.querySelector('#settings-import-db')).click();      } +    /** +     * @param {Event} e +     */      async _onSettingsImportDatabaseChange(e) {          if (this._settingsExportDatabaseToken !== null) {              // An existing import or export is in progress. @@ -520,22 +653,23 @@ export class BackupController {              return;          } -        const errorMessageContainer = document.querySelector('#db-ops-error-report'); +        const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report'));          errorMessageContainer.style.display = 'none'; -        const files = e.target.files; -        if (files.length === 0) { return; } +        const element = /** @type {HTMLInputElement} */ (e.currentTarget); +        const files = element.files; +        if (files === null || files.length === 0) { return; }          const pageExitPrevention = this._settingsController.preventPageExit();          const file = files[0]; -        e.target.value = null; +        element.value = '';          try {              const token = {};              this._settingsExportDatabaseToken = token;              await this._importDatabase(this._dictionariesDatabaseName, file);          } catch (error) {              console.log(error); -            const messageContainer = document.querySelector('#db-ops-progress-report'); +            const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report'));              messageContainer.style.color = 'red';              this._databaseExportImportErrorMessage('Encountered errors when importing. Please restart the browser and try again. If it continues to fail, reinstall Yomitan and import dictionaries one-by-one.');          } finally {  |