diff options
Diffstat (limited to 'ext/js/pages/settings/profile-controller.js')
-rw-r--r-- | ext/js/pages/settings/profile-controller.js | 287 |
1 files changed, 223 insertions, 64 deletions
diff --git a/ext/js/pages/settings/profile-controller.js b/ext/js/pages/settings/profile-controller.js index a5bf41b3..c82223b8 100644 --- a/ext/js/pages/settings/profile-controller.js +++ b/ext/js/pages/settings/profile-controller.js @@ -21,50 +21,77 @@ import {yomitan} from '../../yomitan.js'; import {ProfileConditionsUI} from './profile-conditions-ui.js'; export class ProfileController { + /** + * @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 {ProfileConditionsUI} */ this._profileConditionsUI = new ProfileConditionsUI(settingsController); + /** @type {?number} */ this._profileConditionsIndex = null; + /** @type {?HTMLSelectElement} */ this._profileActiveSelect = null; + /** @type {?HTMLSelectElement} */ this._profileTargetSelect = null; + /** @type {?HTMLSelectElement} */ this._profileCopySourceSelect = null; + /** @type {?HTMLElement} */ this._removeProfileNameElement = null; + /** @type {?HTMLButtonElement} */ this._profileAddButton = null; + /** @type {?HTMLButtonElement} */ this._profileRemoveConfirmButton = null; + /** @type {?HTMLButtonElement} */ this._profileCopyConfirmButton = null; + /** @type {?HTMLElement} */ this._profileEntryListContainer = null; + /** @type {?HTMLElement} */ this._profileConditionsProfileName = null; + /** @type {?import('./modal.js').Modal} */ this._profileRemoveModal = null; + /** @type {?import('./modal.js').Modal} */ this._profileCopyModal = null; + /** @type {?import('./modal.js').Modal} */ this._profileConditionsModal = null; + /** @type {boolean} */ this._profileEntriesSupported = false; + /** @type {ProfileEntry[]} */ this._profileEntryList = []; + /** @type {import('settings').Profile[]} */ this._profiles = []; + /** @type {number} */ this._profileCurrent = 0; } + /** @type {number} */ get profileCount() { return this._profiles.length; } + /** @type {number} */ get profileCurrentIndex() { return this._profileCurrent; } + /** */ async prepare() { const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); this._profileConditionsUI.os = os; - this._profileActiveSelect = document.querySelector('#profile-active-select'); - this._profileTargetSelect = document.querySelector('#profile-target-select'); - this._profileCopySourceSelect = document.querySelector('#profile-copy-source-select'); - this._removeProfileNameElement = document.querySelector('#profile-remove-name'); - this._profileAddButton = document.querySelector('#profile-add-button'); - this._profileRemoveConfirmButton = document.querySelector('#profile-remove-confirm-button'); - this._profileCopyConfirmButton = document.querySelector('#profile-copy-confirm-button'); - this._profileEntryListContainer = document.querySelector('#profile-entry-list'); - this._profileConditionsProfileName = document.querySelector('#profile-conditions-profile-name'); + this._profileActiveSelect = /** @type {HTMLSelectElement} */ (document.querySelector('#profile-active-select')); + this._profileTargetSelect = /** @type {HTMLSelectElement} */ (document.querySelector('#profile-target-select')); + this._profileCopySourceSelect = /** @type {HTMLSelectElement} */ (document.querySelector('#profile-copy-source-select')); + this._removeProfileNameElement = /** @type {HTMLElement} */ (document.querySelector('#profile-remove-name')); + this._profileAddButton = /** @type {HTMLButtonElement} */ (document.querySelector('#profile-add-button')); + this._profileRemoveConfirmButton = /** @type {HTMLButtonElement} */ (document.querySelector('#profile-remove-confirm-button')); + this._profileCopyConfirmButton = /** @type {HTMLButtonElement} */ (document.querySelector('#profile-copy-confirm-button')); + this._profileEntryListContainer = /** @type {HTMLElement} */ (document.querySelector('#profile-entry-list')); + this._profileConditionsProfileName = /** @type {HTMLElement} */ (document.querySelector('#profile-conditions-profile-name')); this._profileRemoveModal = this._modalController.getModal('profile-remove'); this._profileCopyModal = this._modalController.getModal('profile-copy'); this._profileConditionsModal = this._modalController.getModal('profile-conditions'); @@ -82,6 +109,10 @@ export class ProfileController { this._onOptionsChanged(); } + /** + * @param {number} profileIndex + * @param {number} offset + */ async moveProfile(profileIndex, offset) { if (this._getProfile(profileIndex) === null) { return; } @@ -91,6 +122,10 @@ export class ProfileController { await this.swapProfiles(profileIndex, profileIndexNew); } + /** + * @param {number} profileIndex + * @param {string} value + */ async setProfileName(profileIndex, value) { const profile = this._getProfile(profileIndex); if (profile === null) { return; } @@ -104,11 +139,14 @@ export class ProfileController { await this._settingsController.setGlobalSetting(`profiles[${profileIndex}].name`, value); } + /** + * @param {number} profileIndex + */ async setDefaultProfile(profileIndex) { const profile = this._getProfile(profileIndex); if (profile === null) { return; } - this._profileActiveSelect.value = `${profileIndex}`; + /** @type {HTMLSelectElement} */ (this._profileActiveSelect).value = `${profileIndex}`; this._profileCurrent = profileIndex; const profileEntry = this._getProfileEntry(profileIndex); @@ -117,6 +155,10 @@ export class ProfileController { await this._settingsController.setGlobalSetting('profileCurrent', profileIndex); } + /** + * @param {number} sourceProfileIndex + * @param {number} destinationProfileIndex + */ async copyProfile(sourceProfileIndex, destinationProfileIndex) { const sourceProfile = this._getProfile(sourceProfileIndex); if (sourceProfile === null || !this._getProfile(destinationProfileIndex)) { return; } @@ -140,9 +182,12 @@ export class ProfileController { await this._settingsController.refresh(); } + /** + * @param {number} profileIndex + */ async duplicateProfile(profileIndex) { const profile = this._getProfile(profileIndex); - if (this.profile === null) { return; } + if (profile === null) { return; } // Create new profile const newProfile = clone(profile); @@ -169,6 +214,9 @@ export class ProfileController { this._settingsController.profileIndex = index; } + /** + * @param {number} profileIndex + */ async deleteProfile(profileIndex) { const profile = this._getProfile(profileIndex); if (profile === null || this.profileCount <= 1) { return; } @@ -178,6 +226,7 @@ export class ProfileController { const settingsProfileIndex = this._settingsController.profileIndex; // Construct settings modifications + /** @type {import('settings-modifications').Modification[]} */ const modifications = [{ action: 'splice', path: 'profiles', @@ -225,6 +274,10 @@ export class ProfileController { await this._settingsController.modifyGlobalSettings(modifications); } + /** + * @param {number} index1 + * @param {number} index2 + */ async swapProfiles(index1, index2) { const profile1 = this._getProfile(index1); const profile2 = this._getProfile(index2); @@ -238,6 +291,7 @@ export class ProfileController { const settingsProfileIndexNew = this._getSwappedValue(settingsProfileIndex, index1, index2); // Construct settings modifications + /** @type {import('settings-modifications').Modification[]} */ const modifications = [{ action: 'swap', path1: `profiles[${index1}]`, @@ -278,15 +332,21 @@ export class ProfileController { } } + /** + * @param {number} profileIndex + */ openDeleteProfileModal(profileIndex) { const profile = this._getProfile(profileIndex); if (profile === null || this.profileCount <= 1) { return; } - this._removeProfileNameElement.textContent = profile.name; - this._profileRemoveModal.node.dataset.profileIndex = `${profileIndex}`; - this._profileRemoveModal.setVisible(true); + /** @type {HTMLElement} */ (this._removeProfileNameElement).textContent = profile.name; + /** @type {import('./modal.js').Modal} */ (this._profileRemoveModal).node.dataset.profileIndex = `${profileIndex}`; + /** @type {import('./modal.js').Modal} */ (this._profileRemoveModal).setVisible(true); } + /** + * @param {number} profileIndex + */ openCopyProfileModal(profileIndex) { const profile = this._getProfile(profileIndex); if (profile === null || this.profileCount <= 1) { return; } @@ -301,16 +361,20 @@ export class ProfileController { } const profileIndexString = `${profileIndex}`; - for (const option of this._profileCopySourceSelect.querySelectorAll('option')) { + const select = /** @type {HTMLSelectElement} */ (this._profileCopySourceSelect); + for (const option of select.querySelectorAll('option')) { const {value} = option; option.disabled = (value === profileIndexString); } - this._profileCopySourceSelect.value = `${copyFromIndex}`; + select.value = `${copyFromIndex}`; - this._profileCopyModal.node.dataset.profileIndex = `${profileIndex}`; - this._profileCopyModal.setVisible(true); + /** @type {import('./modal.js').Modal} */ (this._profileCopyModal).node.dataset.profileIndex = `${profileIndex}`; + /** @type {import('./modal.js').Modal} */ (this._profileCopyModal).setVisible(true); } + /** + * @param {number} profileIndex + */ openProfileConditionsModal(profileIndex) { const profile = this._getProfile(profileIndex); if (profile === null) { return; } @@ -328,6 +392,7 @@ export class ProfileController { // Private + /** */ async _onOptionsChanged() { // Update state const {profiles, profileCurrent} = await this._settingsController.getOptionsFull(); @@ -339,8 +404,8 @@ export class ProfileController { // Udpate UI this._updateProfileSelectOptions(); - this._profileActiveSelect.value = `${profileCurrent}`; - this._profileTargetSelect.value = `${settingsProfileIndex}`; + /** @type {HTMLSelectElement} */ (this._profileActiveSelect).value = `${profileCurrent}`; + /** @type {HTMLSelectElement} */ (this._profileTargetSelect).value = `${settingsProfileIndex}`; // Update profile conditions this._profileConditionsUI.cleanup(); @@ -361,51 +426,65 @@ export class ProfileController { } } + /** + * @param {Event} e + */ _onProfileActiveChange(e) { - const value = this._tryGetValidProfileIndex(e.currentTarget.value); + const element = /** @type {HTMLSelectElement} */ (e.currentTarget); + const value = this._tryGetValidProfileIndex(element.value); if (value === null) { return; } this.setDefaultProfile(value); } + /** + * @param {Event} e + */ _onProfileTargetChange(e) { - const value = this._tryGetValidProfileIndex(e.currentTarget.value); + const element = /** @type {HTMLSelectElement} */ (e.currentTarget); + const value = this._tryGetValidProfileIndex(element.value); if (value === null) { return; } this._settingsController.profileIndex = value; } + /** */ _onAdd() { this.duplicateProfile(this._settingsController.profileIndex); } + /** */ _onDeleteConfirm() { - const modal = this._profileRemoveModal; + const modal = /** @type {import('./modal.js').Modal} */ (this._profileRemoveModal); modal.setVisible(false); const {node} = modal; - let profileIndex = node.dataset.profileIndex; + const profileIndex = node.dataset.profileIndex; delete node.dataset.profileIndex; - profileIndex = this._tryGetValidProfileIndex(profileIndex); - if (profileIndex === null) { return; } + const validProfileIndex = this._tryGetValidProfileIndex(profileIndex); + if (validProfileIndex === null) { return; } - this.deleteProfile(profileIndex); + this.deleteProfile(validProfileIndex); } + /** */ _onCopyConfirm() { - const modal = this._profileCopyModal; + const modal = /** @type {import('./modal.js').Modal} */ (this._profileCopyModal); modal.setVisible(false); const {node} = modal; - let destinationProfileIndex = node.dataset.profileIndex; + const destinationProfileIndex = node.dataset.profileIndex; delete node.dataset.profileIndex; - destinationProfileIndex = this._tryGetValidProfileIndex(destinationProfileIndex); - if (destinationProfileIndex === null) { return; } + const validDestinationProfileIndex = this._tryGetValidProfileIndex(destinationProfileIndex); + if (validDestinationProfileIndex === null) { return; } - const sourceProfileIndex = this._tryGetValidProfileIndex(this._profileCopySourceSelect.value); + const sourceProfileIndex = this._tryGetValidProfileIndex(/** @type {HTMLSelectElement} */ (this._profileCopySourceSelect).value); if (sourceProfileIndex === null) { return; } - this.copyProfile(sourceProfileIndex, destinationProfileIndex); + this.copyProfile(sourceProfileIndex, validDestinationProfileIndex); } + /** + * @param {import('profile-conditions-ui').ConditionGroupCountChangedEvent} details + */ _onConditionGroupCountChanged({count, profileIndex}) { if (profileIndex >= 0 && profileIndex < this._profileEntryList.length) { const profileEntry = this._profileEntryList[profileIndex]; @@ -413,15 +492,19 @@ export class ProfileController { } } + /** + * @param {number} profileIndex + */ _addProfileEntry(profileIndex) { const profile = this._profiles[profileIndex]; - const node = this._settingsController.instantiateTemplate('profile-entry'); - const entry = new ProfileEntry(this, node); + const node = /** @type {HTMLElement} */ (this._settingsController.instantiateTemplate('profile-entry')); + const entry = new ProfileEntry(this, node, profile, profileIndex); this._profileEntryList.push(entry); - entry.prepare(profile, profileIndex); - this._profileEntryListContainer.appendChild(node); + entry.prepare(); + /** @type {HTMLElement} */ (this._profileEntryListContainer).appendChild(node); } + /** */ _updateProfileSelectOptions() { for (const select of this._getAllProfileSelects()) { const fragment = document.createDocumentFragment(); @@ -437,6 +520,10 @@ export class ProfileController { } } + /** + * @param {number} index + * @param {string} name + */ _updateSelectName(index, name) { const optionValue = `${index}`; for (const select of this._getAllProfileSelects()) { @@ -448,14 +535,21 @@ export class ProfileController { } } + /** + * @returns {HTMLSelectElement[]} + */ _getAllProfileSelects() { return [ - this._profileActiveSelect, - this._profileTargetSelect, - this._profileCopySourceSelect + /** @type {HTMLSelectElement} */ (this._profileActiveSelect), + /** @type {HTMLSelectElement} */ (this._profileTargetSelect), + /** @type {HTMLSelectElement} */ (this._profileCopySourceSelect) ]; } + /** + * @param {string|undefined} stringValue + * @returns {?number} + */ _tryGetValidProfileIndex(stringValue) { if (typeof stringValue !== 'string') { return null; } const intValue = parseInt(stringValue, 10); @@ -467,6 +561,12 @@ export class ProfileController { ); } + /** + * @param {string} name + * @param {import('settings').Profile[]} profiles + * @param {number} maxUniqueAttempts + * @returns {string} + */ _createCopyName(name, profiles, maxUniqueAttempts) { let space, index, prefix, suffix; const match = /^([\w\W]*\(Copy)((\s+)(\d+))?(\)\s*)$/.exec(name); @@ -502,44 +602,80 @@ export class ProfileController { } } + /** + * @template T + * @param {T} currentValue + * @param {T} value1 + * @param {T} value2 + * @returns {T} + */ _getSwappedValue(currentValue, value1, value2) { if (currentValue === value1) { return value2; } if (currentValue === value2) { return value1; } return currentValue; } + /** + * @param {number} profileIndex + * @returns {?import('settings').Profile} + */ _getProfile(profileIndex) { return (profileIndex >= 0 && profileIndex < this._profiles.length ? this._profiles[profileIndex] : null); } + /** + * @param {number} profileIndex + * @returns {?ProfileEntry} + */ _getProfileEntry(profileIndex) { return (profileIndex >= 0 && profileIndex < this._profileEntryList.length ? this._profileEntryList[profileIndex] : null); } + /** + * @param {Element} node1 + * @param {Element} node2 + */ _swapDomNodes(node1, node2) { const parent1 = node1.parentNode; const parent2 = node2.parentNode; const next1 = node1.nextSibling; const next2 = node2.nextSibling; - if (node2 !== next1) { parent1.insertBefore(node2, next1); } - if (node1 !== next2) { parent2.insertBefore(node1, next2); } + if (node2 !== next1 && parent1 !== null) { parent1.insertBefore(node2, next1); } + if (node1 !== next2 && parent2 !== null) { parent2.insertBefore(node1, next2); } } } class ProfileEntry { - constructor(profileController, node) { + /** + * @param {ProfileController} profileController + * @param {HTMLElement} node + * @param {import('settings').Profile} profile + * @param {number} index + */ + constructor(profileController, node, profile, index) { + /** @type {ProfileController} */ this._profileController = profileController; + /** @type {HTMLElement} */ this._node = node; - this._profile = null; - this._index = 0; - this._isDefaultRadio = null; - this._nameInput = null; - this._countLink = null; - this._countText = null; - this._menuButton = null; + /** @type {import('settings').Profile} */ + this._profile = profile; + /** @type {number} */ + this._index = index; + /** @type {HTMLInputElement} */ + this._isDefaultRadio = /** @type {HTMLInputElement} */ (node.querySelector('.profile-entry-is-default-radio')); + /** @type {HTMLInputElement} */ + this._nameInput = /** @type {HTMLInputElement} */ (node.querySelector('.profile-entry-name-input')); + /** @type {HTMLElement} */ + this._countLink = /** @type {HTMLElement} */ (node.querySelector('.profile-entry-condition-count-link')); + /** @type {HTMLElement} */ + this._countText = /** @type {HTMLElement} */ (node.querySelector('.profile-entry-condition-count')); + /** @type {HTMLButtonElement} */ + this._menuButton = /** @type {HTMLButtonElement} */ (node.querySelector('.profile-entry-menu-button')); + /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); } + /** @type {number} */ get index() { return this._index; } @@ -548,21 +684,13 @@ class ProfileEntry { this._index = value; } + /** @type {HTMLElement} */ get node() { return this._node; } - prepare(profile, index) { - this._profile = profile; - this._index = index; - - const node = this._node; - this._isDefaultRadio = node.querySelector('.profile-entry-is-default-radio'); - this._nameInput = node.querySelector('.profile-entry-name-input'); - this._countLink = node.querySelector('.profile-entry-condition-count-link'); - this._countText = node.querySelector('.profile-entry-condition-count'); - this._menuButton = node.querySelector('.profile-entry-menu-button'); - + /** */ + prepare() { this.updateState(); this._eventListeners.addEventListener(this._isDefaultRadio, 'change', this._onIsDefaultRadioChange.bind(this), false); @@ -572,6 +700,7 @@ class ProfileEntry { this._eventListeners.addEventListener(this._menuButton, 'menuClose', this._onMenuClose.bind(this), false); } + /** */ cleanup() { this._eventListeners.removeAllEventListeners(); if (this._node.parentNode !== null) { @@ -579,41 +708,63 @@ class ProfileEntry { } } + /** + * @param {string} value + */ setName(value) { if (this._nameInput.value === value) { return; } this._nameInput.value = value; } + /** + * @param {boolean} value + */ setIsDefault(value) { this._isDefaultRadio.checked = value; } + /** */ updateState() { this._nameInput.value = this._profile.name; this._countText.textContent = `${this._profile.conditionGroups.length}`; this._isDefaultRadio.checked = (this._index === this._profileController.profileCurrentIndex); } + /** + * @param {number} count + */ setConditionGroupsCount(count) { this._countText.textContent = `${count}`; } // Private + /** + * @param {Event} e + */ _onIsDefaultRadioChange(e) { - if (!e.currentTarget.checked) { return; } + const element = /** @type {HTMLInputElement} */ (e.currentTarget); + if (!element.checked) { return; } this._profileController.setDefaultProfile(this._index); } + /** + * @param {Event} e + */ _onNameInputInput(e) { - const name = e.currentTarget.value; + const element = /** @type {HTMLInputElement} */ (e.currentTarget); + const name = element.value; this._profileController.setProfileName(this._index, name); } + /** */ _onConditionsCountLinkClick() { this._profileController.openProfileConditionsModal(this._index); } + /** + * @param {import('popup-menu').MenuOpenEvent} e + */ _onMenuOpen(e) { const bodyNode = e.detail.menu.bodyNode; const count = this._profileController.profileCount; @@ -623,6 +774,9 @@ class ProfileEntry { this._setMenuActionEnabled(bodyNode, 'delete', count > 1); } + /** + * @param {import('popup-menu').MenuCloseEvent} e + */ _onMenuClose(e) { switch (e.detail.action) { case 'moveUp': @@ -646,8 +800,13 @@ class ProfileEntry { } } + /** + * @param {Element} menu + * @param {string} action + * @param {boolean} enabled + */ _setMenuActionEnabled(menu, action, enabled) { - const element = menu.querySelector(`[data-menu-action="${action}"]`); + const element = /** @type {HTMLButtonElement} */ (menu.querySelector(`[data-menu-action="${action}"]`)); if (element === null) { return; } element.disabled = !enabled; } |