diff options
-rw-r--r-- | ext/bg/js/api.js | 6 | ||||
-rw-r--r-- | ext/bg/js/settings-profiles.js | 201 | ||||
-rw-r--r-- | ext/bg/js/settings.js | 11 | ||||
-rw-r--r-- | ext/bg/settings.html | 53 |
4 files changed, 260 insertions, 11 deletions
diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 81772d08..f32b984f 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -21,9 +21,13 @@ function apiOptionsGet(optionsContext) { return utilBackend().getOptions(optionsContext); } +function apiOptionsGetFull() { + return utilBackend().getFullOptions(); +} + async function apiOptionsSave(source) { const backend = utilBackend(); - const options = await backend.getFullOptions(); + const options = await apiOptionsGetFull(); await optionsSave(options); backend.onOptionsUpdated(source); } diff --git a/ext/bg/js/settings-profiles.js b/ext/bg/js/settings-profiles.js new file mode 100644 index 00000000..dca452d2 --- /dev/null +++ b/ext/bg/js/settings-profiles.js @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2019 Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +let currentProfileIndex = 0; + +function getOptionsContext() { + return { + index: currentProfileIndex + }; +} + + +async function profileOptionsSetup() { + const optionsFull = await apiOptionsGetFull(); + currentProfileIndex = optionsFull.profileCurrent; + + profileOptionsSetupEventListeners(); + await profileOptionsUpdateTarget(optionsFull); +} + +function profileOptionsSetupEventListeners() { + $('#profile-target').change(utilAsync(onTargetProfileChanged)); + $('#profile-name').change(onProfileNameChanged); + $('#profile-add').click(utilAsync(onProfileAdd)); + $('#profile-remove').click(utilAsync(onProfileRemove)); + $('#profile-remove-confirm').click(utilAsync(onProfileRemoveConfirm)); + $('.profile-form').find('input, select, textarea').not('.profile-form-manual').change(utilAsync(onProfileOptionsChanged)); +} + +function tryGetIntegerValue(selector, min, max) { + const value = parseInt($(selector).val(), 10); + return ( + typeof value === 'number' && + Number.isFinite(value) && + Math.floor(value) === value && + value >= min && + value < max + ) ? value : null; +} + +async function profileFormRead(optionsFull) { + const profile = optionsFull.profiles[currentProfileIndex]; + + // Current profile + const index = tryGetIntegerValue('#profile-active', 0, optionsFull.profiles.length); + if (index !== null) { + optionsFull.profileCurrent = index; + } + + // Profile name + profile.name = $('#profile-name').val(); +} + +async function profileFormWrite(optionsFull) { + const profile = optionsFull.profiles[currentProfileIndex]; + + profileOptionsPopulateSelect($('#profile-active'), optionsFull.profiles, optionsFull.profileCurrent); + profileOptionsPopulateSelect($('#profile-target'), optionsFull.profiles, currentProfileIndex); + $('#profile-remove').prop('disabled', optionsFull.profiles.length <= 1); + + $('#profile-name').val(profile.name); +} + +function profileOptionsPopulateSelect(select, profiles, currentValue) { + select.empty(); + + for (let i = 0; i < profiles.length; ++i) { + const profile = profiles[i]; + select.append($(`<option value="${i}">${profile.name}</option>`)); + } + + select.val(`${currentValue}`); +} + +async function profileOptionsUpdateTarget(optionsFull) { + profileFormWrite(optionsFull); + + const optionsContext = getOptionsContext(); + const options = await apiOptionsGet(optionsContext); + await formWrite(options); +} + +function profileOptionsCreateCopyName(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; + } + } +} + +async function onProfileOptionsChanged(e) { + if (!e.originalEvent && !e.isTrigger) { + return; + } + + const optionsFull = await apiOptionsGetFull(); + await profileFormRead(optionsFull); + await apiOptionsSave(); +} + +async function onTargetProfileChanged() { + const optionsFull = await apiOptionsGetFull(); + const index = tryGetIntegerValue('#profile-target', 0, optionsFull.profiles.length); + if (index === null || currentProfileIndex === index) { + return; + } + + currentProfileIndex = index; + + await profileOptionsUpdateTarget(optionsFull); +} + +async function onProfileAdd() { + const optionsFull = await apiOptionsGetFull(); + const profile = utilIsolate(optionsFull.profiles[currentProfileIndex]); + profile.name = profileOptionsCreateCopyName(profile.name, optionsFull.profiles, 100); + optionsFull.profiles.push(profile); + currentProfileIndex = optionsFull.profiles.length - 1; + await profileOptionsUpdateTarget(optionsFull); + await apiOptionsSave(); +} + +async function onProfileRemove() { + const optionsFull = await apiOptionsGetFull(); + if (optionsFull.profiles.length <= 1) { + return; + } + + const profile = optionsFull.profiles[currentProfileIndex]; + + $('#profile-remove-modal-profile-name').text(profile.name); + $('#profile-remove-modal').modal('show'); +} + +async function onProfileRemoveConfirm() { + $('#profile-remove-modal').modal('hide'); + + const optionsFull = await apiOptionsGetFull(); + if (optionsFull.profiles.length <= 1) { + return; + } + + optionsFull.profiles.splice(currentProfileIndex, 1); + + if (currentProfileIndex >= optionsFull.profiles.length) { + --currentProfileIndex; + } + + if (optionsFull.profileCurrent >= optionsFull.profiles.length) { + optionsFull.profileCurrent = optionsFull.profiles.length - 1; + } + + await profileOptionsUpdateTarget(optionsFull); + await apiOptionsSave(); +} + +function onProfileNameChanged() { + $('#profile-active, #profile-target').find(`[value="${currentProfileIndex}"]`).text(this.value); +} diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 88929c49..b6434843 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -16,12 +16,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -function getOptionsContext() { - return { - depth: 0 - }; -} - async function formRead(options) { options.general.enable = $('#enable').prop('checked'); options.general.showGuide = $('#show-usage-guide').prop('checked'); @@ -239,11 +233,8 @@ async function onFormOptionsChanged(e) { } async function onReady() { - const optionsContext = getOptionsContext(); - const options = await apiOptionsGet(optionsContext); - formSetupEventListeners(); - await formWrite(options); + await profileOptionsSetup(); storageInfoInitialize(); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 7df47980..1a1bc2ed 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -67,6 +67,58 @@ </head> <body> <div class="container-fluid"> + <div class="profile-form"> + <h3>Profiles</h3> + + <p class="help-block"> + Profiles allow you to create multiple configurations and quickly switch between them. + </p> + + <div class="form-group"> + <label for="profile-active">Active profile</label> + <select class="form-control" id="profile-active"></select> + </div> + + <div class="form-group"> + <label for="profile-target">Modifying profile</label> + <div class="input-group"> + <div class="input-group-btn"> + <button class="btn btn-default" id="profile-add" title="Add"><span class="glyphicon glyphicon-plus"></span></button> + <button class="btn btn-default" id="profile-move-up" title="Move up"><span class="glyphicon glyphicon-arrow-up"></span></button> + <button class="btn btn-default" id="profile-move-down" title="Move down"><span class="glyphicon glyphicon-arrow-down"></span></button> + <button class="btn btn-default" id="profile-copy" title="Copy"><span class="glyphicon glyphicon-copy"></span></button> + </div> + <select class="form-control profile-form-manual" id="profile-target"></select> + <div class="input-group-btn"> + <button class="btn btn-danger" id="profile-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button> + </div> + </div> + </div> + + <div class="form-group"> + <label for="profile-name">Profile name</label> + <input type="text" id="profile-name" class="form-control"> + </div> + + <div class="modal fade" tabindex="-1" role="dialog" id="profile-remove-modal"> + <div class="modal-dialog modal-dialog-centered"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title">Confirm profile removal</h4> + </div> + <div class="modal-body"> + Are you sure you want to delete the profile <em id="profile-remove-modal-profile-name"></em>? + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> + <button type="button" class="btn btn-danger" id="profile-remove-confirm">Remove Profile</button> + </div> + </div> + </div> + </div> + </div> + <div> <h3>General Options</h3> @@ -498,6 +550,7 @@ <script src="/bg/js/templates.js"></script> <script src="/bg/js/util.js"></script> + <script src="/bg/js/settings-profiles.js"></script> <script src="/bg/js/settings.js"></script> </body> </html> |