/* * Copyright (C) 2020 Yomichan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ /* global * Modal * ProfileConditionsUI * api */ class ProfileController { constructor(settingsController) { this._settingsController = settingsController; this._profileConditionsUI = new ProfileConditionsUI(settingsController); this._profileActiveSelect = null; this._profileTargetSelect = null; this._profileCopySourceSelect = null; this._profileNameInput = null; this._removeProfileNameElement = null; this._profileRemoveButton = null; this._profileCopyButton = null; this._profileMoveUpButton = null; this._profileMoveDownButton = null; this._profileRemoveModal = null; this._profileCopyModal = null; this._optionsFull = null; } async prepare() { const {platform: {os}} = await api.getEnvironmentInfo(); this._profileConditionsUI.os = os; this._profileActiveSelect = document.querySelector('#profile-active'); this._profileTargetSelect = document.querySelector('#profile-target'); this._profileCopySourceSelect = document.querySelector('#profile-copy-source'); this._profileNameInput = document.querySelector('#profile-name'); this._removeProfileNameElement = document.querySelector('#profile-remove-modal-profile-name'); this._profileRemoveButton = document.querySelector('#profile-remove'); this._profileCopyButton = document.querySelector('#profile-copy'); this._profileMoveUpButton = document.querySelector('#profile-move-up'); this._profileMoveDownButton = document.querySelector('#profile-move-down'); this._profileRemoveModal = new Modal(document.querySelector('#profile-remove-modal')); this._profileCopyModal = new Modal(document.querySelector('#profile-copy-modal')); this._profileActiveSelect.addEventListener('change', this._onProfileActiveChange.bind(this), false); this._profileTargetSelect.addEventListener('change', this._onProfileTargetChange.bind(this), false); this._profileNameInput.addEventListener('change', this._onNameChanged.bind(this), false); document.querySelector('#profile-add').addEventListener('click', this._onAdd.bind(this), false); this._profileRemoveButton.addEventListener('click', this._onRemove.bind(this), false); document.querySelector('#profile-remove-confirm').addEventListener('click', this._onRemoveConfirm.bind(this), false); this._profileCopyButton.addEventListener('click', this._onCopy.bind(this), false); document.querySelector('#profile-copy-confirm').addEventListener('click', this._onCopyConfirm.bind(this), false); this._profileMoveUpButton.addEventListener('click', this._onMove.bind(this, -1), false); this._profileMoveDownButton.addEventListener('click', this._onMove.bind(this, 1), false); this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); this._onOptionsChanged(); } // Private async _onOptionsChanged() { this._optionsFull = await this._settingsController.getOptionsFull(); this._profileConditionsUI.cleanup(); const {profiles, profileCurrent} = this._optionsFull; const profileIndex = this._settingsController.profileIndex; this._updateSelectOptions(this._profileActiveSelect); this._updateSelectOptions(this._profileTargetSelect); this._updateSelectOptions(this._profileCopySourceSelect, [profileIndex]); this._profileActiveSelect.value = `${profileCurrent}`; this._profileTargetSelect.value = `${profileIndex}`; this._profileRemoveButton.disabled = (profiles.length <= 1); this._profileCopyButton.disabled = (profiles.length <= 1); this._profileMoveUpButton.disabled = (profileIndex <= 0); this._profileMoveDownButton.disabled = (profileIndex >= profiles.length - 1); if (profileIndex >= 0 && profileIndex < profiles.length) { const currentProfile = profiles[profileIndex]; this._profileNameInput.value = currentProfile.name; const {conditionGroups} = currentProfile; this._profileConditionsUI.prepare(conditionGroups); } } _onProfileActiveChange(e) { const max = this._optionsFull.profiles.length; const value = this._tryGetIntegerValue(e.currentTarget.value, 0, max); if (value === null) { return; } this._settingsController.setGlobalSetting('profileCurrent', value); } _onProfileTargetChange(e) { const max = this._optionsFull.profiles.length; const value = this._tryGetIntegerValue(e.currentTarget.value, 0, max); if (value === null) { return; } this._settingsController.profileIndex = value; } _onNameChanged(e) { const value = e.currentTarget.value; const profileIndex = this._settingsController.profileIndex; this._settingsController.setGlobalSetting(`profiles[${profileIndex}].name`, value); this._updateSelectName(profileIndex, value); } _onAdd() { this._addProfile(); } _onRemove(e) { if (e.shiftKey) { return this._onRemoveConfirm(); } if (this._optionsFull.profiles.length <= 1) { return; } const profileIndex = this._settingsController.profileIndex; const profile = this._optionsFull.profiles[profileIndex]; this._removeProfileNameElement.textContent = profile.name; this._profileRemoveModal.setVisible(true); } _onRemoveConfirm() { this._profileRemoveModal.setVisible(false); if (this._optionsFull.profiles.length <= 1) { return; } const profileIndex = this._settingsController.profileIndex; this._removeProfile(profileIndex); } _onCopy() { if (this._optionsFull.profiles.length <= 1) { return; } const {profiles, profileCurrent} = this._optionsFull; const profileIndex = this._settingsController.profileIndex; let copyFromIndex = profileCurrent; if (copyFromIndex === profileIndex) { if (profileIndex !== 0) { copyFromIndex = 0; } else if (profiles.length > 1) { copyFromIndex = 1; } } this._profileCopySourceSelect.value = `${copyFromIndex}`; this._profileCopyModal.setVisible(true); } _onCopyConfirm() { this._profileCopyModal.setVisible(false); const profileIndex = this._settingsController.profileIndex; const max = this._optionsFull.profiles.length; const index = this._tryGetIntegerValue('#profile-copy-source', 0, max); if (index === null || index === profileIndex) { return; } this._copyProfile(profileIndex, index); } _onMove(offset) { const profileIndex = this._settingsController.profileIndex; const max = this._optionsFull.profiles.length; const profileIndexNew = Math.max(0, Math.min(max - 1, profileIndex + offset)); if (profileIndex === profileIndexNew) { return; } this._swapProfiles(profileIndex, profileIndexNew); } _updateSelectOptions(select, disabled) { const {profiles} = this._optionsFull; const fragment = document.createDocumentFragment(); for (let i = 0; i < profiles.length; ++i) { const profile = profiles[i]; const option = document.createElement('option'); option.value = `${i}`; option.textContent = profile.name; option.disabled = (Array.isArray(disabled) && disabled.includes(i)); fragment.appendChild(option); } select.textContent = ''; select.appendChild(fragment); } _updateSelectName(index, name) { const selects = [ this._profileActiveSelect, this._profileTargetSelect, this._profileCopySourceSelect ]; const optionValue = `${index}`; for (const select of selects) { for (const option of select.querySelectorAll('option')) { if (option.value === optionValue) { option.textContent = name; } } } } _tryGetIntegerValue(value, min, max) { value = parseInt(value, 10); return ( typeof value === 'number' && Number.isFinite(value) && Math.floor(value) === value && value >= min && value < max ) ? value : null; } _createCopyName(name, profiles, maxUniqueAttempts) { let space, index, prefix, suffix; const match = /^([\w\W]*\(Copy)((\s+)(\d+))?(\)\s*)$/.exec(name); if (match === null) { prefix = `${name} (Copy`; space = ''; index = ''; suffix = ')'; } else { prefix = match[1]; suffix = match[5]; if (typeof match[2] === 'string') { space = match[3]; index = parseInt(match[4], 10) + 1; } else { space = ' '; index = 2; } } let i = 0; while (true) { const newName = `${prefix}${space}${index}${suffix}`; if (i++ >= maxUniqueAttempts || profiles.findIndex((profile) => profile.name === newName) < 0) { return newName; } if (typeof index !== 'number') { index = 2; space = ' '; } else { ++index; } } } _getSwappedValue(currentValue, value1, value2) { if (currentValue === value1) { return value2; } if (currentValue === value2) { return value1; } return null; } async _addProfile() { const profileIndex = this._settingsController.profileIndex; const profiles = this._optionsFull.profiles; const profile = profiles[profileIndex]; const newProfile = clone(profile); newProfile.name = this._createCopyName(profile.name, profiles, 100); const index = profiles.length; await this._settingsController.modifyGlobalSettings([{ action: 'splice', path: 'profiles', start: index, deleteCount: 0, items: [newProfile] }]); this._settingsController.profileIndex = index; } async _removeProfile(index) { const {profiles, profileCurrent} = this._optionsFull; let newProfileCurrent = profileCurrent; const modifications = [{ action: 'splice', path: 'profiles', start: index, deleteCount: 1, items: [] }]; if (profileCurrent >= index) { newProfileCurrent = Math.min(newProfileCurrent - 1, profiles.length - 1); modifications.push({ action: 'set', path: 'profileCurrent', value: newProfileCurrent }); } const profileIndex = this._settingsController.profileIndex; await this._settingsController.modifyGlobalSettings(modifications); if (profileIndex === index) { this._settingsController.profileIndex = newProfileCurrent; } else { await this._onOptionsChanged(); } } async _swapProfiles(index1, index2) { const {profileCurrent} = this._optionsFull; const modifications = [{ action: 'swap', path1: `profiles[${index1}]`, path2: `profiles[${index2}]` }]; const newProfileCurrent = this._getSwappedValue(profileCurrent, index1, index2); if (newProfileCurrent !== profileCurrent) { modifications.push({ action: 'set', path: 'profileCurrent', value: newProfileCurrent }); } const profileIndex = this._settingsController.profileIndex; const newProfileIndex = this._getSwappedValue(profileIndex, index1, index2); await this._settingsController.modifyGlobalSettings(modifications); if (profileIndex !== newProfileIndex) { this._settingsController.profileIndex = newProfileIndex; } } async _copyProfile(index, copyFromIndex) { const profiles = this._optionsFull.profiles; const copyFromProfile = profiles[copyFromIndex]; const options = clone(copyFromProfile.options); await this._settingsController.modifyGlobalSettings([{ action: 'set', path: `profiles[${index}].options`, value: options }]); await this._settingsController.refresh(); } }