aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/bg/js/api.js6
-rw-r--r--ext/bg/js/settings-profiles.js201
-rw-r--r--ext/bg/js/settings.js11
-rw-r--r--ext/bg/settings.html53
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">&times;</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>