From fadf2d1a70ccc8a5b24034fe076fc5b15d0bab94 Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Tue, 20 Aug 2019 15:26:22 +0200 Subject: [PATCH 1/9] First PoC for UI for customizing profile --- index.html | 1 + js/control/Profile.js | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/index.html b/index.html index 2c0dcfc..a7534d8 100644 --- a/index.html +++ b/index.html @@ -646,6 +646,7 @@ >Custom profile
+
-
-
- - - - Help +
+
+
+ + +
+
+
+ +
+
+ + + + Help +
diff --git a/js/control/Profile.js b/js/control/Profile.js index 9c51e36..68a433d 100644 --- a/js/control/Profile.js +++ b/js/control/Profile.js @@ -7,6 +7,11 @@ BR.Profile = L.Evented.extend({ lineNumbers: true }); + var that = this; + L.DomUtil.get('profile_advanced').addEventListener('click', function() { + that._toggleAdvanced(); + }); + L.DomUtil.get('upload').onclick = L.bind(this._upload, this); L.DomUtil.get('clear').onclick = L.bind(this.clear, this); @@ -88,24 +93,27 @@ BR.Profile = L.Evented.extend({ }); if (global) { global = global[0].split('\n').slice(1); - var assignRegex = /assign\s*(\w*)\s*=?\s*([\w\.]*)\s*(#.*)?$/; + // Comment is mandatory + var assignRegex = /assign\s*(\w*)\s*=?\s*([\w\.]*)\s*#\s*(.*)\s*$/; var params = {}; global.forEach(function(item) { var match = item.match(assignRegex); + var value; if (match) { - if (match[2] == 'true') { - match[2] = 1; - } else if (match[2] == 'false') { - match[2] = 0; + if (match[2] == 'true' || match[2] == 'false') { + paramType = 'checkbox'; + value = match[2] == 'true'; } else { - match[2] = Number.parseFloat(match[2]); - } - if (Number.isNaN(match[2])) { - return; + value = Number.parseFloat(match[2]); + if (Number.isNaN(value)) { + return; + } + paramType = 'number'; } params[match[1]] = { - comment: match[3] ? match[3].replace(/^#\s+|\s+$/g, '') : null, - value: match[2] + comment: match[3], + type: paramType, + value: value }; } }); @@ -113,23 +121,32 @@ BR.Profile = L.Evented.extend({ var paramsSection = L.DomUtil.get('profile_params'); paramsSection.innerHTML = ''; Object.keys(params).forEach(function(param) { - var p = document.createElement('p'); + var div = document.createElement('div'); var label = document.createElement('label'); - label.innerHTML = param; var input = document.createElement('input'); - input.type = 'text'; - input.value = params[param].value; - p.appendChild(label); - label.appendChild(input); - paramsSection.appendChild(label); - if (params[param].comment) { - var p = document.createElement('p'); - p.innerHTML = params[param].comment; - paramsSection.append(p); + input.type = params[param].type; + if (input.type == 'checkbox') { + input.checked = params[param].value; + label.appendChild(input); + label.append(' ' + param); + } else { + input.value = params[param].value; + label.append(param + ' '); + label.appendChild(input); } + div.appendChild(label); + var small = document.createElement('small'); + small.innerHTML = ' (' + params[param].comment + ')'; + div.appendChild(small); + paramsSection.appendChild(div); }); this.editor.setValue(profileText); this.editor.markClean(); + }, + + _toggleAdvanced: function() { + L.DomUtil.get('profile_params_container').style.display = 'none'; + L.DomUtil.get('profile_editor').style.display = 'flex'; } }); From aa6d13025cdd585e31a418694d8a4f72a426706b Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Mon, 2 Sep 2019 12:04:11 +0200 Subject: [PATCH 3/9] Build on Locus' syntax --- js/control/Profile.js | 62 ++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/js/control/Profile.js b/js/control/Profile.js index 68a433d..338fb56 100644 --- a/js/control/Profile.js +++ b/js/control/Profile.js @@ -92,26 +92,48 @@ BR.Profile = L.Evented.extend({ return e.startsWith('global'); }); if (global) { + // Remove ---context:global line global = global[0].split('\n').slice(1); + // Comment is mandatory - var assignRegex = /assign\s*(\w*)\s*=?\s*([\w\.]*)\s*#\s*(.*)\s*$/; + var assignRegex = /assign\s*(\w*)\s*=?\s*([\w\.]*)\s*#\s*%(.*)%\s*(\|\s*(.*)\s*\|\s*(.*)\s*)?$/; var params = {}; global.forEach(function(item) { var match = item.match(assignRegex); var value; if (match) { - if (match[2] == 'true' || match[2] == 'false') { - paramType = 'checkbox'; - value = match[2] == 'true'; - } else { - value = Number.parseFloat(match[2]); + var name = match[1]; + var value = match[2]; + var description = match[5]; + + // Find out type + var paramType = match[6]; + if (paramType.match(/\[.*\]/)) { + console.log('TODO: ' + paramType); // TODO + return; + } + + // Type is missing, let's try to induce it from value + if (!paramType) { + if (value == 'true' || value == 'false') { + paramType = 'boolean'; + } else { + paramType = 'number'; + } + } + + // Sanitize value according to type + if (paramType == 'boolean') { + value = value == 'true'; + } else if (paramType == 'number') { + value = Number.parseFloat(value); if (Number.isNaN(value)) { return; } - paramType = 'number'; } - params[match[1]] = { - comment: match[3], + + params[name] = { + description: description, type: paramType, value: value }; @@ -124,19 +146,23 @@ BR.Profile = L.Evented.extend({ var div = document.createElement('div'); var label = document.createElement('label'); var input = document.createElement('input'); - input.type = params[param].type; - if (input.type == 'checkbox') { - input.checked = params[param].value; - label.appendChild(input); - label.append(' ' + param); - } else { + + var paramType = params[param].type; + if (paramType == 'number') { + input.type = 'number'; input.value = params[param].value; - label.append(param + ' '); - label.appendChild(input); + } else if (paramType == 'boolean') { + input.type = 'checkbox'; + input.checked = params[param].value; + } else { + // Unknown parameter type, skip it + return; } + label.appendChild(input); + label.append(' ' + param); div.appendChild(label); var small = document.createElement('small'); - small.innerHTML = ' (' + params[param].comment + ')'; + small.innerHTML = ' (' + params[param].description.replace(/^\s+|\s+$/g, '') + ')'; div.appendChild(small); paramsSection.appendChild(div); }); From 39b7dd4a0ada7ec25898268e4ff61d079947da91 Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Mon, 2 Sep 2019 13:37:08 +0200 Subject: [PATCH 4/9] Handle i18n of profile parameters --- js/control/Profile.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/js/control/Profile.js b/js/control/Profile.js index 338fb56..1f044a4 100644 --- a/js/control/Profile.js +++ b/js/control/Profile.js @@ -159,10 +159,19 @@ BR.Profile = L.Evented.extend({ return; } label.appendChild(input); - label.append(' ' + param); + var name = i18next.exists('profileParameters.' + param + '.name') + ? i18next.t('profileParameters.' + param + '.name') + : param; + label.append(' ' + name); + div.appendChild(label); + var small = document.createElement('small'); - small.innerHTML = ' (' + params[param].description.replace(/^\s+|\s+$/g, '') + ')'; + var description = i18next.exists('profileParameters.' + param + '.description') + ? i18next.t('profileParameters.' + param + '.description') + : params[param].description.replace(/^\s+|\s+$/g, ''); + small.innerHTML = ' (' + description + ')'; + div.appendChild(small); paramsSection.appendChild(div); }); From ff86c85a8acd96798c7478104a27ffde88f4938f Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Thu, 5 Sep 2019 16:55:27 +0200 Subject: [PATCH 5/9] Full first draft of a working UI --- css/style.css | 25 +++++++- index.html | 30 +++++----- js/control/Profile.js | 131 +++++++++++++++++++++++++++++++++--------- locales/en.json | 7 ++- 4 files changed, 147 insertions(+), 46 deletions(-) diff --git a/css/style.css b/css/style.css index 7c3b616..1570b6f 100644 --- a/css/style.css +++ b/css/style.css @@ -426,7 +426,6 @@ table.dataTable.display tbody tr.even:hover { * leaflet-sidebar-v2 */ -.leaflet-sidebar-pane#tab_profile, .leaflet-sidebar-pane#tab_data, .leaflet-sidebar-pane#tab_itinerary { /* Full height for content with inner scrolling, @@ -434,6 +433,30 @@ table.dataTable.display tbody tr.even:hover { height: 100%; } +.leaflet-sidebar-pane#tab_profile { + min-height: 100%; +} + +.leaflet-sidebar-pane#tab_profile .form-group { + margin-bottom: 5px; +} + +.leaflet-sidebar-pane#tab_profile label { + font-weight: 700; + margin-bottom: 0; +} + +.leaflet-sidebar-pane#tab_profile input[type='checkbox'] { + margin-right: 5px; + vertical-align: middle; +} + +.leaflet-sidebar-pane#tab_profile .help-block { + display: block; + color: #737373; + margin-bottom: 0.5rem; +} + .leaflet-sidebar-content { /* for optional-layers-tree */ overflow-x: auto; diff --git a/index.html b/index.html index e5510ad..12111c2 100644 --- a/index.html +++ b/index.html @@ -554,8 +554,8 @@ @@ -643,30 +643,22 @@

Custom profile + >Customize profile

- - + +
@@ -700,6 +692,10 @@ > Help + +
diff --git a/js/control/Profile.js b/js/control/Profile.js index 1f044a4..9e1865d 100644 --- a/js/control/Profile.js +++ b/js/control/Profile.js @@ -11,7 +11,11 @@ BR.Profile = L.Evented.extend({ L.DomUtil.get('profile_advanced').addEventListener('click', function() { that._toggleAdvanced(); }); + L.DomUtil.get('profile_basic').addEventListener('click', function() { + that._toggleAdvanced(); + }); + L.DomUtil.get('save').onclick = L.bind(this._save, this); L.DomUtil.get('upload').onclick = L.bind(this._upload, this); L.DomUtil.get('clear').onclick = L.bind(this.clear, this); @@ -87,7 +91,41 @@ BR.Profile = L.Evented.extend({ }); }, + _save: function(evt) { + var profileText = this.cache[this.profileName]; + document.querySelectorAll('#profile_params input, #profile_params select').forEach(function(input) { + var name = input.name; + var value; + if (input.type == 'checkbox') { + value = input.checked; + } else { + value = input.value; + } + + var re = new RegExp( + '(assign\\s*' + + name + + '\\s*=?\\s*)([\\w.]*)(\\s*#\\s*%(.*)%\\s*(\\|\\s*(.*)\\s*\\|\\s*(.*)\\s*)?[\\r\\n])' + ); + profileText = profileText.replace(re, function(match, p1, p2, p3) { + return p1 + value + p3; + }); + }); + this.fire('update', { + profileText: profileText, + callback: function() {} + }); + }, + _setValue: function(profileText) { + if (L.DomUtil.get('profile_editor').style.display == 'flex') { + // Set value of the full editor and exit + this.editor.setValue(profileText); + this.editor.markClean(); + return; + } + + // Otherwise, create user friendly form var global = profileText.split('---context:').filter(function(e) { return e.startsWith('global'); }); @@ -108,9 +146,18 @@ BR.Profile = L.Evented.extend({ // Find out type var paramType = match[6]; + var paramValues = {}; if (paramType.match(/\[.*\]/)) { - console.log('TODO: ' + paramType); // TODO - return; + paramType + .slice(1, -1) + .split(',') + .forEach(function(option) { + var splitOption = option.split('='); + var value = (splitOption[0] || '').replace(/^\s+|\s+$/g, ''); + var description = (splitOption[1] || '').replace(/^\s+|\s+$/g, ''); + paramValues[value] = description; + }); + paramType = 'select'; } // Type is missing, let's try to induce it from value @@ -135,7 +182,8 @@ BR.Profile = L.Evented.extend({ params[name] = { description: description, type: paramType, - value: value + value: value, + possible_values: paramValues }; } }); @@ -145,43 +193,74 @@ BR.Profile = L.Evented.extend({ Object.keys(params).forEach(function(param) { var div = document.createElement('div'); var label = document.createElement('label'); - var input = document.createElement('input'); var paramType = params[param].type; - if (paramType == 'number') { - input.type = 'number'; - input.value = params[param].value; - } else if (paramType == 'boolean') { - input.type = 'checkbox'; - input.checked = params[param].value; - } else { - // Unknown parameter type, skip it - return; - } - label.appendChild(input); - var name = i18next.exists('profileParameters.' + param + '.name') + var paramName = i18next.exists('profileParameters.' + param + '.name') ? i18next.t('profileParameters.' + param + '.name') : param; - label.append(' ' + name); + if (paramType == 'select') { + var select = document.createElement('select'); + select.name = paramName; + select.className = 'form-control'; + label.htmlFor = select.id = 'customize-profile-' + paramName; - div.appendChild(label); + var paramValues = params[param].possible_values; + Object.keys(paramValues).forEach(function(paramValue) { + var option = document.createElement('option'); + option.value = paramValue; + option.append(paramValues[paramValue]); + select.appendChild(option); + }); - var small = document.createElement('small'); + label.append(paramName); + div.appendChild(label); + div.appendChild(select); + } else { + var input = document.createElement('input'); + input.name = paramName; + label.htmlFor = input.id = 'customize-profile-' + paramName; + if (paramType == 'number') { + input.type = 'number'; + input.value = params[param].value; + input.className = 'form-control'; + + label.append(paramName); + div.appendChild(label); + div.appendChild(input); + div.className = 'form-group'; + } else if (paramType == 'boolean') { + input.type = 'checkbox'; + input.checked = params[param].value; + + div.appendChild(input); + label.append(paramName); + div.appendChild(label); + } else { + // Unknown parameter type, skip it + return; + } + } + + var helpBlock = document.createElement('p'); var description = i18next.exists('profileParameters.' + param + '.description') ? i18next.t('profileParameters.' + param + '.description') : params[param].description.replace(/^\s+|\s+$/g, ''); - small.innerHTML = ' (' + description + ')'; + helpBlock.innerHTML = description; + helpBlock.className = 'help-block'; - div.appendChild(small); + div.appendChild(helpBlock); paramsSection.appendChild(div); }); - - this.editor.setValue(profileText); - this.editor.markClean(); }, _toggleAdvanced: function() { - L.DomUtil.get('profile_params_container').style.display = 'none'; - L.DomUtil.get('profile_editor').style.display = 'flex'; + if (L.DomUtil.get('profile_editor').style.display == 'flex') { + L.DomUtil.get('profile_params_container').style.display = 'initial'; + L.DomUtil.get('profile_editor').style.display = 'none'; + } else { + L.DomUtil.get('profile_params_container').style.display = 'none'; + L.DomUtil.get('profile_editor').style.display = 'flex'; + } + this._setValue(this.cache[this.profileName]); } }); diff --git a/locales/en.json b/locales/en.json index e4a6a10..e64ea00 100644 --- a/locales/en.json +++ b/locales/en.json @@ -158,8 +158,8 @@ } }, "sidebar": { - "custom-profile": { - "title": "Custom profile" + "customize-profile": { + "title": "Customize profile" }, "data": { "title": "Data" @@ -197,6 +197,9 @@ "clear": "Clear", "help": "Help", "placeholder": "Write your custom profile here.", + "save": "Save", + "switch_advanced": "Switch to advanced editor", + "switch_basic": "Switch to basic editor", "upload": "Upload" } }, From 83390fc05c5c10ffeb2498e283944b0b97049ece Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Thu, 5 Sep 2019 17:07:22 +0200 Subject: [PATCH 6/9] Add a message when no configuration options are available --- js/control/Profile.js | 5 +++++ locales/en.json | 1 + 2 files changed, 6 insertions(+) diff --git a/js/control/Profile.js b/js/control/Profile.js index 9e1865d..a807ba6 100644 --- a/js/control/Profile.js +++ b/js/control/Profile.js @@ -190,6 +190,11 @@ BR.Profile = L.Evented.extend({ } var paramsSection = L.DomUtil.get('profile_params'); paramsSection.innerHTML = ''; + + if (!Object.keys(params).length) { + paramsSection.append(i18next.t('sidebar.profile.no_easy_configuration_warning')); + } + Object.keys(params).forEach(function(param) { var div = document.createElement('div'); var label = document.createElement('label'); diff --git a/locales/en.json b/locales/en.json index e64ea00..ad7ca4d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -196,6 +196,7 @@ "profile": { "clear": "Clear", "help": "Help", + "no_easy_configuration_warning": "No easy configuration is available for this profile.", "placeholder": "Write your custom profile here.", "save": "Save", "switch_advanced": "Switch to advanced editor", From 43ac9db50c98091d4511b5b82dc8ba02542b3b87 Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Fri, 27 Sep 2019 11:18:27 +0200 Subject: [PATCH 7/9] Keep advanced and basic editor in sync --- js/control/Profile.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/js/control/Profile.js b/js/control/Profile.js index a807ba6..dd32c3d 100644 --- a/js/control/Profile.js +++ b/js/control/Profile.js @@ -91,7 +91,7 @@ BR.Profile = L.Evented.extend({ }); }, - _save: function(evt) { + _buildCustomProfile: function() { var profileText = this.cache[this.profileName]; document.querySelectorAll('#profile_params input, #profile_params select').forEach(function(input) { var name = input.name; @@ -111,6 +111,11 @@ BR.Profile = L.Evented.extend({ return p1 + value + p3; }); }); + return profileText; + }, + + _save: function(evt) { + var profileText = this._buildCustomProfile(); this.fire('update', { profileText: profileText, callback: function() {} @@ -262,10 +267,11 @@ BR.Profile = L.Evented.extend({ if (L.DomUtil.get('profile_editor').style.display == 'flex') { L.DomUtil.get('profile_params_container').style.display = 'initial'; L.DomUtil.get('profile_editor').style.display = 'none'; + this._setValue(this.editor.getValue()); } else { L.DomUtil.get('profile_params_container').style.display = 'none'; L.DomUtil.get('profile_editor').style.display = 'flex'; + this._setValue(this._buildCustomProfile()); } - this._setValue(this.cache[this.profileName]); } }); From a77413e1ebe6baa4fb0638239bd6ec33538f2809 Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Tue, 8 Oct 2019 14:20:51 +0200 Subject: [PATCH 8/9] Fix an error upon editing profile with a route set --- js/control/Profile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/control/Profile.js b/js/control/Profile.js index dd32c3d..d234fcb 100644 --- a/js/control/Profile.js +++ b/js/control/Profile.js @@ -40,8 +40,8 @@ BR.Profile = L.Evented.extend({ empty = !this.editor.getValue(), clean = this.editor.isClean(); - this.profileName = profileName; if (profileName && BR.conf.profilesUrl && (empty || clean)) { + this.profileName = profileName; if (!(profileName in this.cache)) { profileUrl = BR.conf.profilesUrl + profileName + '.brf'; BR.Util.get( From f3317733630bedc5de2940c309b68435fe46b457 Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Tue, 8 Oct 2019 14:33:41 +0200 Subject: [PATCH 9/9] Cache custom profiles --- js/control/Profile.js | 13 +++++++++++-- js/index.js | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/js/control/Profile.js b/js/control/Profile.js index d234fcb..f1f7af4 100644 --- a/js/control/Profile.js +++ b/js/control/Profile.js @@ -82,11 +82,15 @@ BR.Profile = L.Evented.extend({ $(button).button('uploading'); evt.preventDefault(); + var that = this; this.fire('update', { profileText: profile, - callback: function() { + callback: function(err, profileId, profileText) { $(button).button('reset'); $(button).blur(); + if (!err) { + that.cache[profileId] = profileText; + } } }); }, @@ -116,9 +120,14 @@ BR.Profile = L.Evented.extend({ _save: function(evt) { var profileText = this._buildCustomProfile(); + var that = this; this.fire('update', { profileText: profileText, - callback: function() {} + callback: function(err, profileId, profileText) { + if (!err) { + that.cache[profileId] = profileText; + } + } }); }, diff --git a/js/index.js b/js/index.js index 200c8e2..48742fe 100644 --- a/js/index.js +++ b/js/index.js @@ -187,7 +187,7 @@ } if (evt.callback) { - evt.callback(); + evt.callback(err, profileId, evt.profileText); } }); });