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