diff --git a/umap/static/umap/js/umap.autocomplete.js b/umap/static/umap/js/umap.autocomplete.js
index efd9bf03..d7f588c3 100644
--- a/umap/static/umap/js/umap.autocomplete.js
+++ b/umap/static/umap/js/umap.autocomplete.js
@@ -1,314 +1,340 @@
L.U.AutoComplete = L.Class.extend({
+ options: {
+ placeholder: 'Start typing...',
+ emptyMessage: 'No result',
+ allowFree: true,
+ minChar: 2,
+ maxResults: 5,
+ },
- options: {
- placeholder: 'Start typing...',
- emptyMessage: 'No result',
- allowFree: true,
- minChar: 2,
- maxResults: 5
- },
+ CACHE: '',
+ RESULTS: [],
- CACHE: '',
- RESULTS: [],
-
- initialize: function (el, options) {
- this.el = el;
- var ui = new L.U.UI(document.querySelector('header'));
- this.xhr = new L.U.Xhr(ui);
- L.setOptions(this, options);
- var CURRENT = null;
- try {
- Object.defineProperty(this, 'CURRENT', {
- get: function () {
- return CURRENT;
- },
- set: function (index) {
- if (typeof index === 'object') {
- index = this.resultToIndex(index);
- }
- CURRENT = index;
- }
- });
- } catch (e) {
- // Hello IE8
- }
- return this;
- },
-
- createInput: function () {
- this.input = L.DomUtil.element('input', {
- type: 'text',
- placeholder: this.options.placeholder,
- autocomplete: 'off',
- className: this.options.className
- }, this.el);
- L.DomEvent.on(this.input, 'keydown', this.onKeyDown, this);
- L.DomEvent.on(this.input, 'keyup', this.onKeyUp, this);
- L.DomEvent.on(this.input, 'blur', this.onBlur, this);
- },
-
- createContainer: function () {
- this.container = L.DomUtil.element('ul', {className: 'umap-autocomplete'}, document.body);
- },
-
- resizeContainer: function()
- {
- var l = this.getLeft(this.input);
- var t = this.getTop(this.input) + this.input.offsetHeight;
- this.container.style.left = l + 'px';
- this.container.style.top = t + 'px';
- var width = this.options.width ? this.options.width : this.input.offsetWidth - 2;
- this.container.style.width = width + 'px';
- },
-
-
- onKeyDown: function (e) {
- switch (e.keyCode) {
- case L.U.Keys.TAB:
- if(this.CURRENT !== null) this.setChoice();
- L.DomEvent.stop(e);
- break;
- case L.U.Keys.ENTER:
- L.DomEvent.stop(e);
- this.setChoice();
- break;
- case L.U.Keys.ESC:
- L.DomEvent.stop(e);
- this.hide();
- break;
- case L.U.Keys.DOWN:
- if(this.RESULTS.length > 0) {
- if(this.CURRENT !== null && this.CURRENT < this.RESULTS.length - 1) { // what if one result?
- this.CURRENT++;
- this.highlight();
- }
- else if(this.CURRENT === null) {
- this.CURRENT = 0;
- this.highlight();
- }
- }
- break;
- case L.U.Keys.UP:
- if(this.CURRENT !== null) {
- L.DomEvent.stop(e);
- }
- if(this.RESULTS.length > 0) {
- if(this.CURRENT > 0) {
- this.CURRENT--;
- this.highlight();
- }
- else if(this.CURRENT === 0) {
- this.CURRENT = null;
- this.highlight();
- }
- }
- break;
- }
- },
-
- onKeyUp: function (e) {
- var special = [
- L.U.Keys.TAB,
- L.U.Keys.ENTER,
- L.U.Keys.LEFT,
- L.U.Keys.RIGHT,
- L.U.Keys.DOWN,
- L.U.Keys.UP,
- L.U.Keys.APPLE,
- L.U.Keys.SHIFT,
- L.U.Keys.ALT,
- L.U.Keys.CTRL
- ];
- if (special.indexOf(e.keyCode) === -1)
- {
- this.search();
- }
- },
-
- onBlur: function () {
- var self = this;
- setTimeout(function () {
- self.hide();
- }, 100);
- },
-
- clear: function () {
- this.RESULTS = [];
- this.CURRENT = null;
- this.CACHE = '';
- this.container.innerHTML = '';
- },
-
- hide: function() {
- this.clear();
- this.container.style.display = 'none';
- this.input.value = '';
- },
-
- setChoice: function (choice) {
- choice = choice || this.RESULTS[this.CURRENT];
- if (choice) {
- this.input.value = choice.item.label;
- this.options.on_select(choice);
- this.displaySelected(choice);
- this.hide();
- if (this.options.callback) {
- L.Util.bind(this.options.callback, this)(choice);
- }
- }
- },
-
- search: function() {
- var val = this.input.value;
- if (val.length < this.options.minChar) {
- this.clear();
- return;
- }
- if( val + '' === this.CACHE + '') return;
- else this.CACHE = val;
- this._do_search(val, function (data) {
- this.handleResults(data.data);
- }, this);
- },
-
- createResult: function (item) {
- var el = L.DomUtil.element('li', {}, this.container);
- el.textContent = item.label;
- var result = {
- item: item,
- el: el
- };
- L.DomEvent.on(el, 'mouseover', function () {
- this.CURRENT = result;
- this.highlight();
- }, this);
- L.DomEvent.on(el, 'mousedown', function () {
- this.setChoice();
- }, this);
- return result;
- },
-
- resultToIndex: function (result) {
- var out = null;
- this.forEach(this.RESULTS, function (item, index) {
- if (item.item.value == result.item.value) {
- out = index;
- return;
- }
- });
- return out;
- },
-
- handleResults: function(data) {
- var self = this;
- this.clear();
- this.container.style.display = 'block';
- this.resizeContainer();
- this.forEach(data, function (item) {
- self.RESULTS.push(self.createResult(item));
- });
- this.CURRENT = 0;
- this.highlight();
- //TODO manage no results
- },
-
- highlight: function () {
- var self = this;
- this.forEach(this.RESULTS, function (result, index) {
- if (index === self.CURRENT) L.DomUtil.addClass(result.el, 'on');
- else L.DomUtil.removeClass(result.el, 'on');
- });
- },
-
- getLeft: function (el) {
- var tmp = el.offsetLeft;
- el = el.offsetParent;
- while(el) {
- tmp += el.offsetLeft;
- el = el.offsetParent;
- }
- return tmp;
- },
-
- getTop: function (el) {
- var tmp = el.offsetTop;
- el = el.offsetParent;
- while(el) {
- tmp += el.offsetTop;
- el = el.offsetParent;
- }
- return tmp;
- },
-
- forEach: function (els, callback) {
- Array.prototype.forEach.call(els, callback);
+ initialize(el, options) {
+ this.el = el
+ const ui = new L.U.UI(document.querySelector('header'))
+ this.xhr = new L.U.Xhr(ui)
+ L.setOptions(this, options)
+ let CURRENT = null
+ try {
+ Object.defineProperty(this, 'CURRENT', {
+ get() {
+ return CURRENT
+ },
+ set(index) {
+ if (typeof index === 'object') {
+ index = this.resultToIndex(index)
+ }
+ CURRENT = index
+ },
+ })
+ } catch (e) {
+ // Hello IE8
}
+ return this
+ },
-});
+ createInput() {
+ this.input = L.DomUtil.element(
+ 'input',
+ {
+ type: 'text',
+ placeholder: this.options.placeholder,
+ autocomplete: 'off',
+ className: this.options.className,
+ },
+ this.el
+ )
+ L.DomEvent.on(this.input, 'keydown', this.onKeyDown, this)
+ L.DomEvent.on(this.input, 'keyup', this.onKeyUp, this)
+ L.DomEvent.on(this.input, 'blur', this.onBlur, this)
+ },
+ createContainer() {
+ this.container = L.DomUtil.element(
+ 'ul',
+ { className: 'umap-autocomplete' },
+ document.body
+ )
+ },
+
+ resizeContainer() {
+ const l = this.getLeft(this.input)
+ const t = this.getTop(this.input) + this.input.offsetHeight
+ this.container.style.left = `${l}px`
+ this.container.style.top = `${t}px`
+ const width = this.options.width ? this.options.width : this.input.offsetWidth - 2
+ this.container.style.width = `${width}px`
+ },
+
+ onKeyDown(e) {
+ switch (e.keyCode) {
+ case L.U.Keys.TAB:
+ if (this.CURRENT !== null) this.setChoice()
+ L.DomEvent.stop(e)
+ break
+ case L.U.Keys.ENTER:
+ L.DomEvent.stop(e)
+ this.setChoice()
+ break
+ case L.U.Keys.ESC:
+ L.DomEvent.stop(e)
+ this.hide()
+ break
+ case L.U.Keys.DOWN:
+ if (this.RESULTS.length > 0) {
+ if (this.CURRENT !== null && this.CURRENT < this.RESULTS.length - 1) {
+ // what if one result?
+ this.CURRENT++
+ this.highlight()
+ } else if (this.CURRENT === null) {
+ this.CURRENT = 0
+ this.highlight()
+ }
+ }
+ break
+ case L.U.Keys.UP:
+ if (this.CURRENT !== null) {
+ L.DomEvent.stop(e)
+ }
+ if (this.RESULTS.length > 0) {
+ if (this.CURRENT > 0) {
+ this.CURRENT--
+ this.highlight()
+ } else if (this.CURRENT === 0) {
+ this.CURRENT = null
+ this.highlight()
+ }
+ }
+ break
+ }
+ },
+
+ onKeyUp({ keyCode }) {
+ const special = [
+ L.U.Keys.TAB,
+ L.U.Keys.ENTER,
+ L.U.Keys.LEFT,
+ L.U.Keys.RIGHT,
+ L.U.Keys.DOWN,
+ L.U.Keys.UP,
+ L.U.Keys.APPLE,
+ L.U.Keys.SHIFT,
+ L.U.Keys.ALT,
+ L.U.Keys.CTRL,
+ ]
+ if (!special.includes(keyCode)) {
+ this.search()
+ }
+ },
+
+ onBlur() {
+ const self = this
+ setTimeout(() => {
+ self.hide()
+ }, 100)
+ },
+
+ clear() {
+ this.RESULTS = []
+ this.CURRENT = null
+ this.CACHE = ''
+ this.container.innerHTML = ''
+ },
+
+ hide() {
+ this.clear()
+ this.container.style.display = 'none'
+ this.input.value = ''
+ },
+
+ setChoice(choice = this.RESULTS[this.CURRENT]) {
+ if (choice) {
+ this.input.value = choice.item.label
+ this.options.on_select(choice)
+ this.displaySelected(choice)
+ this.hide()
+ if (this.options.callback) {
+ L.Util.bind(this.options.callback, this)(choice)
+ }
+ }
+ },
+
+ search() {
+ const val = this.input.value
+ if (val.length < this.options.minChar) {
+ this.clear()
+ return
+ }
+ if (`${val}` === `${this.CACHE}`) return
+ else this.CACHE = val
+ this._do_search(
+ val,
+ function (data) {
+ this.handleResults(data.data)
+ },
+ this
+ )
+ },
+
+ createResult(item) {
+ const el = L.DomUtil.element('li', {}, this.container)
+ el.textContent = item.label
+ const result = {
+ item,
+ el,
+ }
+ L.DomEvent.on(
+ el,
+ 'mouseover',
+ function () {
+ this.CURRENT = result
+ this.highlight()
+ },
+ this
+ )
+ L.DomEvent.on(
+ el,
+ 'mousedown',
+ function () {
+ this.setChoice()
+ },
+ this
+ )
+ return result
+ },
+
+ resultToIndex(result) {
+ let out = null
+ this.forEach(this.RESULTS, (item, index) => {
+ if (item.item.value == result.item.value) {
+ out = index
+ return
+ }
+ })
+ return out
+ },
+
+ handleResults(data) {
+ const self = this
+ this.clear()
+ this.container.style.display = 'block'
+ this.resizeContainer()
+ this.forEach(data, (item) => {
+ self.RESULTS.push(self.createResult(item))
+ })
+ this.CURRENT = 0
+ this.highlight()
+ //TODO manage no results
+ },
+
+ highlight() {
+ const self = this
+ this.forEach(this.RESULTS, ({ el }, index) => {
+ if (index === self.CURRENT) L.DomUtil.addClass(el, 'on')
+ else L.DomUtil.removeClass(el, 'on')
+ })
+ },
+
+ getLeft(el) {
+ let tmp = el.offsetLeft
+ el = el.offsetParent
+ while (el) {
+ tmp += el.offsetLeft
+ el = el.offsetParent
+ }
+ return tmp
+ },
+
+ getTop(el) {
+ let tmp = el.offsetTop
+ el = el.offsetParent
+ while (el) {
+ tmp += el.offsetTop
+ el = el.offsetParent
+ }
+ return tmp
+ },
+
+ forEach(els, callback) {
+ Array.prototype.forEach.call(els, callback)
+ },
+})
L.U.AutoComplete.Ajax = L.U.AutoComplete.extend({
+ initialize(el, options) {
+ L.U.AutoComplete.prototype.initialize.call(this, el, options)
+ if (!this.el) return this
+ this.createInput()
+ this.createContainer()
+ this.selected_container = this.initSelectedContainer()
+ },
- initialize: function (el, options) {
- L.U.AutoComplete.prototype.initialize.call(this, el, options);
- if (!this.el) return this;
- this.createInput();
- this.createContainer();
- this.selected_container = this.initSelectedContainer();
- },
-
- optionToResult: function (option) {
- return {
- value: option.value,
- label: option.innerHTML
- };
- },
-
- _do_search: function (val, callback, context) {
- val = val.toLowerCase();
- this.xhr.get('/agnocomplete/AutocompleteUser/?q=' + encodeURIComponent(val), {callback: callback, context: context || this});
+ optionToResult({ value, innerHTML }) {
+ return {
+ value: value,
+ label: innerHTML,
}
+ },
-});
+ _do_search(val, callback, context) {
+ val = val.toLowerCase()
+ this.xhr.get(`/agnocomplete/AutocompleteUser/?q=${encodeURIComponent(val)}`, {
+ callback,
+ context: context || this,
+ })
+ },
+})
L.U.AutoComplete.Ajax.SelectMultiple = L.U.AutoComplete.Ajax.extend({
+ initSelectedContainer() {
+ return L.DomUtil.after(
+ this.input,
+ L.DomUtil.element('ul', { className: 'umap-multiresult' })
+ )
+ },
- initSelectedContainer: function () {
- return L.DomUtil.after(this.input, L.DomUtil.element('ul', {className: 'umap-multiresult'}));
- },
-
- displaySelected: function (result) {
- var result_el = L.DomUtil.element('li', {}, this.selected_container);
- result_el.textContent = result.item.label;
- var close = L.DomUtil.element('span', {className: 'close'}, result_el);
- close.textContent = '×';
- L.DomEvent.on(close, 'click', function () {
- this.selected_container.removeChild(result_el);
- this.options.on_unselect(result);
- }, this);
- this.hide();
- }
-
-});
-
+ displaySelected(result) {
+ const result_el = L.DomUtil.element('li', {}, this.selected_container)
+ result_el.textContent = result.item.label
+ const close = L.DomUtil.element('span', { className: 'close' }, result_el)
+ close.textContent = '×'
+ L.DomEvent.on(
+ close,
+ 'click',
+ function () {
+ this.selected_container.removeChild(result_el)
+ this.options.on_unselect(result)
+ },
+ this
+ )
+ this.hide()
+ },
+})
L.U.AutoComplete.Ajax.Select = L.U.AutoComplete.Ajax.extend({
+ initSelectedContainer() {
+ return L.DomUtil.after(
+ this.input,
+ L.DomUtil.element('div', { className: 'umap-singleresult' })
+ )
+ },
- initSelectedContainer: function () {
- return L.DomUtil.after(this.input, L.DomUtil.element('div', {className: 'umap-singleresult'}));
- },
-
- displaySelected: function (result) {
- var result_el = L.DomUtil.element('div', {}, this.selected_container);
- result_el.textContent = result.item.label;
- var close = L.DomUtil.element('span', {className: 'close'}, result_el);
- close.textContent = '×';
- this.input.style.display = 'none';
- L.DomEvent.on(close, 'click', function () {
- this.selected_container.innerHTML = '';
- this.input.style.display = 'block';
- }, this);
- this.hide();
- }
-
-});
+ displaySelected({ item }) {
+ const result_el = L.DomUtil.element('div', {}, this.selected_container)
+ result_el.textContent = item.label
+ const close = L.DomUtil.element('span', { className: 'close' }, result_el)
+ close.textContent = '×'
+ this.input.style.display = 'none'
+ L.DomEvent.on(
+ close,
+ 'click',
+ function () {
+ this.selected_container.innerHTML = ''
+ this.input.style.display = 'block'
+ },
+ this
+ )
+ this.hide()
+ },
+})
diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js
index 66d4647e..d78a8b4b 100644
--- a/umap/static/umap/js/umap.controls.js
+++ b/umap/static/umap/js/umap.controls.js
@@ -1,1310 +1,1353 @@
L.U.BaseAction = L.ToolbarAction.extend({
-
- initialize: function (map) {
- this.map = map;
- this.options.toolbarIcon = {
- className: this.options.className,
- tooltip: this.options.tooltip
- };
- L.ToolbarAction.prototype.initialize.call(this);
- if (this.options.helpMenu && !this.map.helpMenuActions[this.options.className]) this.map.helpMenuActions[this.options.className] = this;
+ initialize(map) {
+ this.map = map
+ this.options.toolbarIcon = {
+ className: this.options.className,
+ tooltip: this.options.tooltip,
}
-
-});
+ L.ToolbarAction.prototype.initialize.call(this)
+ if (this.options.helpMenu && !this.map.helpMenuActions[this.options.className])
+ this.map.helpMenuActions[this.options.className] = this
+ },
+})
L.U.ImportAction = L.U.BaseAction.extend({
+ options: {
+ helpMenu: true,
+ className: 'upload-data dark',
+ tooltip: `${L._('Import data')} (Ctrl+I)`,
+ },
- options: {
- helpMenu: true,
- className: 'upload-data dark',
- tooltip: L._('Import data') + ' (Ctrl+I)'
- },
-
- addHooks: function () {
- this.map.importPanel();
- }
-
-});
+ addHooks() {
+ this.map.importPanel()
+ },
+})
L.U.EditPropertiesAction = L.U.BaseAction.extend({
+ options: {
+ helpMenu: true,
+ className: 'update-map-settings dark',
+ tooltip: L._('Edit map settings'),
+ },
- options: {
- helpMenu: true,
- className: 'update-map-settings dark',
- tooltip: L._('Edit map settings')
- },
-
- addHooks: function () {
- this.map.edit();
- }
-
-});
+ addHooks() {
+ this.map.edit()
+ },
+})
L.U.ChangeTileLayerAction = L.U.BaseAction.extend({
+ options: {
+ helpMenu: true,
+ className: 'dark update-map-tilelayers',
+ tooltip: L._('Change tilelayers'),
+ },
- options: {
- helpMenu: true,
- className: 'dark update-map-tilelayers',
- tooltip: L._('Change tilelayers')
- },
-
- addHooks: function () {
- this.map.updateTileLayers();
- }
-
-});
+ addHooks() {
+ this.map.updateTileLayers()
+ },
+})
L.U.ManageDatalayersAction = L.U.BaseAction.extend({
+ options: {
+ className: 'dark manage-datalayers',
+ tooltip: L._('Manage layers'),
+ },
- options: {
- className: 'dark manage-datalayers',
- tooltip: L._('Manage layers')
- },
-
- addHooks: function () {
- this.map.manageDatalayers();
- }
-
-});
+ addHooks() {
+ this.map.manageDatalayers()
+ },
+})
L.U.UpdateExtentAction = L.U.BaseAction.extend({
+ options: {
+ className: 'update-map-extent dark',
+ tooltip: L._('Save this center and zoom'),
+ },
- options: {
- className: 'update-map-extent dark',
- tooltip: L._('Save this center and zoom')
- },
-
- addHooks: function () {
- this.map.updateExtent();
- }
-
-});
+ addHooks() {
+ this.map.updateExtent()
+ },
+})
L.U.UpdatePermsAction = L.U.BaseAction.extend({
+ options: {
+ className: 'update-map-permissions dark',
+ tooltip: L._('Update permissions and editors'),
+ },
- options: {
- className: 'update-map-permissions dark',
- tooltip: L._('Update permissions and editors')
- },
-
- addHooks: function () {
- this.map.permissions.edit();
- }
-
-});
+ addHooks() {
+ this.map.permissions.edit()
+ },
+})
L.U.DrawMarkerAction = L.U.BaseAction.extend({
+ options: {
+ helpMenu: true,
+ className: 'umap-draw-marker dark',
+ tooltip: L._('Draw a marker'),
+ },
- options: {
- helpMenu: true,
- className: 'umap-draw-marker dark',
- tooltip: L._('Draw a marker')
- },
-
- addHooks: function () {
- this.map.startMarker();
- }
-
-});
+ addHooks() {
+ this.map.startMarker()
+ },
+})
L.U.DrawPolylineAction = L.U.BaseAction.extend({
+ options: {
+ helpMenu: true,
+ className: 'umap-draw-polyline dark',
+ tooltip: L._('Draw a polyline'),
+ },
- options: {
- helpMenu: true,
- className: 'umap-draw-polyline dark',
- tooltip: L._('Draw a polyline')
- },
-
- addHooks: function () {
- this.map.startPolyline();
- }
-
-});
+ addHooks() {
+ this.map.startPolyline()
+ },
+})
L.U.DrawPolygonAction = L.U.BaseAction.extend({
+ options: {
+ helpMenu: true,
+ className: 'umap-draw-polygon dark',
+ tooltip: L._('Draw a polygon'),
+ },
- options: {
- helpMenu: true,
- className: 'umap-draw-polygon dark',
- tooltip: L._('Draw a polygon')
- },
-
- addHooks: function () {
- this.map.startPolygon();
- }
-
-});
+ addHooks() {
+ this.map.startPolygon()
+ },
+})
L.U.AddPolylineShapeAction = L.U.BaseAction.extend({
+ options: {
+ className: 'umap-draw-polyline-multi dark',
+ tooltip: L._('Add a line to the current multi'),
+ },
- options: {
- className: 'umap-draw-polyline-multi dark',
- tooltip: L._('Add a line to the current multi')
- },
-
- addHooks: function () {
- this.map.editedFeature.editor.newShape();
- }
-
-});
+ addHooks() {
+ this.map.editedFeature.editor.newShape()
+ },
+})
L.U.AddPolygonShapeAction = L.U.AddPolylineShapeAction.extend({
-
- options: {
- className: 'umap-draw-polygon-multi dark',
- tooltip: L._('Add a polygon to the current multi')
- }
-
-});
+ options: {
+ className: 'umap-draw-polygon-multi dark',
+ tooltip: L._('Add a polygon to the current multi'),
+ },
+})
L.U.BaseFeatureAction = L.ToolbarAction.extend({
+ initialize(map, feature, latlng) {
+ this.map = map
+ this.feature = feature
+ this.latlng = latlng
+ L.ToolbarAction.prototype.initialize.call(this)
+ this.postInit()
+ },
- initialize: function (map, feature, latlng) {
- this.map = map;
- this.feature = feature;
- this.latlng = latlng;
- L.ToolbarAction.prototype.initialize.call(this);
- this.postInit();
- },
+ postInit() {},
- postInit: function () {},
+ hideToolbar() {
+ this.map.removeLayer(this.toolbar)
+ },
- hideToolbar: function () {
- this.map.removeLayer(this.toolbar);
- },
-
- addHooks: function () {
- this.onClick({latlng: this.latlng});
- this.hideToolbar();
- }
-
-});
+ addHooks() {
+ this.onClick({ latlng: this.latlng })
+ this.hideToolbar()
+ },
+})
L.U.CreateHoleAction = L.U.BaseFeatureAction.extend({
-
- options: {
- toolbarIcon: {
- className: 'umap-new-hole',
- tooltip: L._('Start a hole here')
- }
+ options: {
+ toolbarIcon: {
+ className: 'umap-new-hole',
+ tooltip: L._('Start a hole here'),
},
+ },
- onClick: function (e) {
- this.feature.startHole(e);
- }
-
-});
+ onClick(e) {
+ this.feature.startHole(e)
+ },
+})
L.U.ToggleEditAction = L.U.BaseFeatureAction.extend({
-
- options: {
- toolbarIcon: {
- className: 'umap-toggle-edit',
- tooltip: L._('Toggle edit mode (Shift+Click)')
- }
+ options: {
+ toolbarIcon: {
+ className: 'umap-toggle-edit',
+ tooltip: L._('Toggle edit mode (Shift+Click)'),
},
+ },
- onClick: function (e) {
- if (this.feature._toggleEditing) this.feature._toggleEditing(e); // Path
- else this.feature.edit(e); // Marker
- }
-
-});
+ onClick(e) {
+ if (this.feature._toggleEditing) this.feature._toggleEditing(e) // Path
+ else this.feature.edit(e) // Marker
+ },
+})
L.U.DeleteFeatureAction = L.U.BaseFeatureAction.extend({
-
- options: {
- toolbarIcon: {
- className: 'umap-delete-all',
- tooltip: L._('Delete this feature')
- }
+ options: {
+ toolbarIcon: {
+ className: 'umap-delete-all',
+ tooltip: L._('Delete this feature'),
},
+ },
- postInit: function () {
- if (!this.feature.isMulti()) this.options.toolbarIcon.className = 'umap-delete-one-of-one';
- },
+ postInit() {
+ if (!this.feature.isMulti())
+ this.options.toolbarIcon.className = 'umap-delete-one-of-one'
+ },
- onClick: function (e) {
- this.feature.confirmDelete(e);
- }
-
-});
+ onClick(e) {
+ this.feature.confirmDelete(e)
+ },
+})
L.U.DeleteShapeAction = L.U.BaseFeatureAction.extend({
-
- options: {
- toolbarIcon: {
- className: 'umap-delete-one-of-multi',
- tooltip: L._('Delete this shape')
- }
+ options: {
+ toolbarIcon: {
+ className: 'umap-delete-one-of-multi',
+ tooltip: L._('Delete this shape'),
},
+ },
- onClick: function (e) {
- this.feature.enableEdit().deleteShapeAt(e.latlng);
- }
-
-});
+ onClick({ latlng }) {
+ this.feature.enableEdit().deleteShapeAt(latlng)
+ },
+})
L.U.ExtractShapeFromMultiAction = L.U.BaseFeatureAction.extend({
-
- options: {
- toolbarIcon: {
- className: 'umap-extract-shape-from-multi',
- tooltip: L._('Extract shape to separate feature')
- }
+ options: {
+ toolbarIcon: {
+ className: 'umap-extract-shape-from-multi',
+ tooltip: L._('Extract shape to separate feature'),
},
+ },
- onClick: function (e) {
- this.feature.isolateShape(e.latlng);
- }
-
-});
+ onClick({ latlng }) {
+ this.feature.isolateShape(latlng)
+ },
+})
L.U.BaseVertexAction = L.U.BaseFeatureAction.extend({
-
- initialize: function (map, feature, latlng, vertex) {
- this.vertex = vertex;
- L.U.BaseFeatureAction.prototype.initialize.call(this, map, feature, latlng);
- }
-
-});
+ initialize(map, feature, latlng, vertex) {
+ this.vertex = vertex
+ L.U.BaseFeatureAction.prototype.initialize.call(this, map, feature, latlng)
+ },
+})
L.U.DeleteVertexAction = L.U.BaseVertexAction.extend({
-
- options: {
- toolbarIcon: {
- className: 'umap-delete-vertex',
- tooltip: L._('Delete this vertex (Alt+Click)')
- }
+ options: {
+ toolbarIcon: {
+ className: 'umap-delete-vertex',
+ tooltip: L._('Delete this vertex (Alt+Click)'),
},
+ },
- onClick: function () {
- this.vertex.delete();
- }
-
-});
+ onClick() {
+ this.vertex.delete()
+ },
+})
L.U.SplitLineAction = L.U.BaseVertexAction.extend({
-
- options: {
- toolbarIcon: {
- className: 'umap-split-line',
- tooltip: L._('Split line')
- }
+ options: {
+ toolbarIcon: {
+ className: 'umap-split-line',
+ tooltip: L._('Split line'),
},
+ },
- onClick: function () {
- this.vertex.split();
- }
-
-});
+ onClick() {
+ this.vertex.split()
+ },
+})
L.U.ContinueLineAction = L.U.BaseVertexAction.extend({
-
- options: {
- toolbarIcon: {
- className: 'umap-continue-line',
- tooltip: L._('Continue line')
- }
+ options: {
+ toolbarIcon: {
+ className: 'umap-continue-line',
+ tooltip: L._('Continue line'),
},
+ },
- onClick: function () {
- this.vertex.continue();
- }
-
-});
+ onClick() {
+ this.vertex.continue()
+ },
+})
// Leaflet.Toolbar doesn't allow twice same toolbar class…
-L.U.SettingsToolbar = L.Toolbar.Control.extend({});
+L.U.SettingsToolbar = L.Toolbar.Control.extend({})
L.U.DrawToolbar = L.Toolbar.Control.extend({
+ initialize(options) {
+ L.Toolbar.Control.prototype.initialize.call(this, options)
+ this.map = this.options.map
+ this.map.on('seteditedfeature', this.redraw, this)
+ },
- initialize: function (options) {
- L.Toolbar.Control.prototype.initialize.call(this, options);
- this.map = this.options.map;
- this.map.on('seteditedfeature', this.redraw, this);
- },
-
- appendToContainer: function (container) {
- this.options.actions = [];
- if (this.map.options.enableMarkerDraw) {
- this.options.actions.push(L.U.DrawMarkerAction);
- }
- if (this.map.options.enablePolylineDraw) {
- this.options.actions.push(L.U.DrawPolylineAction);
- if (this.map.editedFeature && this.map.editedFeature instanceof L.U.Polyline) {
- this.options.actions.push(L.U.AddPolylineShapeAction);
- }
- }
- if (this.map.options.enablePolygonDraw) {
- this.options.actions.push(L.U.DrawPolygonAction);
- if (this.map.editedFeature && this.map.editedFeature instanceof L.U.Polygon) {
- this.options.actions.push(L.U.AddPolygonShapeAction);
- }
- }
- L.Toolbar.Control.prototype.appendToContainer.call(this, container);
- },
-
- redraw: function () {
- var container = this._control.getContainer();
- container.innerHTML = '';
- this.appendToContainer(container);
+ appendToContainer(container) {
+ this.options.actions = []
+ if (this.map.options.enableMarkerDraw) {
+ this.options.actions.push(L.U.DrawMarkerAction)
}
+ if (this.map.options.enablePolylineDraw) {
+ this.options.actions.push(L.U.DrawPolylineAction)
+ if (this.map.editedFeature && this.map.editedFeature instanceof L.U.Polyline) {
+ this.options.actions.push(L.U.AddPolylineShapeAction)
+ }
+ }
+ if (this.map.options.enablePolygonDraw) {
+ this.options.actions.push(L.U.DrawPolygonAction)
+ if (this.map.editedFeature && this.map.editedFeature instanceof L.U.Polygon) {
+ this.options.actions.push(L.U.AddPolygonShapeAction)
+ }
+ }
+ L.Toolbar.Control.prototype.appendToContainer.call(this, container)
+ },
-});
-
+ redraw() {
+ const container = this._control.getContainer()
+ container.innerHTML = ''
+ this.appendToContainer(container)
+ },
+})
L.U.EditControl = L.Control.extend({
+ options: {
+ position: 'topright',
+ },
- options: {
- position: 'topright'
- },
+ onAdd(map) {
+ const container = L.DomUtil.create(
+ 'div',
+ 'leaflet-control-edit-enable umap-control'
+ )
+ const edit = L.DomUtil.create('a', '', container)
+ edit.href = '#'
+ edit.title = `${L._('Enable editing')} (Ctrl+E)`
- onAdd: function (map) {
- var container = L.DomUtil.create('div', 'leaflet-control-edit-enable umap-control'),
- edit = L.DomUtil.create('a', '', container);
- edit.href = '#';
- edit.title = L._('Enable editing') + ' (Ctrl+E)';
-
- L.DomEvent
- .addListener(edit, 'click', L.DomEvent.stop)
- .addListener(edit, 'click', map.enableEdit, map);
- return container;
- }
-
-});
+ L.DomEvent.addListener(edit, 'click', L.DomEvent.stop).addListener(
+ edit,
+ 'click',
+ map.enableEdit,
+ map
+ )
+ return container
+ },
+})
/* Share control */
L.Control.Embed = L.Control.extend({
+ options: {
+ position: 'topleft',
+ },
- options: {
- position: 'topleft'
- },
+ onAdd(map) {
+ const container = L.DomUtil.create('div', 'leaflet-control-embed umap-control')
- onAdd: function (map) {
- var container = L.DomUtil.create('div', 'leaflet-control-embed umap-control');
+ const link = L.DomUtil.create('a', '', container)
+ link.href = '#'
+ link.title = L._('Embed and share this map')
- var link = L.DomUtil.create('a', '', container);
- link.href = '#';
- link.title = L._('Embed and share this map');
+ L.DomEvent.on(link, 'click', L.DomEvent.stop)
+ .on(link, 'click', map.renderShareBox, map)
+ .on(link, 'dblclick', L.DomEvent.stopPropagation)
- L.DomEvent
- .on(link, 'click', L.DomEvent.stop)
- .on(link, 'click', map.renderShareBox, map)
- .on(link, 'dblclick', L.DomEvent.stopPropagation);
-
- return container;
- }
-});
+ return container
+ },
+})
L.U.MoreControls = L.Control.extend({
+ options: {
+ position: 'topleft',
+ },
- options: {
- position: 'topleft'
- },
+ onAdd() {
+ const container = L.DomUtil.create('div', '')
+ const more = L.DomUtil.create('a', 'umap-control-more umap-control-text', container)
+ const less = L.DomUtil.create('a', 'umap-control-less umap-control-text', container)
+ more.href = '#'
+ more.title = L._('More controls')
- onAdd: function () {
- var container = L.DomUtil.create('div', ''),
- more = L.DomUtil.create('a', 'umap-control-more umap-control-text', container),
- less = L.DomUtil.create('a', 'umap-control-less umap-control-text', container);
- more.href = '#';
- more.title = L._('More controls');
+ L.DomEvent.on(more, 'click', L.DomEvent.stop).on(more, 'click', this.toggle, this)
- L.DomEvent
- .on(more, 'click', L.DomEvent.stop)
- .on(more, 'click', this.toggle, this);
+ less.href = '#'
+ less.title = L._('Hide controls')
- less.href = '#';
- less.title = L._('Hide controls');
+ L.DomEvent.on(less, 'click', L.DomEvent.stop).on(less, 'click', this.toggle, this)
- L.DomEvent
- .on(less, 'click', L.DomEvent.stop)
- .on(less, 'click', this.toggle, this);
-
- return container;
- },
-
- toggle: function () {
- var pos = this.getPosition(),
- corner = this._map._controlCorners[pos],
- className = 'umap-more-controls';
- if (L.DomUtil.hasClass(corner, className)) L.DomUtil.removeClass(corner, className);
- else L.DomUtil.addClass(corner, className);
- }
-
-});
+ return container
+ },
+ toggle() {
+ const pos = this.getPosition()
+ const corner = this._map._controlCorners[pos]
+ const className = 'umap-more-controls'
+ if (L.DomUtil.hasClass(corner, className)) L.DomUtil.removeClass(corner, className)
+ else L.DomUtil.addClass(corner, className)
+ },
+})
L.U.PermanentCreditsControl = L.Control.extend({
+ options: {
+ position: 'bottomleft',
+ },
- options: {
- position: 'bottomleft'
- },
+ initialize(map, options) {
+ this.map = map
+ L.Control.prototype.initialize.call(this, options)
+ },
- initialize: function (map, options) {
- this.map = map;
- L.Control.prototype.initialize.call(this, options);
- },
+ onAdd() {
+ const paragraphContainer = L.DomUtil.create(
+ 'div',
+ 'umap-permanent-credits-container'
+ )
+ const creditsParagraph = L.DomUtil.create('p', '', paragraphContainer)
- onAdd: function () {
- var paragraphContainer = L.DomUtil.create('div', 'umap-permanent-credits-container'),
- creditsParagraph = L.DomUtil.create('p', '', paragraphContainer);
+ this.paragraphContainer = paragraphContainer
+ this.setCredits()
+ this.setBackground()
- this.paragraphContainer = paragraphContainer;
- this.setCredits();
- this.setBackground();
+ return paragraphContainer
+ },
- return paragraphContainer;
- },
+ setCredits() {
+ this.paragraphContainer.innerHTML = L.Util.toHTML(this.map.options.permanentCredit)
+ },
- setCredits: function () {
- this.paragraphContainer.innerHTML = L.Util.toHTML(this.map.options.permanentCredit);
- },
-
- setBackground: function () {
- if (this.map.options.permanentCreditBackground) {
- this.paragraphContainer.style.backgroundColor = '#FFFFFFB0';
- } else {
- this.paragraphContainer.style.backgroundColor = '';
- }
+ setBackground() {
+ if (this.map.options.permanentCreditBackground) {
+ this.paragraphContainer.style.backgroundColor = '#FFFFFFB0'
+ } else {
+ this.paragraphContainer.style.backgroundColor = ''
}
-
-});
-
+ },
+})
L.U.DataLayersControl = L.Control.extend({
+ options: {
+ position: 'topleft',
+ },
- options: {
- position: 'topleft'
- },
+ labels: {
+ zoomToLayer: L._('Zoom to layer extent'),
+ toggleLayer: L._('Show/hide layer'),
+ editLayer: L._('Edit'),
+ },
- labels: {
- zoomToLayer: L._('Zoom to layer extent'),
- toggleLayer: L._('Show/hide layer'),
- editLayer: L._('Edit')
- },
+ initialize(map, options) {
+ this.map = map
+ L.Control.prototype.initialize.call(this, options)
+ },
- initialize: function (map, options) {
- this.map = map;
- L.Control.prototype.initialize.call(this, options);
- },
+ _initLayout(map) {
+ const container = (this._container = L.DomUtil.create(
+ 'div',
+ 'leaflet-control-browse umap-control'
+ ))
+ const actions = L.DomUtil.create('div', 'umap-browse-actions', container)
+ this._datalayers_container = L.DomUtil.create(
+ 'ul',
+ 'umap-browse-datalayers',
+ actions
+ )
- _initLayout: function (map) {
- var container = this._container = L.DomUtil.create('div', 'leaflet-control-browse umap-control'),
- actions = L.DomUtil.create('div', 'umap-browse-actions', container);
- this._datalayers_container = L.DomUtil.create('ul', 'umap-browse-datalayers', actions);
+ const link = L.DomUtil.create('a', 'umap-browse-link', actions)
+ link.href = '#'
+ link.title = link.textContent = L._('Browse data')
- var link = L.DomUtil.create('a', 'umap-browse-link', actions);
- link.href = '#';
- link.title = link.textContent = L._('Browse data');
+ const toggle = L.DomUtil.create('a', 'umap-browse-toggle', container)
+ toggle.href = '#'
+ toggle.title = L._('See data layers')
- var toggle = L.DomUtil.create('a', 'umap-browse-toggle', container);
- toggle.href = '#';
- toggle.title = L._('See data layers')
+ L.DomEvent.on(toggle, 'click', L.DomEvent.stop)
- L.DomEvent
- .on(toggle, 'click', L.DomEvent.stop);
+ L.DomEvent.on(link, 'click', L.DomEvent.stop).on(
+ link,
+ 'click',
+ map.openBrowser,
+ map
+ )
- L.DomEvent
- .on(link, 'click', L.DomEvent.stop)
- .on(link, 'click', map.openBrowser, map);
+ map.whenReady(function () {
+ this.update()
+ }, this)
- map.whenReady(function () {
- this.update();
- }, this);
-
- if (L.Browser.pointer) {
- L.DomEvent.disableClickPropagation(container);
- L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
- L.DomEvent.on(container, 'MozMousePixelScroll', L.DomEvent.stopPropagation);
- }
- if (!L.Browser.touch) {
- L.DomEvent.on(container, {
- mouseenter: this.expand,
- mouseleave: this.collapse
- }, this);
- } else {
- L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
- L.DomEvent.on(toggle, 'click', L.DomEvent.stop)
- .on(toggle, 'click', this.expand, this);
- map.on('click', this.collapse, this);
- }
-
- return container;
- },
-
- onAdd: function (map) {
- if (!this._container) this._initLayout(map);
- if (map.options.datalayersControl === 'expanded') this.expand();
- return this._container;
- },
-
- onRemove: function (map) {
- this.collapse();
- },
-
- update: function () {
- if (this._datalayers_container && this._map) {
- this._datalayers_container.innerHTML = '';
- this._map.eachDataLayerReverse(function (datalayer) {
- this.addDataLayer(this._datalayers_container, datalayer);
- }, this)
- }
- },
-
- expand: function () {
- L.DomUtil.addClass(this._container, 'expanded');
- },
-
- collapse: function () {
- if (this._map.options.datalayersControl === 'expanded') return;
- L.DomUtil.removeClass(this._container, 'expanded');
- },
-
- addDataLayer: function (container, datalayer, draggable) {
- var datalayerLi = L.DomUtil.create('li', '', container);
- if (draggable) L.DomUtil.element('i', {className: 'drag-handle', title: L._('Drag to reorder')}, datalayerLi);
- datalayer.renderToolbox(datalayerLi);
- var title = L.DomUtil.add('span', 'layer-title', datalayerLi, datalayer.options.name);
-
- datalayerLi.id = 'browse_data_toggle_' + L.stamp(datalayer);
- L.DomUtil.classIf(datalayerLi, 'off', !datalayer.isVisible());
-
- title.textContent = datalayer.options.name;
- },
-
- newDataLayer: function () {
- var datalayer = this.map.createDataLayer({});
- datalayer.edit();
- },
-
- openPanel: function () {
- if (!this.map.editEnabled) return;
- var container = L.DomUtil.create('ul', 'umap-browse-datalayers');
- this.map.eachDataLayerReverse(function (datalayer) {
- this.addDataLayer(container, datalayer, true);
- }, this);
- var orderable = new L.U.Orderable(container);
- orderable.on('drop', function (e) {
- var layer = this.map.datalayers[e.src.dataset.id],
- other = this.map.datalayers[e.dst.dataset.id],
- minIndex = Math.min(e.initialIndex, e.finalIndex);
- if (e.finalIndex === 0) layer.bringToTop();
- else if (e.finalIndex > e.initialIndex) layer.insertBefore(other);
- else layer.insertAfter(other);
- this.map.eachDataLayerReverse(function (datalayer) {
- if (datalayer.getRank() >= minIndex) datalayer.isDirty = true;
- });
- this.map.indexDatalayers();
- }, this);
-
- var bar = L.DomUtil.create('div', 'button-bar', container),
- add = L.DomUtil.create('a', 'show-on-edit block add-datalayer button', bar);
- add.href = '#';
- add.textContent = add.title = L._('Add a layer');
-
- L.DomEvent
- .on(add, 'click', L.DomEvent.stop)
- .on(add, 'click', this.newDataLayer, this);
-
- this.map.ui.openPanel({data: {html: container}, className: 'dark'});
+ if (L.Browser.pointer) {
+ L.DomEvent.disableClickPropagation(container)
+ L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation)
+ L.DomEvent.on(container, 'MozMousePixelScroll', L.DomEvent.stopPropagation)
+ }
+ if (!L.Browser.touch) {
+ L.DomEvent.on(
+ container,
+ {
+ mouseenter: this.expand,
+ mouseleave: this.collapse,
+ },
+ this
+ )
+ } else {
+ L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation)
+ L.DomEvent.on(toggle, 'click', L.DomEvent.stop).on(
+ toggle,
+ 'click',
+ this.expand,
+ this
+ )
+ map.on('click', this.collapse, this)
}
-});
+ return container
+ },
+
+ onAdd(map) {
+ if (!this._container) this._initLayout(map)
+ if (map.options.datalayersControl === 'expanded') this.expand()
+ return this._container
+ },
+
+ onRemove(map) {
+ this.collapse()
+ },
+
+ update() {
+ if (this._datalayers_container && this._map) {
+ this._datalayers_container.innerHTML = ''
+ this._map.eachDataLayerReverse(function (datalayer) {
+ this.addDataLayer(this._datalayers_container, datalayer)
+ }, this)
+ }
+ },
+
+ expand() {
+ L.DomUtil.addClass(this._container, 'expanded')
+ },
+
+ collapse() {
+ if (this._map.options.datalayersControl === 'expanded') return
+ L.DomUtil.removeClass(this._container, 'expanded')
+ },
+
+ addDataLayer(container, datalayer, draggable) {
+ const datalayerLi = L.DomUtil.create('li', '', container)
+ if (draggable)
+ L.DomUtil.element(
+ 'i',
+ { className: 'drag-handle', title: L._('Drag to reorder') },
+ datalayerLi
+ )
+ datalayer.renderToolbox(datalayerLi)
+ const title = L.DomUtil.add(
+ 'span',
+ 'layer-title',
+ datalayerLi,
+ datalayer.options.name
+ )
+
+ datalayerLi.id = `browse_data_toggle_${L.stamp(datalayer)}`
+ L.DomUtil.classIf(datalayerLi, 'off', !datalayer.isVisible())
+
+ title.textContent = datalayer.options.name
+ },
+
+ newDataLayer() {
+ const datalayer = this.map.createDataLayer({})
+ datalayer.edit()
+ },
+
+ openPanel() {
+ if (!this.map.editEnabled) return
+ const container = L.DomUtil.create('ul', 'umap-browse-datalayers')
+ this.map.eachDataLayerReverse(function (datalayer) {
+ this.addDataLayer(container, datalayer, true)
+ }, this)
+ const orderable = new L.U.Orderable(container)
+ orderable.on(
+ 'drop',
+ function ({ src, dst, initialIndex, finalIndex }) {
+ const layer = this.map.datalayers[src.dataset.id]
+ const other = this.map.datalayers[dst.dataset.id]
+ const minIndex = Math.min(initialIndex, finalIndex)
+ if (finalIndex === 0) layer.bringToTop()
+ else if (finalIndex > initialIndex) layer.insertBefore(other)
+ else layer.insertAfter(other)
+ this.map.eachDataLayerReverse((datalayer) => {
+ if (datalayer.getRank() >= minIndex) datalayer.isDirty = true
+ })
+ this.map.indexDatalayers()
+ },
+ this
+ )
+
+ const bar = L.DomUtil.create('div', 'button-bar', container)
+ const add = L.DomUtil.create('a', 'show-on-edit block add-datalayer button', bar)
+ add.href = '#'
+ add.textContent = add.title = L._('Add a layer')
+
+ L.DomEvent.on(add, 'click', L.DomEvent.stop).on(
+ add,
+ 'click',
+ this.newDataLayer,
+ this
+ )
+
+ this.map.ui.openPanel({ data: { html: container }, className: 'dark' })
+ },
+})
L.U.DataLayer.include({
+ renderToolbox(container) {
+ const toggle = L.DomUtil.create('i', 'layer-toggle', container)
+ const zoomTo = L.DomUtil.create('i', 'layer-zoom_to', container)
+ const edit = L.DomUtil.create('i', 'layer-edit show-on-edit', container)
+ const table = L.DomUtil.create('i', 'layer-table-edit show-on-edit', container)
+ const remove = L.DomUtil.create('i', 'layer-delete show-on-edit', container)
+ zoomTo.title = L._('Zoom to layer extent')
+ toggle.title = L._('Show/hide layer')
+ edit.title = L._('Edit')
+ table.title = L._('Edit properties in a table')
+ remove.title = L._('Delete layer')
+ L.DomEvent.on(toggle, 'click', this.toggle, this)
+ L.DomEvent.on(zoomTo, 'click', this.zoomTo, this)
+ L.DomEvent.on(edit, 'click', this.edit, this)
+ L.DomEvent.on(table, 'click', this.tableEdit, this)
+ L.DomEvent.on(
+ remove,
+ 'click',
+ function () {
+ if (!this.isVisible()) return
+ if (!confirm(L._('Are you sure you want to delete this layer?'))) return
+ this._delete()
+ this.map.ui.closePanel()
+ },
+ this
+ )
+ L.DomUtil.addClass(container, this.getHidableClass())
+ L.DomUtil.classIf(container, 'off', !this.isVisible())
+ container.dataset.id = L.stamp(this)
+ },
- renderToolbox: function (container) {
- var toggle = L.DomUtil.create('i', 'layer-toggle', container),
- zoomTo = L.DomUtil.create('i', 'layer-zoom_to', container),
- edit = L.DomUtil.create('i', 'layer-edit show-on-edit', container),
- table = L.DomUtil.create('i', 'layer-table-edit show-on-edit', container),
- remove = L.DomUtil.create('i', 'layer-delete show-on-edit', container);
- zoomTo.title = L._('Zoom to layer extent');
- toggle.title = L._('Show/hide layer');
- edit.title = L._('Edit');
- table.title = L._('Edit properties in a table');
- remove.title = L._('Delete layer');
- L.DomEvent.on(toggle, 'click', this.toggle, this);
- L.DomEvent.on(zoomTo, 'click', this.zoomTo, this);
- L.DomEvent.on(edit, 'click', this.edit, this);
- L.DomEvent.on(table, 'click', this.tableEdit, this);
- L.DomEvent.on(remove, 'click', function () {
- if (!this.isVisible()) return;
- if (!confirm(L._('Are you sure you want to delete this layer?'))) return;
- this._delete();
- this.map.ui.closePanel();
- }, this);
- L.DomUtil.addClass(container, this.getHidableClass());
- L.DomUtil.classIf(container, 'off', !this.isVisible());
- container.dataset.id = L.stamp(this);
- },
+ getHidableElements() {
+ return document.querySelectorAll(`.${this.getHidableClass()}`)
+ },
- getHidableElements: function () {
- return document.querySelectorAll('.' + this.getHidableClass());
- },
+ getHidableClass() {
+ return `show_with_datalayer_${L.stamp(this)}`
+ },
- getHidableClass: function () {
- return 'show_with_datalayer_' + L.stamp(this);
- },
-
- propagateRemote: function () {
- var els = this.getHidableElements();
- for (var i = 0; i < els.length; i++) {
- L.DomUtil.classIf(els[i], 'remotelayer', this.isRemoteLayer());
- }
- },
-
- propagateHide: function () {
- var els = this.getHidableElements();
- for (var i = 0; i < els.length; i++) {
- L.DomUtil.addClass(els[i], 'off');
- }
- },
-
- propagateShow: function () {
- this.onceLoaded(function () {
- var els = this.getHidableElements();
- for (var i = 0; i < els.length; i++) {
- L.DomUtil.removeClass(els[i], 'off');
- }
- }, this);
+ propagateRemote() {
+ const els = this.getHidableElements()
+ for (let i = 0; i < els.length; i++) {
+ L.DomUtil.classIf(els[i], 'remotelayer', this.isRemoteLayer())
}
+ },
-});
+ propagateHide() {
+ const els = this.getHidableElements()
+ for (let i = 0; i < els.length; i++) {
+ L.DomUtil.addClass(els[i], 'off')
+ }
+ },
+
+ propagateShow() {
+ this.onceLoaded(function () {
+ const els = this.getHidableElements()
+ for (let i = 0; i < els.length; i++) {
+ L.DomUtil.removeClass(els[i], 'off')
+ }
+ }, this)
+ },
+})
L.U.DataLayer.addInitHook(function () {
- this.on('hide', this.propagateHide);
- this.on('show', this.propagateShow);
- this.propagateShow();
-});
-
+ this.on('hide', this.propagateHide)
+ this.on('show', this.propagateShow)
+ this.propagateShow()
+})
L.U.Map.include({
+ _openBrowser() {
+ const browserContainer = L.DomUtil.create('div', 'umap-browse-data')
+ const title = L.DomUtil.add(
+ 'h3',
+ 'umap-browse-title',
+ browserContainer,
+ this.options.name
+ )
+ const filter = L.DomUtil.create('input', '', browserContainer)
+ let filterValue = ''
+ const featuresContainer = L.DomUtil.create(
+ 'div',
+ 'umap-browse-features',
+ browserContainer
+ )
+ const filterKeys = this.getFilterKeys()
+ filter.type = 'text'
+ filter.placeholder = L._('Filter…')
+ filter.value = this.options.filter || ''
- _openBrowser: function () {
- var browserContainer = L.DomUtil.create('div', 'umap-browse-data'),
- title = L.DomUtil.add('h3', 'umap-browse-title', browserContainer, this.options.name),
- filter = L.DomUtil.create('input', '', browserContainer),
- filterValue = '',
- featuresContainer = L.DomUtil.create('div', 'umap-browse-features', browserContainer),
- filterKeys = this.getFilterKeys();
- filter.type = 'text';
- filter.placeholder = L._('Filter…');
- filter.value = this.options.filter || '';
-
- var addFeature = function (feature) {
- var feature_li = L.DomUtil.create('li', feature.getClassName() + ' feature'),
- zoom_to = L.DomUtil.create('i', 'feature-zoom_to', feature_li),
- edit = L.DomUtil.create('i', 'show-on-edit feature-edit', feature_li),
- color = L.DomUtil.create('i', 'feature-color', feature_li),
- title = L.DomUtil.create('span', 'feature-title', feature_li),
- symbol = feature._getIconUrl ? L.U.Icon.prototype.formatUrl(feature._getIconUrl(), feature): null;
- zoom_to.title = L._('Bring feature to center');
- edit.title = L._('Edit this feature');
- title.textContent = feature.getDisplayName() || '—';
- color.style.backgroundColor = feature.getOption('color');
- if (symbol) {
- color.style.backgroundImage = 'url(' + symbol + ')';
- }
- L.DomEvent.on(zoom_to, 'click', function (e) {
- e.callback = L.bind(this.view, this);
- this.zoomTo(e);
- }, feature);
- L.DomEvent.on(title, 'click', function (e) {
- e.callback = L.bind(this.view, this)
- this.zoomTo(e);
- }, feature);
- L.DomEvent.on(edit, 'click', function () {
- this.edit();
- }, feature);
- return feature_li;
- };
-
- var append = function (datalayer) {
- var container = L.DomUtil.create('div', datalayer.getHidableClass(), featuresContainer),
- headline = L.DomUtil.create('h5', '', container);
- container.id = 'browse_data_datalayer_' + datalayer.umap_id;
- datalayer.renderToolbox(headline);
- L.DomUtil.add('span', '', headline, datalayer.options.name);
- var ul = L.DomUtil.create('ul', '', container);
- L.DomUtil.classIf(container, 'off', !datalayer.isVisible());
-
- var build = function () {
- ul.innerHTML = '';
- datalayer.eachFeature(function (feature) {
- if ((filterValue && !feature.matchFilter(filterValue, filterKeys)) || feature.properties.isVisible === false) return;
- ul.appendChild(addFeature(feature));
- });
- };
- build();
- datalayer.on('datachanged', build);
- datalayer.map.ui.once('panel:closed', function () {
- datalayer.off('datachanged', build);
- });
- datalayer.map.ui.once('panel:ready', function () {
- datalayer.map.ui.once('panel:ready', function () {
- datalayer.off('datachanged', build);
- });
- });
- };
-
- var appendAll = function () {
- this.options.filter = filterValue = filter.value;
- featuresContainer.innerHTML = '';
- this.eachBrowsableDataLayer(function (datalayer) {
- append(datalayer);
- });
- };
- var resetLayers = function () {
- this.eachBrowsableDataLayer(function (datalayer) {
- datalayer.resetLayer(true);
- });
- }
- L.bind(appendAll, this)();
- L.DomEvent.on(filter, 'input', appendAll, this);
- L.DomEvent.on(filter, 'input', resetLayers, this);
- var link = L.DomUtil.create('li', '');
- L.DomUtil.create('i', 'umap-icon-16 umap-caption', link);
- var label = L.DomUtil.create('span', '', link);
- label.textContent = label.title = L._('About');
- L.DomEvent.on(link, 'click', this.displayCaption, this);
- this.ui.openPanel({data: {html: browserContainer}, actions: [link]});
- },
-
- _openFilter: function () {
- var filterContainer = L.DomUtil.create('div', 'umap-filter-data'),
- title = L.DomUtil.add('h3', 'umap-filter-title', filterContainer, this.options.name),
- propertiesContainer = L.DomUtil.create('div', 'umap-filter-properties', filterContainer),
- advancedFilterKeys = this.getAdvancedFilterKeys();
-
- var advancedFiltersFull = {};
- var filtersAlreadyLoaded = true;
- if (!this.getMap().options.advancedFilters) {
- this.getMap().options.advancedFilters = {};
- filtersAlreadyLoaded = false;
- }
- advancedFilterKeys.forEach(property => {
- advancedFiltersFull[property] = [];
- if (!filtersAlreadyLoaded || !this.getMap().options.advancedFilters[property]) {
- this.getMap().options.advancedFilters[property] = [];
- }
- });
- this.eachDataLayer(function (datalayer) {
- datalayer.eachFeature(function (feature) {
- advancedFilterKeys.forEach(property => {
- if (feature.properties[property]) {
- if (!advancedFiltersFull[property].includes(feature.properties[property])) {
- advancedFiltersFull[property].push(feature.properties[property]);
- }
- }
- });
- });
- });
-
- var addPropertyValue = function (property, value) {
- var property_li = L.DomUtil.create('li', ''),
- filter_check = L.DomUtil.create('input', '', property_li),
- filter_label = L.DomUtil.create('label', '', property_li);
- filter_check.type = 'checkbox';
- filter_check.id = `checkbox_${property}_${value}`;
- filter_check.checked = this.getMap().options.advancedFilters[property] && this.getMap().options.advancedFilters[property].includes(value);
- filter_check.setAttribute('data-property', property);
- filter_check.setAttribute('data-value', value);
- filter_label.htmlFor = `checkbox_${property}_${value}`;
- filter_label.innerHTML = value;
- L.DomEvent.on(filter_check, 'change', function (e) {
- var property = e.srcElement.dataset.property;
- var value = e.srcElement.dataset.value;
- if (e.srcElement.checked) {
- this.getMap().options.advancedFilters[property].push(value);
- } else {
- this.getMap().options.advancedFilters[property].splice(this.getMap().options.advancedFilters[property].indexOf(value), 1);
- }
- L.bind(filterFeatures, this)();
- }, this);
- return property_li
- };
-
- var addProperty = function (property) {
- var container = L.DomUtil.create('div', 'property-container', propertiesContainer),
- headline = L.DomUtil.add('h5', '', container, property);
- var ul = L.DomUtil.create('ul', '', container);
- var orderedValues = advancedFiltersFull[property];
- orderedValues.sort();
- orderedValues.forEach(value => {
- ul.appendChild(L.bind(addPropertyValue, this)(property, value));
- });
- };
-
- var filterFeatures = function () {
- var noResults = true;
- this.eachDataLayer(function (datalayer) {
- datalayer.eachFeature(function (feature) {
- feature.properties.isVisible = true;
- for (const [property, values] of Object.entries(this.map.options.advancedFilters)) {
- if (values.length > 0) {
- if (!feature.properties[property] || !values.includes(feature.properties[property])) {
- feature.properties.isVisible = false;
- }
- }
- }
- if (feature.properties.isVisible) {
- noResults = false;
- if (!this.isLoaded()) this.fetchData();
- this.map.addLayer(feature);
- this.fire('show');
- } else {
- this.map.removeLayer(feature);
- this.fire('hide');
- }
- });
- });
- if (noResults) {
- this.help.show('advancedFiltersNoResults');
- } else {
- this.help.hide();
- }
- };
-
- propertiesContainer.innerHTML = '';
- advancedFilterKeys.forEach(property => {
- L.bind(addProperty, this)(property);
- });
-
- var link = L.DomUtil.create('li', '');
- L.DomUtil.create('i', 'umap-icon-16 umap-caption', link);
- var label = L.DomUtil.create('span', '', link);
- label.textContent = label.title = L._('About');
- L.DomEvent.on(link, 'click', this.displayCaption, this);
- this.ui.openPanel({ data: { html: filterContainer }, actions: [link] });
+ const addFeature = (feature) => {
+ const feature_li = L.DomUtil.create('li', `${feature.getClassName()} feature`)
+ const zoom_to = L.DomUtil.create('i', 'feature-zoom_to', feature_li)
+ const edit = L.DomUtil.create('i', 'show-on-edit feature-edit', feature_li)
+ const color = L.DomUtil.create('i', 'feature-color', feature_li)
+ const title = L.DomUtil.create('span', 'feature-title', feature_li)
+ const symbol = feature._getIconUrl
+ ? L.U.Icon.prototype.formatUrl(feature._getIconUrl(), feature)
+ : null
+ zoom_to.title = L._('Bring feature to center')
+ edit.title = L._('Edit this feature')
+ title.textContent = feature.getDisplayName() || '—'
+ color.style.backgroundColor = feature.getOption('color')
+ if (symbol) {
+ color.style.backgroundImage = `url(${symbol})`
+ }
+ L.DomEvent.on(
+ zoom_to,
+ 'click',
+ function (e) {
+ e.callback = L.bind(this.view, this)
+ this.zoomTo(e)
+ },
+ feature
+ )
+ L.DomEvent.on(
+ title,
+ 'click',
+ function (e) {
+ e.callback = L.bind(this.view, this)
+ this.zoomTo(e)
+ },
+ feature
+ )
+ L.DomEvent.on(
+ edit,
+ 'click',
+ function () {
+ this.edit()
+ },
+ feature
+ )
+ return feature_li
}
-});
+ const append = (datalayer) => {
+ const container = L.DomUtil.create(
+ 'div',
+ datalayer.getHidableClass(),
+ featuresContainer
+ )
+ const headline = L.DomUtil.create('h5', '', container)
+ container.id = `browse_data_datalayer_${datalayer.umap_id}`
+ datalayer.renderToolbox(headline)
+ L.DomUtil.add('span', '', headline, datalayer.options.name)
+ const ul = L.DomUtil.create('ul', '', container)
+ L.DomUtil.classIf(container, 'off', !datalayer.isVisible())
+ const build = () => {
+ ul.innerHTML = ''
+ datalayer.eachFeature((feature) => {
+ if (
+ (filterValue && !feature.matchFilter(filterValue, filterKeys)) ||
+ feature.properties.isVisible === false
+ )
+ return
+ ul.appendChild(addFeature(feature))
+ })
+ }
+ build()
+ datalayer.on('datachanged', build)
+ datalayer.map.ui.once('panel:closed', () => {
+ datalayer.off('datachanged', build)
+ })
+ datalayer.map.ui.once('panel:ready', () => {
+ datalayer.map.ui.once('panel:ready', () => {
+ datalayer.off('datachanged', build)
+ })
+ })
+ }
+ const appendAll = function () {
+ this.options.filter = filterValue = filter.value
+ featuresContainer.innerHTML = ''
+ this.eachBrowsableDataLayer((datalayer) => {
+ append(datalayer)
+ })
+ }
+ const resetLayers = function () {
+ this.eachBrowsableDataLayer((datalayer) => {
+ datalayer.resetLayer(true)
+ })
+ }
+ L.bind(appendAll, this)()
+ L.DomEvent.on(filter, 'input', appendAll, this)
+ L.DomEvent.on(filter, 'input', resetLayers, this)
+ const link = L.DomUtil.create('li', '')
+ L.DomUtil.create('i', 'umap-icon-16 umap-caption', link)
+ const label = L.DomUtil.create('span', '', link)
+ label.textContent = label.title = L._('About')
+ L.DomEvent.on(link, 'click', this.displayCaption, this)
+ this.ui.openPanel({ data: { html: browserContainer }, actions: [link] })
+ },
+
+ _openFilter() {
+ const filterContainer = L.DomUtil.create('div', 'umap-filter-data')
+ const title = L.DomUtil.add(
+ 'h3',
+ 'umap-filter-title',
+ filterContainer,
+ this.options.name
+ )
+ const propertiesContainer = L.DomUtil.create(
+ 'div',
+ 'umap-filter-properties',
+ filterContainer
+ )
+ const advancedFilterKeys = this.getAdvancedFilterKeys()
+
+ const advancedFiltersFull = {}
+ let filtersAlreadyLoaded = true
+ if (!this.getMap().options.advancedFilters) {
+ this.getMap().options.advancedFilters = {}
+ filtersAlreadyLoaded = false
+ }
+ advancedFilterKeys.forEach((property) => {
+ advancedFiltersFull[property] = []
+ if (!filtersAlreadyLoaded || !this.getMap().options.advancedFilters[property]) {
+ this.getMap().options.advancedFilters[property] = []
+ }
+ })
+ this.eachDataLayer((datalayer) => {
+ datalayer.eachFeature(({ properties }) => {
+ advancedFilterKeys.forEach((property) => {
+ if (properties[property]) {
+ if (!advancedFiltersFull[property].includes(properties[property])) {
+ advancedFiltersFull[property].push(properties[property])
+ }
+ }
+ })
+ })
+ })
+
+ const addPropertyValue = function (property, value) {
+ const property_li = L.DomUtil.create('li', '')
+ const filter_check = L.DomUtil.create('input', '', property_li)
+ const filter_label = L.DomUtil.create('label', '', property_li)
+ filter_check.type = 'checkbox'
+ filter_check.id = `checkbox_${property}_${value}`
+ filter_check.checked =
+ this.getMap().options.advancedFilters[property] &&
+ this.getMap().options.advancedFilters[property].includes(value)
+ filter_check.setAttribute('data-property', property)
+ filter_check.setAttribute('data-value', value)
+ filter_label.htmlFor = `checkbox_${property}_${value}`
+ filter_label.innerHTML = value
+ L.DomEvent.on(
+ filter_check,
+ 'change',
+ function ({ srcElement }) {
+ const property = srcElement.dataset.property
+ const value = srcElement.dataset.value
+ if (srcElement.checked) {
+ this.getMap().options.advancedFilters[property].push(value)
+ } else {
+ this.getMap().options.advancedFilters[property].splice(
+ this.getMap().options.advancedFilters[property].indexOf(value),
+ 1
+ )
+ }
+ L.bind(filterFeatures, this)()
+ },
+ this
+ )
+ return property_li
+ }
+
+ const addProperty = function (property) {
+ const container = L.DomUtil.create(
+ 'div',
+ 'property-container',
+ propertiesContainer
+ )
+ const headline = L.DomUtil.add('h5', '', container, property)
+ const ul = L.DomUtil.create('ul', '', container)
+ const orderedValues = advancedFiltersFull[property]
+ orderedValues.sort()
+ orderedValues.forEach((value) => {
+ ul.appendChild(L.bind(addPropertyValue, this)(property, value))
+ })
+ }
+
+ var filterFeatures = function () {
+ let noResults = true
+ this.eachDataLayer((datalayer) => {
+ datalayer.eachFeature(function (feature) {
+ feature.properties.isVisible = true
+ for (const [property, values] of Object.entries(
+ this.map.options.advancedFilters
+ )) {
+ if (values.length > 0) {
+ if (
+ !feature.properties[property] ||
+ !values.includes(feature.properties[property])
+ ) {
+ feature.properties.isVisible = false
+ }
+ }
+ }
+ if (feature.properties.isVisible) {
+ noResults = false
+ if (!this.isLoaded()) this.fetchData()
+ this.map.addLayer(feature)
+ this.fire('show')
+ } else {
+ this.map.removeLayer(feature)
+ this.fire('hide')
+ }
+ })
+ })
+ if (noResults) {
+ this.help.show('advancedFiltersNoResults')
+ } else {
+ this.help.hide()
+ }
+ }
+
+ propertiesContainer.innerHTML = ''
+ advancedFilterKeys.forEach((property) => {
+ L.bind(addProperty, this)(property)
+ })
+
+ const link = L.DomUtil.create('li', '')
+ L.DomUtil.create('i', 'umap-icon-16 umap-caption', link)
+ const label = L.DomUtil.create('span', '', link)
+ label.textContent = label.title = L._('About')
+ L.DomEvent.on(link, 'click', this.displayCaption, this)
+ this.ui.openPanel({ data: { html: filterContainer }, actions: [link] })
+ },
+})
L.U.TileLayerControl = L.Control.extend({
+ options: {
+ position: 'topleft',
+ },
- options: {
- position: 'topleft'
- },
+ initialize(map, options) {
+ this.map = map
+ L.Control.prototype.initialize.call(this, options)
+ },
- initialize: function (map, options) {
- this.map = map;
- L.Control.prototype.initialize.call(this, options);
- },
+ onAdd() {
+ const container = L.DomUtil.create('div', 'leaflet-control-tilelayers umap-control')
- onAdd: function () {
- var container = L.DomUtil.create('div', 'leaflet-control-tilelayers umap-control');
+ const link = L.DomUtil.create('a', '', container)
+ link.href = '#'
+ link.title = L._('Change map background')
- var link = L.DomUtil.create('a', '', container);
- link.href = '#';
- link.title = L._('Change map background');
+ L.DomEvent.on(link, 'click', L.DomEvent.stop)
+ .on(link, 'click', this.openSwitcher, this)
+ .on(link, 'dblclick', L.DomEvent.stopPropagation)
- L.DomEvent
- .on(link, 'click', L.DomEvent.stop)
- .on(link, 'click', this.openSwitcher, this)
- .on(link, 'dblclick', L.DomEvent.stopPropagation);
+ return container
+ },
- return container;
- },
+ openSwitcher(options) {
+ this._tilelayers_container = L.DomUtil.create(
+ 'ul',
+ 'umap-tilelayer-switcher-container'
+ )
+ this.buildList(options)
+ },
- openSwitcher: function (options) {
- this._tilelayers_container = L.DomUtil.create('ul', 'umap-tilelayer-switcher-container');
- this.buildList(options);
- },
+ buildList(options) {
+ this.map.eachTileLayer(function (tilelayer) {
+ if (
+ window.location.protocol === 'https:' &&
+ tilelayer.options.url_template.indexOf('http:') === 0
+ )
+ return
+ this.addTileLayerElement(tilelayer, options)
+ }, this)
+ this.map.ui.openPanel({
+ data: { html: this._tilelayers_container },
+ className: options.className,
+ })
+ },
- buildList: function (options) {
- this.map.eachTileLayer(function (tilelayer) {
- if (window.location.protocol === 'https:' && tilelayer.options.url_template.indexOf('http:') === 0) return;
- this.addTileLayerElement(tilelayer, options);
- }, this);
- this.map.ui.openPanel({data: {html: this._tilelayers_container}, className: options.className});
- },
-
- addTileLayerElement: function (tilelayer, options) {
- var selectedClass = this.map.hasLayer(tilelayer) ? 'selected' : '',
- el = L.DomUtil.create('li', selectedClass, this._tilelayers_container),
- img = L.DomUtil.create('img', '', el),
- name = L.DomUtil.create('div', '', el);
- img.src = L.Util.template(tilelayer.options.url_template, this.map.demoTileInfos);
- name.textContent = tilelayer.options.name;
- L.DomEvent.on(el, 'click', function () {
- this.map.selectTileLayer(tilelayer);
- this.map.ui.closePanel();
- if (options && options.callback) options.callback(tilelayer);
- }, this);
- }
-
-
-});
+ addTileLayerElement(tilelayer, options) {
+ const selectedClass = this.map.hasLayer(tilelayer) ? 'selected' : ''
+ const el = L.DomUtil.create('li', selectedClass, this._tilelayers_container)
+ const img = L.DomUtil.create('img', '', el)
+ const name = L.DomUtil.create('div', '', el)
+ img.src = L.Util.template(tilelayer.options.url_template, this.map.demoTileInfos)
+ name.textContent = tilelayer.options.name
+ L.DomEvent.on(
+ el,
+ 'click',
+ function () {
+ this.map.selectTileLayer(tilelayer)
+ this.map.ui.closePanel()
+ if (options && options.callback) options.callback(tilelayer)
+ },
+ this
+ )
+ },
+})
L.U.AttributionControl = L.Control.Attribution.extend({
+ options: {
+ prefix: '',
+ },
- options: {
- prefix: ''
- },
-
- _update: function () {
- L.Control.Attribution.prototype._update.call(this);
- if (this._map.options.shortCredit) {
- L.DomUtil.add('span', '', this._container, ' — ' + L.Util.toHTML(this._map.options.shortCredit));
- }
- if (this._map.options.captionMenus) {
- var link = L.DomUtil.add('a', '', this._container, ' — ' + L._('About'));
- L.DomEvent
- .on(link, 'click', L.DomEvent.stop)
- .on(link, 'click', this._map.displayCaption, this._map)
- .on(link, 'dblclick', L.DomEvent.stop);
- }
- if (window.top === window.self && this._map.options.captionMenus) {
- // We are not in iframe mode
- var home = L.DomUtil.add('a', '', this._container, ' — ' + L._('Home'));
- home.href = '/';
- }
+ _update() {
+ L.Control.Attribution.prototype._update.call(this)
+ if (this._map.options.shortCredit) {
+ L.DomUtil.add(
+ 'span',
+ '',
+ this._container,
+ ` — ${L.Util.toHTML(this._map.options.shortCredit)}`
+ )
}
-
-});
-
+ if (this._map.options.captionMenus) {
+ const link = L.DomUtil.add('a', '', this._container, ` — ${L._('About')}`)
+ L.DomEvent.on(link, 'click', L.DomEvent.stop)
+ .on(link, 'click', this._map.displayCaption, this._map)
+ .on(link, 'dblclick', L.DomEvent.stop)
+ }
+ if (window.top === window.self && this._map.options.captionMenus) {
+ // We are not in iframe mode
+ const home = L.DomUtil.add('a', '', this._container, ` — ${L._('Home')}`)
+ home.href = '/'
+ }
+ },
+})
L.U.Search = L.PhotonSearch.extend({
+ initialize(map, input, options) {
+ L.PhotonSearch.prototype.initialize.call(this, map, input, options)
+ this.options.url = map.options.urls.search
+ },
- initialize: function (map, input, options) {
- L.PhotonSearch.prototype.initialize.call(this, map, input, options);
- this.options.url = map.options.urls.search;
- },
+ onBlur(e) {
+ // Overrided because we don't want to hide the results on blur.
+ this.fire('blur')
+ },
- onBlur: function (e) {
- // Overrided because we don't want to hide the results on blur.
- this.fire('blur');
- },
+ formatResult(feature, el) {
+ const self = this
+ const tools = L.DomUtil.create('span', 'search-result-tools', el)
+ const zoom = L.DomUtil.create('i', 'feature-zoom_to', tools)
+ const edit = L.DomUtil.create('i', 'feature-edit show-on-edit', tools)
+ zoom.title = L._('Zoom to this place')
+ edit.title = L._('Save this location as new feature')
+ // We need to use "mousedown" because Leaflet.Photon listen to mousedown
+ // on el.
+ L.DomEvent.on(zoom, 'mousedown', (e) => {
+ L.DomEvent.stop(e)
+ self.zoomToFeature(feature)
+ })
+ L.DomEvent.on(edit, 'mousedown', (e) => {
+ L.DomEvent.stop(e)
+ const datalayer = self.map.defaultDataLayer()
+ const layer = datalayer.geojsonToFeatures(feature)
+ layer.isDirty = true
+ layer.edit()
+ })
+ this._formatResult(feature, el)
+ },
- formatResult: function (feature, el) {
- var self = this;
- var tools = L.DomUtil.create('span', 'search-result-tools', el),
- zoom = L.DomUtil.create('i', 'feature-zoom_to', tools),
- edit = L.DomUtil.create('i', 'feature-edit show-on-edit', tools);
- zoom.title = L._('Zoom to this place');
- edit.title = L._('Save this location as new feature');
- // We need to use "mousedown" because Leaflet.Photon listen to mousedown
- // on el.
- L.DomEvent.on(zoom, 'mousedown', function (e) {
- L.DomEvent.stop(e);
- self.zoomToFeature(feature);
- });
- L.DomEvent.on(edit, 'mousedown', function (e) {
- L.DomEvent.stop(e);
- var datalayer = self.map.defaultDataLayer();
- var layer = datalayer.geojsonToFeatures(feature);
- layer.isDirty = true;
- layer.edit();
- });
- this._formatResult(feature, el);
- },
+ zoomToFeature({ geometry }) {
+ const zoom = Math.max(this.map.getZoom(), 16) // Never unzoom.
+ this.map.setView([geometry.coordinates[1], geometry.coordinates[0]], zoom)
+ },
- zoomToFeature: function (feature) {
- var zoom = Math.max(this.map.getZoom(), 16); // Never unzoom.
- this.map.setView([feature.geometry.coordinates[1], feature.geometry.coordinates[0]], zoom);
- },
-
- onSelected: function (feature) {
- this.zoomToFeature(feature);
- this.map.ui.closePanel();
- }
-
-});
+ onSelected(feature) {
+ this.zoomToFeature(feature)
+ this.map.ui.closePanel()
+ },
+})
L.U.SearchControl = L.Control.extend({
+ options: {
+ position: 'topleft',
+ },
- options: {
- position: 'topleft',
- },
+ onAdd(map) {
+ const container = L.DomUtil.create('div', 'leaflet-control-search umap-control')
+ const self = this
- onAdd: function (map) {
- var container = L.DomUtil.create('div', 'leaflet-control-search umap-control'),
- self = this;
+ L.DomEvent.disableClickPropagation(container)
+ const link = L.DomUtil.create('a', '', container)
+ link.href = '#'
+ link.title = L._('Search a place name')
+ L.DomEvent.on(link, 'click', (e) => {
+ L.DomEvent.stop(e)
+ self.openPanel(map)
+ })
+ return container
+ },
- L.DomEvent.disableClickPropagation(container);
- var link = L.DomUtil.create('a', '', container);
- link.href = '#';
- link.title = L._('Search a place name')
- L.DomEvent.on(link, 'click', function (e) {
- L.DomEvent.stop(e);
- self.openPanel(map);
- });
- return container;
- },
-
- openPanel: function (map) {
- var options = {
- limit: 10,
- noResultLabel: L._('No results'),
- }
- if (map.options.photonUrl) options.url = map.options.photonUrl;
- var container = L.DomUtil.create('div', '');
-
- var title = L.DomUtil.create('h3', '', container);
- title.textContent = L._('Search location');
- var input = L.DomUtil.create('input', 'photon-input', container);
- var resultsContainer = L.DomUtil.create('div', 'photon-autocomplete', container);
- this.search = new L.U.Search(map, input, options);
- var id = Math.random();
- this.search.on('ajax:send', function () {
- map.fire('dataloading', {id: id});
- });
- this.search.on('ajax:return', function () {
- map.fire('dataload', {id: id});
- });
- this.search.resultsContainer = resultsContainer;
- map.ui.once('panel:ready', function () {
- input.focus();
- });
- map.ui.openPanel({data: {html: container}});
+ openPanel(map) {
+ const options = {
+ limit: 10,
+ noResultLabel: L._('No results'),
}
+ if (map.options.photonUrl) options.url = map.options.photonUrl
+ const container = L.DomUtil.create('div', '')
-});
-
+ const title = L.DomUtil.create('h3', '', container)
+ title.textContent = L._('Search location')
+ const input = L.DomUtil.create('input', 'photon-input', container)
+ const resultsContainer = L.DomUtil.create('div', 'photon-autocomplete', container)
+ this.search = new L.U.Search(map, input, options)
+ const id = Math.random()
+ this.search.on('ajax:send', () => {
+ map.fire('dataloading', { id })
+ })
+ this.search.on('ajax:return', () => {
+ map.fire('dataload', { id })
+ })
+ this.search.resultsContainer = resultsContainer
+ map.ui.once('panel:ready', () => {
+ input.focus()
+ })
+ map.ui.openPanel({ data: { html: container } })
+ },
+})
L.Control.MiniMap.include({
+ initialize(layer, options) {
+ L.Util.setOptions(this, options)
+ this._layer = this._cloneLayer(layer)
+ },
- initialize: function (layer, options) {
- L.Util.setOptions(this, options);
- this._layer = this._cloneLayer(layer);
- },
-
- onMainMapBaseLayerChange: function (e) {
- var layer = this._cloneLayer(e.layer);
- if (this._miniMap.hasLayer(this._layer)) {
- this._miniMap.removeLayer(this._layer);
- }
- this._layer = layer;
- this._miniMap.addLayer(this._layer);
- },
-
- _cloneLayer: function (layer) {
- return new L.TileLayer(layer._url, L.Util.extend({}, layer.options));
+ onMainMapBaseLayerChange(e) {
+ const layer = this._cloneLayer(e.layer)
+ if (this._miniMap.hasLayer(this._layer)) {
+ this._miniMap.removeLayer(this._layer)
}
+ this._layer = layer
+ this._miniMap.addLayer(this._layer)
+ },
-});
-
+ _cloneLayer({ _url, options }) {
+ return new L.TileLayer(_url, L.Util.extend({}, options))
+ },
+})
L.Control.Loading.include({
+ onAdd(map) {
+ this._container = L.DomUtil.create('div', 'umap-loader', map._controlContainer)
+ map.on('baselayerchange', this._layerAdd, this)
+ this._addMapListeners(map)
+ this._map = map
+ },
- onAdd: function (map) {
- this._container = L.DomUtil.create('div', 'umap-loader', map._controlContainer);
- map.on('baselayerchange', this._layerAdd, this);
- this._addMapListeners(map);
- this._map = map;
- },
-
- _showIndicator: function () {
- L.DomUtil.addClass(this._map._container, 'umap-loading');
- },
-
- _hideIndicator: function() {
- L.DomUtil.removeClass(this._map._container, 'umap-loading');
- }
-
-});
+ _showIndicator() {
+ L.DomUtil.addClass(this._map._container, 'umap-loading')
+ },
+ _hideIndicator() {
+ L.DomUtil.removeClass(this._map._container, 'umap-loading')
+ },
+})
/*
-* Make it dynamic
-*/
+ * Make it dynamic
+ */
L.U.ContextMenu = L.Map.ContextMenu.extend({
+ _createItems(e) {
+ this._map.setContextMenuItems(e)
+ L.Map.ContextMenu.prototype._createItems.call(this)
+ },
- _createItems: function (e) {
- this._map.setContextMenuItems(e);
- L.Map.ContextMenu.prototype._createItems.call(this);
- },
-
- _showAtPoint: function (pt, e) {
- this._items = [];
- this._container.innerHTML = '';
- this._createItems(e);
- L.Map.ContextMenu.prototype._showAtPoint.call(this, pt, e);
- }
-
-});
+ _showAtPoint(pt, e) {
+ this._items = []
+ this._container.innerHTML = ''
+ this._createItems(e)
+ L.Map.ContextMenu.prototype._showAtPoint.call(this, pt, e)
+ },
+})
L.U.IframeExporter = L.Evented.extend({
+ options: {
+ includeFullScreenLink: true,
+ currentView: false,
+ keepCurrentDatalayers: false,
+ viewCurrentFeature: false,
+ },
- options: {
- includeFullScreenLink: true,
- currentView: false,
- keepCurrentDatalayers: false,
- viewCurrentFeature: false
- },
+ queryString: {
+ scaleControl: false,
+ miniMap: false,
+ scrollWheelZoom: false,
+ zoomControl: true,
+ allowEdit: false,
+ moreControl: true,
+ searchControl: null,
+ tilelayersControl: null,
+ embedControl: null,
+ datalayersControl: true,
+ onLoadPanel: 'none',
+ captionBar: false,
+ captionMenus: true,
+ },
- queryString: {
- scaleControl: false,
- miniMap: false,
- scrollWheelZoom: false,
- zoomControl: true,
- allowEdit: false,
- moreControl: true,
- searchControl: null,
- tilelayersControl: null,
- embedControl: null,
- datalayersControl: true,
- onLoadPanel: 'none',
- captionBar: false,
- captionMenus: true
- },
+ dimensions: {
+ width: '100%',
+ height: '300px',
+ },
- dimensions: {
- width: '100%',
- height: '300px'
- },
+ initialize(map) {
+ this.map = map
+ this.baseUrl = L.Util.getBaseUrl()
+ // Use map default, not generic default
+ this.queryString.onLoadPanel = this.map.options.onLoadPanel
+ },
- initialize: function (map) {
- this.map = map;
- this.baseUrl = L.Util.getBaseUrl();
- // Use map default, not generic default
- this.queryString.onLoadPanel = this.map.options.onLoadPanel;
- },
+ getMap() {
+ return this.map
+ },
- getMap: function () {
- return this.map;
- },
-
- build: function () {
- var datalayers = [];
- if (this.options.viewCurrentFeature && this.map.currentFeature) {
- this.queryString.feature = this.map.currentFeature.getSlug();
- }
- if (this.options.keepCurrentDatalayers) {
- this.map.eachDataLayer(function (datalayer) {
- if (datalayer.isVisible() && datalayer.umap_id) {
- datalayers.push(datalayer.umap_id);
- }
- });
- this.queryString.datalayers = datalayers.join(',');
- } else {
- delete this.queryString.datalayers;
- }
- var currentView = this.options.currentView ? window.location.hash : '',
- iframeUrl = this.baseUrl + '?' + L.Util.buildQueryString(this.queryString) + currentView,
- code = '';
- if (this.options.includeFullScreenLink) {
- code += '
' + L._('See full screen') + '
';
- }
- return code;
+ build() {
+ const datalayers = []
+ if (this.options.viewCurrentFeature && this.map.currentFeature) {
+ this.queryString.feature = this.map.currentFeature.getSlug()
}
-
-});
+ if (this.options.keepCurrentDatalayers) {
+ this.map.eachDataLayer((datalayer) => {
+ if (datalayer.isVisible() && datalayer.umap_id) {
+ datalayers.push(datalayer.umap_id)
+ }
+ })
+ this.queryString.datalayers = datalayers.join(',')
+ } else {
+ delete this.queryString.datalayers
+ }
+ const currentView = this.options.currentView ? window.location.hash : ''
+ const iframeUrl = `${this.baseUrl}?${L.Util.buildQueryString(
+ this.queryString
+ )}${currentView}`
+ let code = ``
+ if (this.options.includeFullScreenLink) {
+ code += `${L._('See full screen')}
`
+ }
+ return code
+ },
+})
L.U.Editable = L.Editable.extend({
+ initialize(map, options) {
+ L.Editable.prototype.initialize.call(this, map, options)
+ this.on(
+ 'editable:drawing:start editable:drawing:click editable:drawing:move',
+ this.drawingTooltip
+ )
+ this.on('editable:drawing:end', this.closeTooltip)
+ // Layer for items added by users
+ this.on('editable:drawing:cancel', ({ layer }) => {
+ if (layer._latlngs && layer._latlngs.length < layer.editor.MIN_VERTEX) layer.del()
+ if (layer instanceof L.U.Marker) layer.del()
+ })
+ this.on('editable:drawing:commit', function (e) {
+ e.layer.isDirty = true
+ if (this.map.editedFeature !== e.layer) e.layer.edit(e)
+ })
+ this.on('editable:editing', (e) => {
+ const layer = e.layer
+ layer.isDirty = true
+ if (layer._tooltip && layer.isTooltipOpen()) {
+ layer._tooltip.setLatLng(layer.getCenter())
+ layer._tooltip.update()
+ }
+ })
+ this.on('editable:vertex:ctrlclick', ({ vertex }) => {
+ const index = vertex.getIndex()
+ if (index === 0 || (index === vertex.getLastIndex() && vertex.continue))
+ vertex.continue()
+ })
+ this.on('editable:vertex:altclick', ({ vertex }) => {
+ if (vertex.editor.vertexCanBeDeleted(vertex)) vertex.delete()
+ })
+ this.on('editable:vertex:rawclick', this.onVertexRawClick)
+ },
- initialize: function (map, options) {
- L.Editable.prototype.initialize.call(this, map, options);
- this.on('editable:drawing:start editable:drawing:click editable:drawing:move', this.drawingTooltip);
- this.on('editable:drawing:end', this.closeTooltip);
- // Layer for items added by users
- this.on('editable:drawing:cancel', function (e) {
- if (e.layer._latlngs && e.layer._latlngs.length < e.layer.editor.MIN_VERTEX) e.layer.del();
- if (e.layer instanceof L.U.Marker) e.layer.del();
- });
- this.on('editable:drawing:commit', function (e) {
- e.layer.isDirty = true;
- if (this.map.editedFeature !== e.layer) e.layer.edit(e);
- });
- this.on('editable:editing', function (e) {
- var layer = e.layer;
- layer.isDirty = true;
- if (layer._tooltip && layer.isTooltipOpen()) {
- layer._tooltip.setLatLng(layer.getCenter());
- layer._tooltip.update();
- }
- });
- this.on('editable:vertex:ctrlclick', function (e) {
- var index = e.vertex.getIndex();
- if (index === 0 || index === e.vertex.getLastIndex() && e.vertex.continue) e.vertex.continue();
- });
- this.on('editable:vertex:altclick', function (e) {
- if (e.vertex.editor.vertexCanBeDeleted(e.vertex)) e.vertex.delete();
- });
- this.on('editable:vertex:rawclick', this.onVertexRawClick);
- },
+ createPolyline(latlngs) {
+ return new L.U.Polyline(this.map, latlngs)
+ },
- createPolyline: function (latlngs) {
- return new L.U.Polyline(this.map, latlngs);
- },
+ createPolygon(latlngs) {
+ const polygon = new L.U.Polygon(this.map, latlngs)
+ return polygon
+ },
- createPolygon: function (latlngs) {
- var polygon = new L.U.Polygon(this.map, latlngs);
- return polygon;
- },
+ createMarker(latlng) {
+ return new L.U.Marker(this.map, latlng)
+ },
- createMarker: function (latlng) {
- return new L.U.Marker(this.map, latlng);
- },
+ connectCreatedToMap(layer) {
+ // Overrided from Leaflet.Editable
+ const datalayer = this.map.defaultDataLayer()
+ datalayer.addLayer(layer)
+ layer.isDirty = true
+ return layer
+ },
- connectCreatedToMap: function (layer) {
- // Overrided from Leaflet.Editable
- var datalayer = this.map.defaultDataLayer();
- datalayer.addLayer(layer);
- layer.isDirty = true;
- return layer;
- },
-
- drawingTooltip: function (e) {
- if (e.layer instanceof L.Marker && e.type != "editable:drawing:move") {
- this.map.ui.tooltip({content: L._('Click to add a marker')});
- }
- if (!(e.layer instanceof L.Polyline)) {
- // only continue with Polylines and Polygons
- return;
- }
-
- var content;
- var measure;
- if (e.layer.editor._drawnLatLngs) {
- // when drawing (a Polyline or Polygon)
- if (!e.layer.editor._drawnLatLngs.length) {
- // when drawing first point
- if (e.layer instanceof L.Polygon){
- content = L._('Click to start drawing a polygon');
- } else if (e.layer instanceof L.Polyline) {
- content = L._('Click to start drawing a line');
- }
- } else {
- var tmpLatLngs = e.layer.editor._drawnLatLngs.slice();
- tmpLatLngs.push(e.latlng);
- measure = e.layer.getMeasure(tmpLatLngs);
-
- if (e.layer.editor._drawnLatLngs.length < e.layer.editor.MIN_VERTEX) {
- // when drawing second point
- content = L._('Click to continue drawing');
- } else {
- // when drawing third point (or more)
- content = L._('Click last point to finish shape');
- }
- }
- } else {
- // when moving an existing point
- measure = e.layer.getMeasure();
- }
- if (measure){
- if (e.layer instanceof L.Polygon){
- content += L._(' (area: {measure})', { measure: measure });
- } else if (e.layer instanceof L.Polyline) {
- content += L._(' (length: {measure})', { measure: measure });
- }
- }
- if (content) {
- this.map.ui.tooltip({content: content});
- }
- },
-
- closeTooltip: function () {
- this.map.ui.closeTooltip();
- },
-
- onVertexRawClick: function (e) {
- e.layer.onVertexRawClick(e);
- L.DomEvent.stop(e);
- e.cancel();
+ drawingTooltip({ layer, type, latlng }) {
+ if (layer instanceof L.Marker && type != 'editable:drawing:move') {
+ this.map.ui.tooltip({ content: L._('Click to add a marker') })
+ }
+ if (!(layer instanceof L.Polyline)) {
+ // only continue with Polylines and Polygons
+ return
}
-});
+ let content
+ let measure
+ if (layer.editor._drawnLatLngs) {
+ // when drawing (a Polyline or Polygon)
+ if (!layer.editor._drawnLatLngs.length) {
+ // when drawing first point
+ if (layer instanceof L.Polygon) {
+ content = L._('Click to start drawing a polygon')
+ } else if (layer instanceof L.Polyline) {
+ content = L._('Click to start drawing a line')
+ }
+ } else {
+ const tmpLatLngs = layer.editor._drawnLatLngs.slice()
+ tmpLatLngs.push(latlng)
+ measure = layer.getMeasure(tmpLatLngs)
+
+ if (layer.editor._drawnLatLngs.length < layer.editor.MIN_VERTEX) {
+ // when drawing second point
+ content = L._('Click to continue drawing')
+ } else {
+ // when drawing third point (or more)
+ content = L._('Click last point to finish shape')
+ }
+ }
+ } else {
+ // when moving an existing point
+ measure = layer.getMeasure()
+ }
+ if (measure) {
+ if (layer instanceof L.Polygon) {
+ content += L._(' (area: {measure})', { measure })
+ } else if (layer instanceof L.Polyline) {
+ content += L._(' (length: {measure})', { measure })
+ }
+ }
+ if (content) {
+ this.map.ui.tooltip({ content })
+ }
+ },
+
+ closeTooltip() {
+ this.map.ui.closeTooltip()
+ },
+
+ onVertexRawClick(e) {
+ e.layer.onVertexRawClick(e)
+ L.DomEvent.stop(e)
+ e.cancel()
+ },
+})
diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js
index 614fce48..31705b30 100644
--- a/umap/static/umap/js/umap.core.js
+++ b/umap/static/umap/js/umap.core.js
@@ -1,560 +1,622 @@
/* Poor man pub/sub handler, enough for now */
-L.UmapSingleton = L.Evented.extend({});
-L.U = new L.UmapSingleton();
-L.U.Map = L.Map.extend({});
+L.UmapSingleton = L.Evented.extend({})
+L.U = new L.UmapSingleton()
+L.U.Map = L.Map.extend({})
/*
-* Utils
-*/
-L.Util.queryString = function (name, fallback) {
- var decode = function (s) { return decodeURIComponent(s.replace(/\+/g, ' ')); };
- var qs = window.location.search.slice(1).split('&'), qa = {};
- for (var i in qs) {
- var key = qs[i].split('=');
- if (!key) continue;
- qa[decode(key[0])] = key[1] ? decode(key[1]) : 1;
+ * Utils
+ */
+L.Util.queryString = (name, fallback) => {
+ const decode = (s) => decodeURIComponent(s.replace(/\+/g, ' '))
+ const qs = window.location.search.slice(1).split('&')
+ const qa = {}
+ for (const i in qs) {
+ const key = qs[i].split('=')
+ if (!key) continue
+ qa[decode(key[0])] = key[1] ? decode(key[1]) : 1
+ }
+ return qa[name] || fallback
+}
+
+L.Util.booleanFromQueryString = (name) => {
+ const value = L.Util.queryString(name)
+ return value === '1' || value === 'true'
+}
+
+L.Util.setFromQueryString = (options, name) => {
+ const value = L.Util.queryString(name)
+ if (typeof value !== 'undefined') options[name] = value
+}
+
+L.Util.setBooleanFromQueryString = (options, name) => {
+ const value = L.Util.queryString(name)
+ if (typeof value !== 'undefined') options[name] = value == '1' || value == 'true'
+}
+L.Util.setNullableBooleanFromQueryString = (options, name) => {
+ let value = L.Util.queryString(name)
+ if (typeof value !== 'undefined') {
+ if (value === 'null') value = null
+ else if (value === '0' || value === 'false') value = false
+ else value = true
+ options[name] = value
+ }
+}
+L.Util.escapeHTML = (s) => {
+ s = s ? s.toString() : ''
+ return s.replace(/ {
+ if (!r) return ''
+ let ii
+
+ // detect newline format
+ const newline = r.includes('\r\n') ? '\r\n' : r.includes('\n') ? '\n' : ''
+
+ // Escape tags
+ r = r.replace(/$1')
+ r = r.replace(/^## (.*)/gm, '$1
')
+ r = r.replace(/^# (.*)/gm, '$1
')
+ r = r.replace(/^---/gm, '
')
+
+ // bold, italics
+ r = r.replace(/\*\*(.*?)\*\*/g, '$1')
+ r = r.replace(/\*(.*?)\*/g, '$1')
+
+ // unordered lists
+ r = r.replace(/^\*\* (.*)/gm, '')
+ r = r.replace(/^\* (.*)/gm, '')
+ for (ii = 0; ii < 3; ii++)
+ r = r.replace(new RegExp(`${newline}`, 'g'), newline)
+
+ // links
+ r = r.replace(/(\[\[http)/g, '[[h_t_t_p') // Escape for avoiding clash between [[http://xxx]] and http://xxx
+ r = r.replace(/({{http)/g, '{{h_t_t_p')
+ r = r.replace(/(=http)/g, '=h_t_t_p') // http://xxx as query string, see https://github.com/umap-project/umap/issues/607
+ r = r.replace(/(https?:[^ \<)\n]*)/g, '$1')
+ r = r.replace(/\[\[(h_t_t_ps?:[^\]|]*?)\]\]/g, '$1')
+ r = r.replace(
+ /\[\[(h_t_t_ps?:[^|]*?)\|(.*?)\]\]/g,
+ '$2'
+ )
+ r = r.replace(/\[\[([^\]|]*?)\]\]/g, '$1')
+ r = r.replace(/\[\[([^|]*?)\|(.*?)\]\]/g, '$2')
+
+ // iframe
+ r = r.replace(
+ /{{{(h_t_t_ps?[^ |{]*)}}}/g,
+ ''
+ )
+ r = r.replace(
+ /{{{(h_t_t_ps?[^ |{]*)\|(\d*)(px)?}}}/g,
+ ''
+ )
+ r = r.replace(
+ /{{{(h_t_t_ps?[^ |{]*)\|(\d*)(px)?\*(\d*)(px)?}}}/g,
+ ''
+ )
+
+ // images
+ r = r.replace(/{{([^\]|]*?)}}/g, '
')
+ r = r.replace(/{{([^|]*?)\|(\d*?)}}/g, '
')
+
+ //Unescape http
+ r = r.replace(/(h_t_t_p)/g, 'http')
+
+ // Preserver line breaks
+ if (newline) r = r.replace(new RegExp(`${newline}(?=[^]+)`, 'g'), `
${newline}`)
+
+ return r
+}
+L.Util.isObject = (what) => typeof what === 'object' && what !== null
+L.Util.CopyJSON = (geojson) => JSON.parse(JSON.stringify(geojson))
+L.Util.detectFileType = ({ name, type }) => {
+ const filename = name ? escape(name.toLowerCase()) : ''
+ function ext(_) {
+ return filename.includes(_)
+ }
+ if (type === 'application/vnd.google-earth.kml+xml' || ext('.kml')) {
+ return 'kml'
+ }
+ if (ext('.gpx')) return 'gpx'
+ if (ext('.geojson') || ext('.json')) return 'geojson'
+ if (type === 'text/csv' || ext('.csv') || ext('.tsv') || ext('.dsv')) {
+ return 'csv'
+ }
+ if (ext('.xml') || ext('.osm')) return 'osm'
+ if (ext('.umap')) return 'umap'
+}
+
+L.Util.usableOption = (options, option) =>
+ options[option] !== undefined && options[option] !== ''
+
+L.Util.greedyTemplate = (str, data, ignore) => {
+ function getValue(data, path) {
+ let value = data
+ for (let i = 0; i < path.length; i++) {
+ value = value[path[i]]
+ if (value === undefined) break
}
- return qa[name] || fallback;
-};
+ return value
+ }
-L.Util.booleanFromQueryString = function (name) {
- var value = L.Util.queryString(name);
- return value === '1' || value === 'true';
-};
-
-L.Util.setFromQueryString = function (options, name) {
- var value = L.Util.queryString(name);
- if (typeof value !== 'undefined') options[name] = value;
-};
-
-L.Util.setBooleanFromQueryString = function (options, name) {
- var value = L.Util.queryString(name);
- if (typeof value !== 'undefined') options[name] = value == '1' || value == 'true';
-};
-L.Util.setNullableBooleanFromQueryString = function (options, name) {
- var value = L.Util.queryString(name);
- if (typeof value !== 'undefined') {
- if (value === 'null') value = null;
- else if (value === '0' || value === 'false') value = false;
- else value = true;
- options[name] = value;
+ return str.replace(
+ /\{ *([\w_\:\.\|]+)(?:\|("[^"]*"))? *\}/g,
+ (str, key, staticFallback) => {
+ const vars = key.split('|')
+ let value
+ let path
+ if (staticFallback !== undefined) {
+ vars.push(staticFallback)
+ }
+ for (let i = 0; i < vars.length; i++) {
+ path = vars[i]
+ if (path.startsWith('"') && path.endsWith('"'))
+ value = path.substring(1, path.length - 1) // static default value.
+ else value = getValue(data, path.split('.'))
+ if (value !== undefined) break
+ }
+ if (value === undefined) {
+ if (ignore) value = str
+ else value = ''
+ }
+ return value
}
-};
-L.Util.escapeHTML = function (s) {
- s = s? s.toString() : '';
- return s.replace(/ {
+ const sortKeys = (sortKey || 'name').split(',')
- // Escape tags
- r = r.replace(/$1');
- r = r.replace(/^## (.*)/gm, '$1
');
- r = r.replace(/^# (.*)/gm, '$1
');
- r = r.replace(/^---/gm, '
');
-
- // bold, italics
- r = r.replace(/\*\*(.*?)\*\*/g, '$1');
- r = r.replace(/\*(.*?)\*/g, '$1');
-
- // unordered lists
- r = r.replace(/^\*\* (.*)/gm, '');
- r = r.replace(/^\* (.*)/gm, '');
- for (ii = 0; ii < 3; ii++) r = r.replace(new RegExp('
' + newline + '', 'g'), newline);
-
- // links
- r = r.replace(/(\[\[http)/g, '[[h_t_t_p'); // Escape for avoiding clash between [[http://xxx]] and http://xxx
- r = r.replace(/({{http)/g, '{{h_t_t_p');
- r = r.replace(/(=http)/g, '=h_t_t_p'); // http://xxx as query string, see https://github.com/umap-project/umap/issues/607
- r = r.replace(/(https?:[^ \<)\n]*)/g, '$1');
- r = r.replace(/\[\[(h_t_t_ps?:[^\]|]*?)\]\]/g, '$1');
- r = r.replace(/\[\[(h_t_t_ps?:[^|]*?)\|(.*?)\]\]/g, '$2');
- r = r.replace(/\[\[([^\]|]*?)\]\]/g, '$1');
- r = r.replace(/\[\[([^|]*?)\|(.*?)\]\]/g, '$2');
-
- // iframe
- r = r.replace(/{{{(h_t_t_ps?[^ |{]*)}}}/g, '');
- r = r.replace(/{{{(h_t_t_ps?[^ |{]*)\|(\d*)(px)?}}}/g, '');
- r = r.replace(/{{{(h_t_t_ps?[^ |{]*)\|(\d*)(px)?\*(\d*)(px)?}}}/g, '');
-
- // images
- r = r.replace(/{{([^\]|]*?)}}/g, '
');
- r = r.replace(/{{([^|]*?)\|(\d*?)}}/g, '
');
-
- //Unescape http
- r = r.replace(/(h_t_t_p)/g, 'http');
-
- // Preserver line breaks
- if (newline) r = r.replace(new RegExp(newline + '(?=[^]+)', 'g'), '
' + newline);
-
- return r;
-};
-L.Util.isObject = function (what) {
- return typeof what === 'object' && what !== null;
-};
-L.Util.CopyJSON = function (geojson) {
- return JSON.parse(JSON.stringify(geojson));
-};
-L.Util.detectFileType = function (f) {
- var filename = f.name ? escape(f.name.toLowerCase()) : '';
- function ext(_) {
- return filename.indexOf(_) !== -1;
+ const sort = (a, b, i) => {
+ const sortKey = sortKeys[i]
+ let score
+ const valA = a.properties[sortKey] || ''
+ const valB = b.properties[sortKey] || ''
+ if (!valA) {
+ score = -1
+ } else if (!valB) {
+ score = 1
+ } else {
+ score = valA.toString().toLowerCase().localeCompare(valB.toString().toLowerCase())
}
- if (f.type === 'application/vnd.google-earth.kml+xml' || ext('.kml')) {
- return 'kml';
+ if (score === 0 && sortKeys[i + 1]) return sort(a, b, i + 1)
+ return score
+ }
+
+ features.sort((a, b) => {
+ if (!a.properties || !b.properties) {
+ return 0
}
- if (ext('.gpx')) return 'gpx';
- if (ext('.geojson') || ext('.json')) return 'geojson';
- if (f.type === 'text/csv' || ext('.csv') || ext('.tsv') || ext('.dsv')) {
- return 'csv';
+ return sort(a, b, 0)
+ })
+
+ return features
+}
+
+L.Util.flattenCoordinates = (coords) => {
+ while (coords[0] && typeof coords[0][0] !== 'number') coords = coords[0]
+ return coords
+}
+
+L.Util.buildQueryString = (params) => {
+ const query_string = []
+ for (const key in params) {
+ query_string.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
+ }
+ return query_string.join('&')
+}
+
+L.Util.getBaseUrl = () => `//${window.location.host}${window.location.pathname}`
+
+L.DomUtil.add = (tagName, className, container, content) => {
+ const el = L.DomUtil.create(tagName, className, container)
+ if (content) {
+ if (content.nodeType && content.nodeType === 1) {
+ el.appendChild(content)
+ } else {
+ el.innerHTML = content
}
- if (ext('.xml') || ext('.osm')) return 'osm';
- if (ext('.umap')) return 'umap';
-};
+ }
+ return el
+}
-L.Util.usableOption = function (options, option) {
- return options[option] !== undefined && options[option] !== '';
-};
-
-L.Util.greedyTemplate = function (str, data, ignore) {
- function getValue(data, path) {
- var value = data
- for (var i = 0; i < path.length; i++) {
- value = value[path[i]]
- if (value === undefined) break;
- }
- return value;
+L.DomUtil.createFieldset = (container, legend, options = {}) => {
+ const fieldset = L.DomUtil.create('div', 'fieldset toggle', container)
+ const legendEl = L.DomUtil.add('h5', 'legend style_options_toggle', fieldset, legend)
+ const fieldsEl = L.DomUtil.add('div', 'fields with-transition', fieldset)
+ L.DomEvent.on(legendEl, 'click', function () {
+ if (L.DomUtil.hasClass(fieldset, 'on')) {
+ L.DomUtil.removeClass(fieldset, 'on')
+ } else {
+ L.DomUtil.addClass(fieldset, 'on')
+ if (options.callback) options.callback.call(options.context || this)
}
+ })
+ return fieldsEl
+}
- return str.replace(/\{ *([\w_\:\.\|]+)(?:\|("[^"]*"))? *\}/g, function (str, key, staticFallback) {
- var vars = key.split('|'), value, path;
- if (staticFallback !== undefined) {
- vars.push(staticFallback);
- }
- for (var i = 0; i < vars.length; i++) {
- path = vars[i];
- if (path.startsWith('"') && path.endsWith('"')) value = path.substring(1, path.length -1); // static default value.
- else value = getValue(data, path.split('.'));
- if (value !== undefined) break;
- }
- if (value === undefined) {
- if (ignore) value = str;
- else value = '';
- }
- return value;
- });
-};
+L.DomUtil.classIf = (el, className, bool) => {
+ if (bool) L.DomUtil.addClass(el, className)
+ else L.DomUtil.removeClass(el, className)
+}
-L.Util.sortFeatures = function (features, sortKey) {
- var sortKeys = (sortKey || 'name').split(',');
+L.DomUtil.element = (what, attrs, parent) => {
+ const el = document.createElement(what)
+ for (const attr in attrs) {
+ el[attr] = attrs[attr]
+ }
+ if (typeof parent !== 'undefined') {
+ parent.appendChild(el)
+ }
+ return el
+}
- var sort = function (a, b, i) {
- var sortKey = sortKeys[i], score,
- valA = a.properties[sortKey] || '',
- valB = b.properties[sortKey] || '';
- if (!valA) {
- score = -1;
- } else if (!valB) {
- score = 1;
- } else {
- score = valA.toString().toLowerCase().localeCompare(valB.toString().toLowerCase());
- }
- if (score === 0 && sortKeys[i + 1]) return sort(a, b, i + 1);
- return score;
- };
+L.DomUtil.before = (target, el) => {
+ target.parentNode.insertBefore(el, target)
+ return el
+}
- features.sort(function (a, b) {
- if (!a.properties || !b.properties) {
- return 0;
- }
- return sort(a, b, 0);
- });
+L.DomUtil.after = ({ parentNode, nextSibling }, el) => {
+ parentNode.insertBefore(el, nextSibling)
+ return el
+}
+L.DomUtil.RGBRegex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/
- return features;
-};
+L.DomUtil.TextColorFromBackgroundColor = (el) => {
+ let out = '#000000'
+ if (!window.getComputedStyle) {
+ return out
+ }
+ let rgb = window.getComputedStyle(el).getPropertyValue('background-color')
+ rgb = L.DomUtil.RGBRegex.exec(rgb)
+ if (!rgb || rgb.length !== 4) {
+ return out
+ }
+ rgb = parseInt(rgb[1], 10) + parseInt(rgb[2], 10) + parseInt(rgb[3], 10)
+ if (rgb < (255 * 3) / 2) {
+ out = '#ffffff'
+ }
+ return out
+}
+L.DomEvent.once = (el, types, fn, context) => {
+ // cf https://github.com/Leaflet/Leaflet/pull/3528#issuecomment-134551575
-L.Util.flattenCoordinates = function (coords) {
- while (coords[0] && typeof coords[0][0] !== 'number') coords = coords[0];
- return coords;
-};
-
-
-L.Util.buildQueryString = function (params) {
- var query_string = [];
- for (var key in params) {
- query_string.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
+ if (typeof types === 'object') {
+ for (const type in types) {
+ L.DomEvent.once(el, type, types[type], fn)
}
- return query_string.join('&');
-};
-
-L.Util.getBaseUrl = function () {
- return '//' + window.location.host + window.location.pathname;
-};
-
-L.DomUtil.add = function (tagName, className, container, content) {
- var el = L.DomUtil.create(tagName, className, container);
- if (content) {
- if (content.nodeType && content.nodeType === 1) {
- el.appendChild(content);
- }
- else {
- el.innerHTML = content;
- }
- }
- return el;
-};
-
-L.DomUtil.createFieldset = function (container, legend, options) {
- options = options || {};
- var fieldset = L.DomUtil.create('div', 'fieldset toggle', container);
- var legendEl = L.DomUtil.add('h5', 'legend style_options_toggle', fieldset, legend);
- var fieldsEl = L.DomUtil.add('div', 'fields with-transition', fieldset);
- L.DomEvent.on(legendEl, 'click', function () {
- if (L.DomUtil.hasClass(fieldset, 'on')) {
- L.DomUtil.removeClass(fieldset, 'on');
- } else {
- L.DomUtil.addClass(fieldset, 'on');
- if (options.callback) options.callback.call(options.context || this);
- }
- });
- return fieldsEl;
-};
-
-L.DomUtil.classIf = function (el, className, bool) {
- if (bool) L.DomUtil.addClass(el, className);
- else L.DomUtil.removeClass(el, className);
-};
-
-
-L.DomUtil.element = function (what, attrs, parent) {
- var el = document.createElement(what);
- for (var attr in attrs) {
- el[attr] = attrs[attr];
- }
- if (typeof parent !== 'undefined') {
- parent.appendChild(el);
- }
- return el;
-};
-
-
-L.DomUtil.before = function (target, el) {
- target.parentNode.insertBefore(el, target);
- return el;
-};
-
-L.DomUtil.after = function (target, el)
-{
- target.parentNode.insertBefore(el, target.nextSibling);
- return el;
-};
-
-L.DomUtil.RGBRegex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/;
-
-L.DomUtil.TextColorFromBackgroundColor = function (el) {
- var out = '#000000';
- if (!window.getComputedStyle) {return out;}
- var rgb = window.getComputedStyle(el).getPropertyValue('background-color');
- rgb = L.DomUtil.RGBRegex.exec(rgb);
- if (!rgb || rgb.length !== 4) {return out;}
- rgb = parseInt(rgb[1], 10) + parseInt(rgb[2], 10) + parseInt(rgb[3], 10);
- if (rgb < (255 * 3 / 2)) {
- out = '#ffffff';
- }
- return out;
-};
-L.DomEvent.once = function (el, types, fn, context) {
- // cf https://github.com/Leaflet/Leaflet/pull/3528#issuecomment-134551575
-
- if (typeof types === 'object') {
- for (var type in types) {
- L.DomEvent.once(el, type, types[type], fn);
- }
- return L.DomEvent;
- }
-
- var handler = L.bind(function () {
- L.DomEvent
- .off(el, types, fn, context)
- .off(el, types, handler, context);
- }, L.DomEvent);
-
- // add a listener that's executed once and removed after that
return L.DomEvent
- .on(el, types, fn, context)
- .on(el, types, handler, context);
-};
+ }
+ const handler = L.bind(() => {
+ L.DomEvent.off(el, types, fn, context).off(el, types, handler, context)
+ }, L.DomEvent)
+
+ // add a listener that's executed once and removed after that
+ return L.DomEvent.on(el, types, fn, context).on(el, types, handler, context)
+}
/*
-* Global events
-*/
+ * Global events
+ */
L.U.Keys = {
- LEFT: 37,
- UP: 38,
- RIGHT: 39,
- DOWN: 40,
- TAB: 9,
- ENTER: 13,
- ESC: 27,
- APPLE: 91,
- SHIFT: 16,
- ALT: 17,
- CTRL: 18,
- E: 69,
- F: 70,
- H: 72,
- I: 73,
- L: 76,
- M: 77,
- P: 80,
- S: 83,
- Z: 90
-};
-
+ LEFT: 37,
+ UP: 38,
+ RIGHT: 39,
+ DOWN: 40,
+ TAB: 9,
+ ENTER: 13,
+ ESC: 27,
+ APPLE: 91,
+ SHIFT: 16,
+ ALT: 17,
+ CTRL: 18,
+ E: 69,
+ F: 70,
+ H: 72,
+ I: 73,
+ L: 76,
+ M: 77,
+ P: 80,
+ S: 83,
+ Z: 90,
+}
L.U.Help = L.Class.extend({
+ initialize(map) {
+ this.map = map
+ this.box = L.DomUtil.create(
+ 'div',
+ 'umap-help-box with-transition dark',
+ document.body
+ )
+ const closeLink = L.DomUtil.create('a', 'umap-close-link', this.box)
+ closeLink.href = '#'
+ L.DomUtil.add('i', 'umap-close-icon', closeLink)
+ const label = L.DomUtil.create('span', '', closeLink)
+ label.title = label.textContent = L._('Close')
+ this.content = L.DomUtil.create('div', 'umap-help-content', this.box)
+ L.DomEvent.on(closeLink, 'click', this.hide, this)
+ },
- initialize: function (map) {
- this.map = map;
- this.box = L.DomUtil.create('div', 'umap-help-box with-transition dark', document.body);
- var closeLink = L.DomUtil.create('a', 'umap-close-link', this.box);
- closeLink.href = '#';
- L.DomUtil.add('i', 'umap-close-icon', closeLink);
- var label = L.DomUtil.create('span', '', closeLink);
- label.title = label.textContent = L._('Close');
- this.content = L.DomUtil.create('div', 'umap-help-content', this.box);
- L.DomEvent.on(closeLink, 'click', this.hide, this);
- },
+ onKeyDown({ keyCode }) {
+ const key = keyCode
+ const ESC = 27
+ if (key === ESC) {
+ this.hide()
+ }
+ },
- onKeyDown: function (e) {
- var key = e.keyCode,
- ESC = 27;
- if (key === ESC) {
- this.hide();
- }
- },
+ show(...args) {
+ this.content.innerHTML = ''
+ for (let i = 0, name; i < args.length; i++) {
+ name = args[i]
+ L.DomUtil.add('div', 'umap-help-entry', this.content, this.resolve(name))
+ }
+ L.DomUtil.addClass(document.body, 'umap-help-on')
+ },
- show: function () {
- this.content.innerHTML = '';
- for (var i = 0, name; i < arguments.length; i++) {
- name = arguments[i];
- L.DomUtil.add('div', 'umap-help-entry', this.content, this.resolve(name));
- }
- L.DomUtil.addClass(document.body, 'umap-help-on');
- },
+ hide() {
+ L.DomUtil.removeClass(document.body, 'umap-help-on')
+ },
- hide: function () {
- L.DomUtil.removeClass(document.body, 'umap-help-on');
- },
+ visible() {
+ return L.DomUtil.hasClass(document.body, 'umap-help-on')
+ },
- visible: function () {
- return L.DomUtil.hasClass(document.body, 'umap-help-on')
- },
+ resolve(name) {
+ return typeof this[name] === 'function' ? this[name]() : this[name]
+ },
- resolve: function (name) {
- return typeof this[name] === 'function' ? this[name]() : this[name];
- },
+ button(container, entries) {
+ const helpButton = L.DomUtil.create('a', 'umap-help-button', container)
+ helpButton.href = '#'
+ if (entries) {
+ L.DomEvent.on(helpButton, 'click', L.DomEvent.stop).on(
+ helpButton,
+ 'click',
+ function (e) {
+ const args = typeof entries === 'string' ? [entries] : entries
+ this.show(...args)
+ },
+ this
+ )
+ }
+ return helpButton
+ },
- button: function (container, entries) {
- var helpButton = L.DomUtil.create('a', 'umap-help-button', container);
- helpButton.href = '#';
- if (entries) {
- L.DomEvent
- .on(helpButton, 'click', L.DomEvent.stop)
- .on(helpButton, 'click', function (e) {
- var args = typeof entries === 'string' ? [entries] : entries;
- this.show.apply(this, args);
- }, this);
- }
- return helpButton;
- },
+ edit() {
+ const container = L.DomUtil.create('div', '')
+ const self = this
+ const title = L.DomUtil.create('h3', '', container)
+ const actionsContainer = L.DomUtil.create('ul', 'umap-edit-actions', container)
+ const addAction = (action) => {
+ const actionContainer = L.DomUtil.add('li', '', actionsContainer)
+ L.DomUtil.add('i', action.options.className, actionContainer),
+ L.DomUtil.add('span', '', actionContainer, action.options.tooltip)
+ L.DomEvent.on(actionContainer, 'click', action.addHooks, action)
+ L.DomEvent.on(actionContainer, 'click', self.hide, self)
+ }
+ title.textContent = L._('Where do we go from here?')
+ for (const id in this.map.helpMenuActions) {
+ addAction(this.map.helpMenuActions[id])
+ }
+ return container
+ },
- edit: function () {
- var container = L.DomUtil.create('div', ''),
- self = this,
- title = L.DomUtil.create('h3', '', container),
- actionsContainer = L.DomUtil.create('ul', 'umap-edit-actions', container);
- var addAction = function (action) {
- var actionContainer = L.DomUtil.add('li', '', actionsContainer);
- L.DomUtil.add('i', action.options.className, actionContainer),
- L.DomUtil.add('span', '', actionContainer, action.options.tooltip);
- L.DomEvent.on(actionContainer, 'click', action.addHooks, action);
- L.DomEvent.on(actionContainer, 'click', self.hide, self);
- };
- title.textContent = L._('Where do we go from here?');
- for (var id in this.map.helpMenuActions) {
- addAction(this.map.helpMenuActions[id]);
- }
- return container;
- },
+ importFormats() {
+ const container = L.DomUtil.create('div')
+ L.DomUtil.add('h3', '', container, 'GeojSON')
+ L.DomUtil.add('p', '', container, L._('All properties are imported.'))
+ L.DomUtil.add('h3', '', container, 'GPX')
+ L.DomUtil.add('p', '', container, `${L._('Properties imported:')}name, desc`)
+ L.DomUtil.add('h3', '', container, 'KML')
+ L.DomUtil.add('p', '', container, `${L._('Properties imported:')}name, description`)
+ L.DomUtil.add('h3', '', container, 'CSV')
+ L.DomUtil.add(
+ 'p',
+ '',
+ container,
+ L._(
+ 'Comma, tab or semi-colon separated values. SRS WGS84 is implied. Only Point geometries are imported. The import will look at the column headers for any mention of «lat» and «lon» at the begining of the header, case insensitive. All other column are imported as properties.'
+ )
+ )
+ L.DomUtil.add('h3', '', container, 'uMap')
+ L.DomUtil.add(
+ 'p',
+ '',
+ container,
+ L._('Imports all umap data, including layers and settings.')
+ )
+ return container
+ },
- importFormats: function () {
- var container = L.DomUtil.create('div');
- L.DomUtil.add('h3', '', container, 'GeojSON');
- L.DomUtil.add('p', '', container, L._('All properties are imported.'));
- L.DomUtil.add('h3', '', container, 'GPX');
- L.DomUtil.add('p', '', container, L._('Properties imported:') + 'name, desc');
- L.DomUtil.add('h3', '', container, 'KML');
- L.DomUtil.add('p', '', container, L._('Properties imported:') + 'name, description');
- L.DomUtil.add('h3', '', container, 'CSV');
- L.DomUtil.add('p', '', container, L._('Comma, tab or semi-colon separated values. SRS WGS84 is implied. Only Point geometries are imported. The import will look at the column headers for any mention of «lat» and «lon» at the begining of the header, case insensitive. All other column are imported as properties.'));
- L.DomUtil.add('h3', '', container, 'uMap');
- L.DomUtil.add('p', '', container, L._('Imports all umap data, including layers and settings.'));
- return container;
- },
+ textFormatting() {
+ const container = L.DomUtil.create('div')
+ const title = L.DomUtil.add('h3', '', container, L._('Text formatting'))
+ const elements = L.DomUtil.create('ul', '', container)
+ L.DomUtil.add('li', '', elements, L._('*simple star for italic*'))
+ L.DomUtil.add('li', '', elements, L._('**double star for bold**'))
+ L.DomUtil.add('li', '', elements, L._('# one hash for main heading'))
+ L.DomUtil.add('li', '', elements, L._('## two hashes for second heading'))
+ L.DomUtil.add('li', '', elements, L._('### three hashes for third heading'))
+ L.DomUtil.add('li', '', elements, L._('Simple link: [[http://example.com]]'))
+ L.DomUtil.add(
+ 'li',
+ '',
+ elements,
+ L._('Link with text: [[http://example.com|text of the link]]')
+ )
+ L.DomUtil.add('li', '', elements, L._('Image: {{http://image.url.com}}'))
+ L.DomUtil.add(
+ 'li',
+ '',
+ elements,
+ L._('Image with custom width (in px): {{http://image.url.com|width}}')
+ )
+ L.DomUtil.add('li', '', elements, L._('Iframe: {{{http://iframe.url.com}}}'))
+ L.DomUtil.add(
+ 'li',
+ '',
+ elements,
+ L._('Iframe with custom height (in px): {{{http://iframe.url.com|height}}}')
+ )
+ L.DomUtil.add(
+ 'li',
+ '',
+ elements,
+ L._(
+ 'Iframe with custom height and width (in px): {{{http://iframe.url.com|height*width}}}'
+ )
+ )
+ L.DomUtil.add('li', '', elements, L._('--- for an horizontal rule'))
+ return container
+ },
- textFormatting: function () {
- var container = L.DomUtil.create('div'),
- title = L.DomUtil.add('h3', '', container, L._('Text formatting')),
- elements = L.DomUtil.create('ul', '', container);
- L.DomUtil.add('li', '', elements, L._('*simple star for italic*'));
- L.DomUtil.add('li', '', elements, L._('**double star for bold**'));
- L.DomUtil.add('li', '', elements, L._('# one hash for main heading'));
- L.DomUtil.add('li', '', elements, L._('## two hashes for second heading'));
- L.DomUtil.add('li', '', elements, L._('### three hashes for third heading'));
- L.DomUtil.add('li', '', elements, L._('Simple link: [[http://example.com]]'));
- L.DomUtil.add('li', '', elements, L._('Link with text: [[http://example.com|text of the link]]'));
- L.DomUtil.add('li', '', elements, L._('Image: {{http://image.url.com}}'));
- L.DomUtil.add('li', '', elements, L._('Image with custom width (in px): {{http://image.url.com|width}}'));
- L.DomUtil.add('li', '', elements, L._('Iframe: {{{http://iframe.url.com}}}'));
- L.DomUtil.add('li', '', elements, L._('Iframe with custom height (in px): {{{http://iframe.url.com|height}}}'));
- L.DomUtil.add('li', '', elements, L._('Iframe with custom height and width (in px): {{{http://iframe.url.com|height*width}}}'));
- L.DomUtil.add('li', '', elements, L._('--- for an horizontal rule'));
- return container;
- },
-
- dynamicProperties: function () {
- var container = L.DomUtil.create('div');
- L.DomUtil.add('h3', '', container, L._('Dynamic properties'));
- L.DomUtil.add('p', '', container, L._('Use placeholders with feature properties between brackets, eg. {name}, they will be dynamically replaced by the corresponding values.'));
- return container;
- },
-
- formatURL: L._('Supported variables that will be dynamically replaced') + ': {bbox}, {lat}, {lng}, {zoom}, {east}, {north}..., {left}, {top}...',
- formatIconSymbol: L._('Symbol can be either a unicode character or an URL. You can use feature properties as variables: ex.: with "http://myserver.org/images/{name}.png", the {name} variable will be replaced by the "name" value of each marker.'),
- colorValue: L._('Must be a valid CSS value (eg.: DarkBlue or #123456)'),
- smoothFactor: L._('How much to simplify the polyline on each zoom level (more = better performance and smoother look, less = more accurate)'),
- dashArray: L._('A comma separated list of numbers that defines the stroke dash pattern. Ex.: "5, 10, 15".'),
- zoomTo: L._('Zoom level for automatic zooms'),
- labelKey: L._('The name of the property to use as feature label (ex.: "nom")'),
- stroke: L._('Whether to display or not polygons paths.'),
- fill: L._('Whether to fill polygons with color.'),
- fillColor: L._('Optional. Same as color if not set.'),
- shortCredit: L._('Will be displayed in the bottom right corner of the map'),
- longCredit: L._('Will be visible in the caption of the map'),
- permanentCredit: L._('Will be permanently visible in the bottom left corner of the map'),
- sortKey: L._('Property to use for sorting features'),
- slugKey: L._('The name of the property to use as feature unique identifier.'),
- filterKey: L._('Comma separated list of properties to use when filtering features'),
- advancedFilterKey: L._('Comma separated list of properties to use for checkbox filtering'),
- advancedFiltersNoResults: L._('No results for these filters'),
- interactive: L._('If false, the polygon will act as a part of the underlying map.'),
- outlink: L._('Define link to open in a new window on polygon click.'),
- dynamicRemoteData: L._('Fetch data each time map view changes.'),
- proxyRemoteData: L._('To use if remote server doesn\'t allow cross domain (slower)'),
- browsable: L._('Set it to false to hide this layer from the slideshow, the data browser, the popup navigation…')
-});
+ dynamicProperties() {
+ const container = L.DomUtil.create('div')
+ L.DomUtil.add('h3', '', container, L._('Dynamic properties'))
+ L.DomUtil.add(
+ 'p',
+ '',
+ container,
+ L._(
+ 'Use placeholders with feature properties between brackets, eg. {name}, they will be dynamically replaced by the corresponding values.'
+ )
+ )
+ return container
+ },
+ formatURL: `${L._(
+ 'Supported variables that will be dynamically replaced'
+ )}: {bbox}, {lat}, {lng}, {zoom}, {east}, {north}..., {left}, {top}...`,
+ formatIconSymbol: L._(
+ 'Symbol can be either a unicode character or an URL. You can use feature properties as variables: ex.: with "http://myserver.org/images/{name}.png", the {name} variable will be replaced by the "name" value of each marker.'
+ ),
+ colorValue: L._('Must be a valid CSS value (eg.: DarkBlue or #123456)'),
+ smoothFactor: L._(
+ 'How much to simplify the polyline on each zoom level (more = better performance and smoother look, less = more accurate)'
+ ),
+ dashArray: L._(
+ 'A comma separated list of numbers that defines the stroke dash pattern. Ex.: "5, 10, 15".'
+ ),
+ zoomTo: L._('Zoom level for automatic zooms'),
+ labelKey: L._('The name of the property to use as feature label (ex.: "nom")'),
+ stroke: L._('Whether to display or not polygons paths.'),
+ fill: L._('Whether to fill polygons with color.'),
+ fillColor: L._('Optional. Same as color if not set.'),
+ shortCredit: L._('Will be displayed in the bottom right corner of the map'),
+ longCredit: L._('Will be visible in the caption of the map'),
+ permanentCredit: L._(
+ 'Will be permanently visible in the bottom left corner of the map'
+ ),
+ sortKey: L._('Property to use for sorting features'),
+ slugKey: L._('The name of the property to use as feature unique identifier.'),
+ filterKey: L._('Comma separated list of properties to use when filtering features'),
+ advancedFilterKey: L._(
+ 'Comma separated list of properties to use for checkbox filtering'
+ ),
+ advancedFiltersNoResults: L._('No results for these filters'),
+ interactive: L._('If false, the polygon will act as a part of the underlying map.'),
+ outlink: L._('Define link to open in a new window on polygon click.'),
+ dynamicRemoteData: L._('Fetch data each time map view changes.'),
+ proxyRemoteData: L._("To use if remote server doesn't allow cross domain (slower)"),
+ browsable: L._(
+ 'Set it to false to hide this layer from the slideshow, the data browser, the popup navigation…'
+ ),
+})
L.U.Orderable = L.Evented.extend({
+ options: {
+ selector: 'li',
+ color: '#374E75',
+ },
- options: {
- selector: 'li',
- color: '#374E75'
- },
+ initialize(parent, options) {
+ L.Util.setOptions(this, options)
+ this.parent = parent
+ this.src = null
+ this.dst = null
+ this.els = this.parent.querySelectorAll(this.options.selector)
+ for (let i = 0; i < this.els.length; i++) this.makeDraggable(this.els[i])
+ },
- initialize: function (parent, options) {
- L.Util.setOptions(this, options);
- this.parent = parent;
- this.src = null;
- this.dst = null;
- this.els = this.parent.querySelectorAll(this.options.selector);
- for (var i = 0; i < this.els.length; i++) this.makeDraggable(this.els[i]);
- },
+ makeDraggable(node) {
+ node.draggable = true
+ L.DomEvent.on(node, 'dragstart', this.onDragStart, this)
+ L.DomEvent.on(node, 'dragenter', this.onDragEnter, this)
+ L.DomEvent.on(node, 'dragover', this.onDragOver, this)
+ L.DomEvent.on(node, 'dragleave', this.onDragLeave, this)
+ L.DomEvent.on(node, 'drop', this.onDrop, this)
+ L.DomEvent.on(node, 'dragend', this.onDragEnd, this)
+ },
- makeDraggable: function (node) {
- node.draggable = true;
- L.DomEvent.on(node, 'dragstart', this.onDragStart, this);
- L.DomEvent.on(node, 'dragenter', this.onDragEnter, this);
- L.DomEvent.on(node, 'dragover', this.onDragOver, this);
- L.DomEvent.on(node, 'dragleave', this.onDragLeave, this);
- L.DomEvent.on(node, 'drop', this.onDrop, this);
- L.DomEvent.on(node, 'dragend', this.onDragEnd, this);
- },
+ nodeIndex(node) {
+ return Array.prototype.indexOf.call(this.parent.children, node)
+ },
- nodeIndex: function (node) {
- return Array.prototype.indexOf.call(this.parent.children, node);
- },
-
- findTarget: function (node) {
- while (node) {
- if (this.nodeIndex(node) !== -1) return node;
- node = node.parentNode;
- }
- },
-
- onDragStart: function (e) {
- // e.target is the source node.
- this.src = e.target;
- this.initialIndex = this.nodeIndex(this.src);
- this.srcBackgroundColor = this.src.style.backgroundColor;
- this.src.style.backgroundColor = this.options.color;
- e.dataTransfer.effectAllowed = 'move';
- e.dataTransfer.setData('text/html', this.src.innerHTML);
- },
-
- onDragOver: function (e) {
- if (e.preventDefault) e.preventDefault(); // Necessary. Allows us to drop.
- e.dataTransfer.dropEffect = 'move';
- return false;
- },
-
- onDragEnter: function (e) {
- // e.target is the current hover target.
- var dst = this.findTarget(e.target);
- if (!dst || dst === this.src) return;
- this.dst = dst;
- var targetIndex = this.nodeIndex(this.dst),
- srcIndex = this.nodeIndex(this.src);
- if (targetIndex > srcIndex) this.parent.insertBefore(this.dst, this.src);
- else this.parent.insertBefore(this.src, this.dst);
- },
-
- onDragLeave: function (e) {
- // e.target is previous target element.
- },
-
- onDrop: function (e) {
- // e.target is current target element.
- if (e.stopPropagation) e.stopPropagation(); // Stops the browser from redirecting.
- if (!this.dst) return;
- this.fire('drop', {
- src: this.src,
- initialIndex: this.initialIndex,
- finalIndex: this.nodeIndex(this.src),
- dst: this.dst
- });
- return false;
- },
-
- onDragEnd: function (e) {
- // e.target is the source node.
- this.src.style.backgroundColor = this.srcBackgroundColor;
+ findTarget(node) {
+ while (node) {
+ if (this.nodeIndex(node) !== -1) return node
+ node = node.parentNode
}
+ },
-});
+ onDragStart({ target, dataTransfer }) {
+ // e.target is the source node.
+ this.src = target
+ this.initialIndex = this.nodeIndex(this.src)
+ this.srcBackgroundColor = this.src.style.backgroundColor
+ this.src.style.backgroundColor = this.options.color
+ dataTransfer.effectAllowed = 'move'
+ dataTransfer.setData('text/html', this.src.innerHTML)
+ },
+
+ onDragOver(e) {
+ if (e.preventDefault) e.preventDefault() // Necessary. Allows us to drop.
+ e.dataTransfer.dropEffect = 'move'
+ return false
+ },
+
+ onDragEnter({ target }) {
+ // e.target is the current hover target.
+ const dst = this.findTarget(target)
+ if (!dst || dst === this.src) return
+ this.dst = dst
+ const targetIndex = this.nodeIndex(this.dst)
+ const srcIndex = this.nodeIndex(this.src)
+ if (targetIndex > srcIndex) this.parent.insertBefore(this.dst, this.src)
+ else this.parent.insertBefore(this.src, this.dst)
+ },
+
+ onDragLeave(e) {
+ // e.target is previous target element.
+ },
+
+ onDrop(e) {
+ // e.target is current target element.
+ if (e.stopPropagation) e.stopPropagation() // Stops the browser from redirecting.
+ if (!this.dst) return
+ this.fire('drop', {
+ src: this.src,
+ initialIndex: this.initialIndex,
+ finalIndex: this.nodeIndex(this.src),
+ dst: this.dst,
+ })
+ return false
+ },
+
+ onDragEnd(e) {
+ // e.target is the source node.
+ this.src.style.backgroundColor = this.srcBackgroundColor
+ },
+})
L.LatLng.prototype.isValid = function () {
- return !isNaN(this.lat) && !isNaN(this.lng);
+ return !isNaN(this.lat) && !isNaN(this.lng)
}
diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js
index 2908a7ac..3744b166 100644
--- a/umap/static/umap/js/umap.features.js
+++ b/umap/static/umap/js/umap.features.js
@@ -1,1057 +1,1117 @@
L.U.FeatureMixin = {
+ staticOptions: {},
- staticOptions: {},
+ initialize(map, latlng, options) {
+ this.map = map
+ if (typeof options === 'undefined') {
+ options = {}
+ }
+ // DataLayer the marker belongs to
+ this.datalayer = options.datalayer || null
+ this.properties = { _umap_options: {} }
+ if (options.geojson) {
+ this.populate(options.geojson)
+ }
+ let isDirty = false
+ const self = this
+ try {
+ Object.defineProperty(this, 'isDirty', {
+ get() {
+ return isDirty
+ },
+ set(status) {
+ if (!isDirty && status) {
+ self.fire('isdirty')
+ }
+ isDirty = status
+ if (self.datalayer) {
+ self.datalayer.isDirty = status
+ }
+ },
+ })
+ } catch (e) {
+ // Certainly IE8, which has a limited version of defineProperty
+ }
+ this.preInit()
+ this.addInteractions()
+ this.parentClass.prototype.initialize.call(this, latlng, options)
+ },
- initialize: function (map, latlng, options) {
- this.map = map;
- if(typeof options === 'undefined') {
- options = {};
- }
- // DataLayer the marker belongs to
- this.datalayer = options.datalayer || null;
- this.properties = {_umap_options: {}};
- if (options.geojson) {
- this.populate(options.geojson);
- }
- var isDirty = false,
- self = this;
- try {
- Object.defineProperty(this, 'isDirty', {
- get: function () {
- return isDirty;
- },
- set: function (status) {
- if (!isDirty && status) {
- self.fire('isdirty');
- }
- isDirty = status;
- if (self.datalayer) {
- self.datalayer.isDirty = status;
- }
- }
- });
- }
- catch (e) {
- // Certainly IE8, which has a limited version of defineProperty
- }
- this.preInit();
- this.addInteractions();
- this.parentClass.prototype.initialize.call(this, latlng, options);
- },
+ preInit() {},
- preInit: function () {},
+ isReadOnly() {
+ return this.datalayer && this.datalayer.isRemoteLayer()
+ },
- isReadOnly: function () {
- return this.datalayer && this.datalayer.isRemoteLayer();
- },
+ getSlug() {
+ return this.properties[this.map.options.slugKey || 'name'] || ''
+ },
- getSlug: function () {
- return this.properties[this.map.options.slugKey || 'name'] || '';
- },
+ getPermalink() {
+ const slug = this.getSlug()
+ if (slug)
+ return `${L.Util.getBaseUrl()}?${L.Util.buildQueryString({ feature: slug })}${
+ window.location.hash
+ }`
+ },
- getPermalink: function () {
- const slug = this.getSlug();
- if (slug) return L.Util.getBaseUrl() + "?" + L.Util.buildQueryString({feature: slug}) + window.location.hash;
- },
+ view(e) {
+ if (this.map.editEnabled) return
+ const outlink = this.properties._umap_options.outlink
+ const target = this.properties._umap_options.outlinkTarget
+ if (outlink) {
+ switch (target) {
+ case 'self':
+ window.location = outlink
+ break
+ case 'parent':
+ window.top.location = outlink
+ break
+ default:
+ const win = window.open(this.properties._umap_options.outlink)
+ }
+ return
+ }
+ // TODO deal with an event instead?
+ if (this.map.slideshow) this.map.slideshow.current = this
+ this.map.currentFeature = this
+ this.attachPopup()
+ this.openPopup((e && e.latlng) || this.getCenter())
+ },
- view: function(e) {
- if (this.map.editEnabled) return;
- var outlink = this.properties._umap_options.outlink,
- target = this.properties._umap_options.outlinkTarget
- if (outlink) {
- switch (target) {
- case 'self':
- window.location = outlink;
- break;
- case 'parent':
- window.top.location = outlink;
- break;
- default:
- var win = window.open(this.properties._umap_options.outlink);
- }
- return;
- }
- // TODO deal with an event instead?
- if (this.map.slideshow) this.map.slideshow.current = this;
- this.map.currentFeature = this;
- this.attachPopup();
- this.openPopup(e && e.latlng || this.getCenter());
- },
+ openPopup(...args) {
+ if (this.map.editEnabled) return
+ this.parentClass.prototype.openPopup.apply(this, args)
+ },
- openPopup: function () {
- if (this.map.editEnabled) return;
- this.parentClass.prototype.openPopup.apply(this, arguments);
- },
+ edit(e) {
+ if (!this.map.editEnabled || this.isReadOnly()) return
+ const container = L.DomUtil.create('div', 'umap-datalayer-container')
- edit: function(e) {
- if(!this.map.editEnabled || this.isReadOnly()) return;
- var container = L.DomUtil.create('div', 'umap-datalayer-container');
+ let builder = new L.U.FormBuilder(this, ['datalayer'], {
+ callback() {
+ this.edit(e)
+ }, // removeLayer step will close the edit panel, let's reopen it
+ })
+ container.appendChild(builder.build())
- var builder = new L.U.FormBuilder(this, ['datalayer'], {
- callback: function () {this.edit(e);} // removeLayer step will close the edit panel, let's reopen it
- });
- container.appendChild(builder.build());
+ const properties = []
+ let property
+ for (let i = 0; i < this.datalayer._propertiesIndex.length; i++) {
+ property = this.datalayer._propertiesIndex[i]
+ if (L.Util.indexOf(['name', 'description'], property) !== -1) {
+ continue
+ }
+ properties.push([`properties.${property}`, { label: property }])
+ }
+ // We always want name and description for now (properties management to come)
+ properties.unshift('properties.description')
+ properties.unshift('properties.name')
+ builder = new L.U.FormBuilder(this, properties, {
+ id: 'umap-feature-properties',
+ callback: this.resetTooltip,
+ })
+ container.appendChild(builder.build())
+ this.appendEditFieldsets(container)
+ const advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'))
+ this.getAdvancedEditActions(advancedActions)
+ this.map.ui.openPanel({ data: { html: container }, className: 'dark' })
+ this.map.editedFeature = this
+ if (!this.isOnScreen()) this.zoomTo(e)
+ },
- var properties = [], property;
- for (var i = 0; i < this.datalayer._propertiesIndex.length; i++) {
- property = this.datalayer._propertiesIndex[i];
- if (L.Util.indexOf(['name', 'description'], property) !== -1) {continue;}
- properties.push(['properties.' + property, {label: property}]);
- }
- // We always want name and description for now (properties management to come)
- properties.unshift('properties.description');
- properties.unshift('properties.name');
- builder = new L.U.FormBuilder(this, properties,
- {
- id: 'umap-feature-properties',
- callback: this.resetTooltip
- }
- );
- container.appendChild(builder.build());
- this.appendEditFieldsets(container);
- var advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'));
- this.getAdvancedEditActions(advancedActions);
- this.map.ui.openPanel({data: {html: container}, className: 'dark'});
- this.map.editedFeature = this;
- if (!this.isOnScreen()) this.zoomTo(e);
- },
+ getAdvancedEditActions(container) {
+ const deleteLink = L.DomUtil.create('a', 'button umap-delete', container)
+ deleteLink.href = '#'
+ deleteLink.textContent = L._('Delete')
+ L.DomEvent.on(
+ deleteLink,
+ 'click',
+ function (e) {
+ L.DomEvent.stop(e)
+ if (this.confirmDelete()) this.map.ui.closePanel()
+ },
+ this
+ )
+ },
- getAdvancedEditActions: function (container) {
- var deleteLink = L.DomUtil.create('a', 'button umap-delete', container);
- deleteLink.href = '#';
- deleteLink.textContent = L._('Delete');
- L.DomEvent.on(deleteLink, 'click', function (e) {
- L.DomEvent.stop(e);
- if (this.confirmDelete()) this.map.ui.closePanel();
- }, this);
- },
+ appendEditFieldsets(container) {
+ const optionsFields = this.getShapeOptions()
+ var builder = new L.U.FormBuilder(this, optionsFields, {
+ id: 'umap-feature-shape-properties',
+ callback: this._redraw,
+ })
+ const shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties'))
+ shapeProperties.appendChild(builder.build())
- appendEditFieldsets: function (container) {
- var optionsFields = this.getShapeOptions();
- var builder = new L.U.FormBuilder(this, optionsFields, {
- id: 'umap-feature-shape-properties',
- callback: this._redraw
- });
- var shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties'));
- shapeProperties.appendChild(builder.build());
+ const advancedOptions = this.getAdvancedOptions()
+ var builder = new L.U.FormBuilder(this, advancedOptions, {
+ id: 'umap-feature-advanced-properties',
+ callback: this._redraw,
+ })
+ const advancedProperties = L.DomUtil.createFieldset(
+ container,
+ L._('Advanced properties')
+ )
+ advancedProperties.appendChild(builder.build())
- var advancedOptions = this.getAdvancedOptions();
- var builder = new L.U.FormBuilder(this, advancedOptions, {
- id: 'umap-feature-advanced-properties',
- callback: this._redraw
- });
- var advancedProperties = L.DomUtil.createFieldset(container, L._('Advanced properties'));
- advancedProperties.appendChild(builder.build());
+ const interactionOptions = this.getInteractionOptions()
+ builder = new L.U.FormBuilder(this, interactionOptions, {
+ callback: this._redraw,
+ })
+ const popupFieldset = L.DomUtil.createFieldset(
+ container,
+ L._('Interaction options')
+ )
+ popupFieldset.appendChild(builder.build())
+ },
- var interactionOptions = this.getInteractionOptions();
- builder = new L.U.FormBuilder(this, interactionOptions, {
- callback: this._redraw
- });
- var popupFieldset = L.DomUtil.createFieldset(container, L._('Interaction options'));
- popupFieldset.appendChild(builder.build());
+ getInteractionOptions() {
+ return [
+ 'properties._umap_options.popupShape',
+ 'properties._umap_options.popupTemplate',
+ 'properties._umap_options.showLabel',
+ 'properties._umap_options.labelDirection',
+ 'properties._umap_options.labelInteractive',
+ ]
+ },
- },
+ endEdit() {},
- getInteractionOptions: function () {
- return [
- 'properties._umap_options.popupShape',
- 'properties._umap_options.popupTemplate',
- 'properties._umap_options.showLabel',
- 'properties._umap_options.labelDirection',
- 'properties._umap_options.labelInteractive'
- ];
- },
+ getDisplayName(fallback) {
+ if (fallback === undefined) fallback = this.datalayer.options.name
+ const key = this.getOption('labelKey') || 'name'
+ // Variables mode.
+ if (key.includes('{')) return L.Util.greedyTemplate(key, this.extendedProperties())
+ // Simple mode.
+ return this.properties[key] || this.properties.title || fallback
+ },
- endEdit: function () {},
+ hasPopupFooter() {
+ if (L.Browser.ielt9) return false
+ if (this.datalayer.isRemoteLayer() && this.datalayer.options.remoteData.dynamic)
+ return false
+ return this.map.options.displayPopupFooter
+ },
- getDisplayName: function (fallback) {
- if (fallback === undefined) fallback = this.datalayer.options.name;
- var key = this.getOption('labelKey') || 'name';
- // Variables mode.
- if (key.indexOf("{") != -1) return L.Util.greedyTemplate(key, this.extendedProperties());
- // Simple mode.
- return this.properties[key] || this.properties.title || fallback;
- },
+ getPopupClass() {
+ const old = this.getOption('popupTemplate') // Retrocompat.
+ return L.U.Popup[this.getOption('popupShape') || old] || L.U.Popup
+ },
- hasPopupFooter: function () {
- if (L.Browser.ielt9) return false;
- if (this.datalayer.isRemoteLayer() && this.datalayer.options.remoteData.dynamic) return false;
- return this.map.options.displayPopupFooter;
- },
+ attachPopup() {
+ const Class = this.getPopupClass()
+ this.bindPopup(new Class(this))
+ },
- getPopupClass: function () {
- var old = this.getOption('popupTemplate'); // Retrocompat.
- return L.U.Popup[this.getOption('popupShape') || old] || L.U.Popup;
- },
+ confirmDelete() {
+ if (confirm(L._('Are you sure you want to delete the feature?'))) {
+ this.del()
+ return true
+ }
+ return false
+ },
- attachPopup: function () {
- var Class = this.getPopupClass();
- this.bindPopup(new Class(this));
- },
+ del() {
+ this.isDirty = true
+ this.map.closePopup()
+ if (this.datalayer) {
+ this.datalayer.removeLayer(this)
+ this.disconnectFromDataLayer(this.datalayer)
+ }
+ },
- confirmDelete: function () {
- if (confirm(L._('Are you sure you want to delete the feature?'))) {
- this.del();
- return true;
- }
- return false;
- },
+ connectToDataLayer(datalayer) {
+ this.datalayer = datalayer
+ this.options.renderer = this.datalayer.renderer
+ },
- del: function () {
- this.isDirty = true;
- this.map.closePopup();
- if (this.datalayer) {
- this.datalayer.removeLayer(this);
- this.disconnectFromDataLayer(this.datalayer);
- }
- },
+ disconnectFromDataLayer(datalayer) {
+ if (this.datalayer === datalayer) {
+ this.datalayer = null
+ }
+ },
- connectToDataLayer: function (datalayer) {
- this.datalayer = datalayer;
- this.options.renderer = this.datalayer.renderer;
- },
+ populate(feature) {
+ this.properties = L.extend({}, feature.properties)
+ this.properties._umap_options = L.extend(
+ {},
+ this.properties._storage_options,
+ this.properties._umap_options
+ )
+ // Retrocompat
+ if (this.properties._umap_options.clickable === false) {
+ this.properties._umap_options.interactive = false
+ delete this.properties._umap_options.clickable
+ }
+ },
- disconnectFromDataLayer: function (datalayer) {
- if (this.datalayer === datalayer) {
- this.datalayer = null;
- }
- },
+ changeDataLayer(datalayer) {
+ if (this.datalayer) {
+ this.datalayer.isDirty = true
+ this.datalayer.removeLayer(this)
+ }
+ datalayer.addLayer(this)
+ datalayer.isDirty = true
+ this._redraw()
+ },
- populate: function (feature) {
- this.properties = L.extend({}, feature.properties);
- this.properties._umap_options = L.extend({}, this.properties._storage_options, this.properties._umap_options);
- // Retrocompat
- if (this.properties._umap_options.clickable === false) {
- this.properties._umap_options.interactive = false;
- delete this.properties._umap_options.clickable;
- }
- },
+ getOption(option, fallback) {
+ let value = fallback
+ if (typeof this.staticOptions[option] !== 'undefined') {
+ value = this.staticOptions[option]
+ } else if (L.Util.usableOption(this.properties._umap_options, option)) {
+ value = this.properties._umap_options[option]
+ } else if (this.datalayer) {
+ value = this.datalayer.getOption(option)
+ } else {
+ value = this.map.getOption(option)
+ }
+ return value
+ },
- changeDataLayer: function(datalayer) {
- if(this.datalayer) {
- this.datalayer.isDirty = true;
- this.datalayer.removeLayer(this);
- }
- datalayer.addLayer(this);
- datalayer.isDirty = true;
- this._redraw();
- },
+ zoomTo(e = {}) {
+ const easing = e.easing !== undefined ? e.easing : this.map.options.easing
+ if (easing) {
+ this.map.flyTo(this.getCenter(), this.getBestZoom())
+ } else {
+ const latlng = e.latlng || this.getCenter()
+ this.map.setView(latlng, this.getBestZoom() || this.map.getZoom())
+ }
+ if (e.callback) e.callback.call(this)
+ },
- getOption: function (option, fallback) {
- var value = fallback;
- if (typeof this.staticOptions[option] !== 'undefined') {
- value = this.staticOptions[option];
- }
- else if (L.Util.usableOption(this.properties._umap_options, option)) {
- value = this.properties._umap_options[option];
- }
- else if (this.datalayer) {
- value = this.datalayer.getOption(option);
- }
- else {
- value = this.map.getOption(option);
- }
- return value;
- },
+ getBestZoom() {
+ return this.getOption('zoomTo')
+ },
- zoomTo: function (e) {
- e = e || {};
- var easing = e.easing !== undefined ? e.easing : this.map.options.easing;
- if (easing) {
- this.map.flyTo(this.getCenter(), this.getBestZoom());
- }
- else {
- var latlng = e.latlng || this.getCenter();
- this.map.setView(latlng, this.getBestZoom() || this.map.getZoom());
- }
- if (e.callback) e.callback.call(this);
- },
+ getNext() {
+ return this.datalayer.getNextFeature(this)
+ },
- getBestZoom: function () {
- return this.getOption('zoomTo');
- },
+ getPrevious() {
+ return this.datalayer.getPreviousFeature(this)
+ },
- getNext: function () {
- return this.datalayer.getNextFeature(this);
- },
+ cloneProperties() {
+ const properties = L.extend({}, this.properties)
+ properties._umap_options = L.extend({}, properties._umap_options)
+ if (Object.keys && Object.keys(properties._umap_options).length === 0) {
+ delete properties._umap_options // It can make a difference on big data sets
+ }
+ return properties
+ },
- getPrevious: function () {
- return this.datalayer.getPreviousFeature(this);
- },
+ deleteProperty(property) {
+ delete this.properties[property]
+ this.makeDirty()
+ },
- cloneProperties: function () {
- var properties = L.extend({}, this.properties);
- properties._umap_options = L.extend({}, properties._umap_options);
- if (Object.keys && Object.keys(properties._umap_options).length === 0) {
- delete properties._umap_options; // It can make a difference on big data sets
- }
- return properties;
- },
+ renameProperty(from, to) {
+ this.properties[to] = this.properties[from]
+ this.deleteProperty(from)
+ },
- deleteProperty: function (property) {
- delete this.properties[property];
- this.makeDirty();
- },
+ toGeoJSON() {
+ const geojson = this.parentClass.prototype.toGeoJSON.call(this)
+ geojson.properties = this.cloneProperties()
+ delete geojson.properties._storage_options
+ return geojson
+ },
- renameProperty: function (from, to) {
- this.properties[to] = this.properties[from];
- this.deleteProperty(from);
- },
+ addInteractions() {
+ this.on('contextmenu editable:vertex:contextmenu', this._showContextMenu, this)
+ this.on('click', this._onClick)
+ },
- toGeoJSON: function () {
- var geojson = this.parentClass.prototype.toGeoJSON.call(this);
- geojson.properties = this.cloneProperties();
- delete geojson.properties._storage_options;
- return geojson;
- },
-
- addInteractions: function () {
- this.on('contextmenu editable:vertex:contextmenu', this._showContextMenu, this);
- this.on('click', this._onClick);
- },
-
- _onClick: function (e) {
- if (this.map.measureTools && this.map.measureTools.enabled()) return;
- this._popupHandlersAdded = true; // Prevent leaflet from managing event
- if(!this.map.editEnabled) {
- this.view(e);
- } else if (!this.isReadOnly()) {
- if(e.originalEvent.shiftKey) {
- if(this._toggleEditing)
- this._toggleEditing(e);
- else
- this.edit(e);
- }
- else {
- new L.Toolbar.Popup(e.latlng, {
- className: 'leaflet-inplace-toolbar',
- anchor: this.getPopupToolbarAnchor(),
- actions: this.getInplaceToolbarActions(e)
- }).addTo(this.map, this, e.latlng);
- }
- }
- L.DomEvent.stop(e);
- },
-
- getPopupToolbarAnchor: function () {
- return [0, 0];
- },
-
- getInplaceToolbarActions: function (e) {
- return [L.U.ToggleEditAction, L.U.DeleteFeatureAction];
- },
-
- _showContextMenu: function (e) {
- L.DomEvent.stop(e);
- var pt = this.map.mouseEventToContainerPoint(e.originalEvent);
- e.relatedTarget = this;
- this.map.contextmenu.showAt(pt, e);
- },
-
- makeDirty: function () {
- this.isDirty = true;
- },
-
- getMap: function () {
- return this.map;
- },
-
- getContextMenuItems: function (e) {
- var permalink = this.getPermalink(),
- items = [];
- if (permalink) items.push({text: L._('Permalink'), callback: function() {window.open(permalink);}});
- if (this.map.editEnabled && !this.isReadOnly()) {
- items = items.concat(this.getContextMenuEditItems(e));
- }
- return items;
- },
-
- getContextMenuEditItems: function () {
- var items = ['-'];
- if (this.map.editedFeature !== this) {
- items.push(
- {
- text: L._('Edit this feature'),
- callback: this.edit,
- context: this,
- iconCls: 'umap-edit'
- }
- );
- }
- items = items.concat(
- {
- text: L._('Edit feature\'s layer'),
- callback: this.datalayer.edit,
- context: this.datalayer,
- iconCls: 'umap-edit'
- },
- {
- text: L._('Delete this feature'),
- callback: this.confirmDelete,
- context: this,
- iconCls: 'umap-delete'
- },
- {
- text: L._('Clone this feature'),
- callback: this.clone,
- context: this
- }
- );
- return items;
- },
-
- onRemove: function (map) {
- this.parentClass.prototype.onRemove.call(this, map);
- if (this.map.editedFeature === this) {
- this.endEdit();
- this.map.ui.closePanel();
- }
- },
-
- resetTooltip: function () {
- var displayName = this.getDisplayName(null),
- showLabel = this.getOption('showLabel'),
- oldLabelHover = this.getOption('labelHover'),
- options = {
- direction: this.getOption('labelDirection'),
- interactive: this.getOption('labelInteractive')
- };
- if (oldLabelHover && showLabel) showLabel = null; // Retrocompat.
- options.permanent = showLabel === true;
- this.unbindTooltip();
- if ((showLabel === true || showLabel === null) && displayName) this.bindTooltip(L.Util.escapeHTML(displayName), options);
- },
-
- matchFilter: function (filter, keys) {
- filter = filter.toLowerCase();
- for (var i = 0; i < keys.length; i++) {
- if ((this.properties[keys[i]] || '').toLowerCase().indexOf(filter) !== -1) return true;
- }
- return false;
- },
-
- onVertexRawClick: function (e) {
+ _onClick(e) {
+ if (this.map.measureTools && this.map.measureTools.enabled()) return
+ this._popupHandlersAdded = true // Prevent leaflet from managing event
+ if (!this.map.editEnabled) {
+ this.view(e)
+ } else if (!this.isReadOnly()) {
+ if (e.originalEvent.shiftKey) {
+ if (this._toggleEditing) this._toggleEditing(e)
+ else this.edit(e)
+ } else {
new L.Toolbar.Popup(e.latlng, {
- className: 'leaflet-inplace-toolbar',
- actions: this.getVertexActions(e)
- }).addTo(this.map, this, e.latlng, e.vertex);
- },
+ className: 'leaflet-inplace-toolbar',
+ anchor: this.getPopupToolbarAnchor(),
+ actions: this.getInplaceToolbarActions(e),
+ }).addTo(this.map, this, e.latlng)
+ }
+ }
+ L.DomEvent.stop(e)
+ },
- getVertexActions: function () {
- return [L.U.DeleteVertexAction];
- },
+ getPopupToolbarAnchor() {
+ return [0, 0]
+ },
- isMulti: function () {
- return false;
- },
+ getInplaceToolbarActions(e) {
+ return [L.U.ToggleEditAction, L.U.DeleteFeatureAction]
+ },
- clone: function () {
- var layer = this.datalayer.geojsonToFeatures(this.toGeoJSON());
- layer.isDirty = true;
- layer.edit();
- return layer;
- },
+ _showContextMenu(e) {
+ L.DomEvent.stop(e)
+ const pt = this.map.mouseEventToContainerPoint(e.originalEvent)
+ e.relatedTarget = this
+ this.map.contextmenu.showAt(pt, e)
+ },
- extendedProperties: function () {
- // Include context properties
- properties = this.map.getGeoContext();
- center = this.getCenter();
- properties.lat = center.lat;
- properties.lon = center.lng;
- properties.lng = center.lng;
- properties.rank = this.getRank() + 1;
- if (typeof this.getMeasure !== 'undefined') {
- properties.measure = this.getMeasure();
- }
- return L.extend(properties, this.properties);
- },
+ makeDirty() {
+ this.isDirty = true
+ },
- getRank: function () {
- return this.datalayer._index.indexOf(L.stamp(this));
+ getMap() {
+ return this.map
+ },
+
+ getContextMenuItems(e) {
+ const permalink = this.getPermalink()
+ let items = []
+ if (permalink)
+ items.push({
+ text: L._('Permalink'),
+ callback() {
+ window.open(permalink)
+ },
+ })
+ if (this.map.editEnabled && !this.isReadOnly()) {
+ items = items.concat(this.getContextMenuEditItems(e))
+ }
+ return items
+ },
+
+ getContextMenuEditItems() {
+ let items = ['-']
+ if (this.map.editedFeature !== this) {
+ items.push({
+ text: L._('Edit this feature'),
+ callback: this.edit,
+ context: this,
+ iconCls: 'umap-edit',
+ })
+ }
+ items = items.concat(
+ {
+ text: L._("Edit feature's layer"),
+ callback: this.datalayer.edit,
+ context: this.datalayer,
+ iconCls: 'umap-edit',
+ },
+ {
+ text: L._('Delete this feature'),
+ callback: this.confirmDelete,
+ context: this,
+ iconCls: 'umap-delete',
+ },
+ {
+ text: L._('Clone this feature'),
+ callback: this.clone,
+ context: this,
+ }
+ )
+ return items
+ },
+
+ onRemove(map) {
+ this.parentClass.prototype.onRemove.call(this, map)
+ if (this.map.editedFeature === this) {
+ this.endEdit()
+ this.map.ui.closePanel()
+ }
+ },
+
+ resetTooltip() {
+ const displayName = this.getDisplayName(null)
+ let showLabel = this.getOption('showLabel')
+ const oldLabelHover = this.getOption('labelHover')
+
+ const options = {
+ direction: this.getOption('labelDirection'),
+ interactive: this.getOption('labelInteractive'),
}
-};
+ if (oldLabelHover && showLabel) showLabel = null // Retrocompat.
+ options.permanent = showLabel === true
+ this.unbindTooltip()
+ if ((showLabel === true || showLabel === null) && displayName)
+ this.bindTooltip(L.Util.escapeHTML(displayName), options)
+ },
+
+ matchFilter(filter, keys) {
+ filter = filter.toLowerCase()
+ for (let i = 0; i < keys.length; i++) {
+ if ((this.properties[keys[i]] || '').toLowerCase().includes(filter)) return true
+ }
+ return false
+ },
+
+ onVertexRawClick(e) {
+ new L.Toolbar.Popup(e.latlng, {
+ className: 'leaflet-inplace-toolbar',
+ actions: this.getVertexActions(e),
+ }).addTo(this.map, this, e.latlng, e.vertex)
+ },
+
+ getVertexActions() {
+ return [L.U.DeleteVertexAction]
+ },
+
+ isMulti() {
+ return false
+ },
+
+ clone() {
+ const layer = this.datalayer.geojsonToFeatures(this.toGeoJSON())
+ layer.isDirty = true
+ layer.edit()
+ return layer
+ },
+
+ extendedProperties() {
+ // Include context properties
+ properties = this.map.getGeoContext()
+ center = this.getCenter()
+ properties.lat = center.lat
+ properties.lon = center.lng
+ properties.lng = center.lng
+ properties.rank = this.getRank() + 1
+ if (typeof this.getMeasure !== 'undefined') {
+ properties.measure = this.getMeasure()
+ }
+ return L.extend(properties, this.properties)
+ },
+
+ getRank() {
+ return this.datalayer._index.indexOf(L.stamp(this))
+ },
+}
L.U.Marker = L.Marker.extend({
- parentClass: L.Marker,
- includes: [L.U.FeatureMixin],
+ parentClass: L.Marker,
+ includes: [L.U.FeatureMixin],
- preInit: function () {
- this.setIcon(this.getIcon());
- },
+ preInit() {
+ this.setIcon(this.getIcon())
+ },
- addInteractions: function () {
- L.U.FeatureMixin.addInteractions.call(this);
- this.on('dragend', function (e) {
- this.isDirty = true;
- this.edit(e);
- }, this);
- if (!this.isReadOnly()) this.on('mouseover', this._enableDragging);
- this.on('mouseout', this._onMouseOut);
- this._popupHandlersAdded = true; // prevent Leaflet from binding event on bindPopup
- },
+ addInteractions() {
+ L.U.FeatureMixin.addInteractions.call(this)
+ this.on(
+ 'dragend',
+ function (e) {
+ this.isDirty = true
+ this.edit(e)
+ },
+ this
+ )
+ if (!this.isReadOnly()) this.on('mouseover', this._enableDragging)
+ this.on('mouseout', this._onMouseOut)
+ this._popupHandlersAdded = true // prevent Leaflet from binding event on bindPopup
+ },
- _onMouseOut: function () {
- if(this.dragging && this.dragging._draggable && !this.dragging._draggable._moving) {
- // Do not disable if the mouse went out while dragging
- this._disableDragging();
- }
- },
-
- _enableDragging: function () {
- // TODO: start dragging after 1 second on mouse down
- if(this.map.editEnabled) {
- if (!this.editEnabled()) this.enableEdit();
- // Enabling dragging on the marker override the Draggable._OnDown
- // event, which, as it stopPropagation, refrain the call of
- // _onDown with map-pane element, which is responsible to
- // set the _moved to false, and thus to enable the click.
- // We should find a cleaner way to handle this.
- this.map.dragging._draggable._moved = false;
- }
- },
-
- _disableDragging: function () {
- if(this.map.editEnabled) {
- if (this.editor && this.editor.drawing) return; // when creating a new marker, the mouse can trigger the mouseover/mouseout event
- // do not listen to them
- this.disableEdit();
- }
- },
-
- _redraw: function() {
- if (this.datalayer && this.datalayer.isVisible()) {
- this._initIcon();
- this.update();
- }
- },
-
- _initIcon: function () {
- this.options.icon = this.getIcon();
- L.Marker.prototype._initIcon.call(this);
- this.resetTooltip();
- },
-
- disconnectFromDataLayer: function (datalayer) {
- this.options.icon.datalayer = null;
- L.U.FeatureMixin.disconnectFromDataLayer.call(this, datalayer);
- },
-
- _getIconUrl: function (name) {
- if (typeof name === 'undefined') name = 'icon';
- return this.getOption(name + 'Url');
- },
-
- getIconClass: function () {
- return this.getOption('iconClass');
- },
-
- getIcon: function () {
- var Class = L.U.Icon[this.getIconClass()] || L.U.Icon.Default;
- return new Class(this.map, {feature: this});
- },
-
- getCenter: function () {
- return this._latlng;
- },
-
- getClassName: function () {
- return 'marker';
- },
-
- getShapeOptions: function () {
- return [
- 'properties._umap_options.color',
- 'properties._umap_options.iconClass',
- 'properties._umap_options.iconUrl'
- ];
- },
-
- getAdvancedOptions: function () {
- return [
- 'properties._umap_options.zoomTo'
- ];
- },
-
- appendEditFieldsets: function (container) {
- L.U.FeatureMixin.appendEditFieldsets.call(this, container);
- var coordinatesOptions = [
- ['_latlng.lat', {handler: 'FloatInput', label: L._('Latitude')}],
- ['_latlng.lng', {handler: 'FloatInput', label: L._('Longitude')}]
- ];
- var builder = new L.U.FormBuilder(this, coordinatesOptions, {
- callback: function () {
- if (!this._latlng.isValid()) return this.map.ui.alert({content: L._('Invalid latitude or longitude'), level: 'error'});
- this._redraw();
- this.zoomTo({easing: false});
- },
- callbackContext: this
- });
- var fieldset = L.DomUtil.createFieldset(container, L._('Coordinates'));
- fieldset.appendChild(builder.build());
- },
-
- zoomTo: function (e) {
- if (this.datalayer.isClustered() && !this._icon) {
- // callback is mandatory for zoomToShowLayer
- this.datalayer.layer.zoomToShowLayer(this, e.callback || function (){});
- } else {
- L.U.FeatureMixin.zoomTo.call(this, e);
- }
- },
-
- isOnScreen: function () {
- var bounds = this.map.getBounds();
- return bounds.contains(this._latlng);
- },
-
- getPopupToolbarAnchor: function () {
- return this.options.icon.options.popupAnchor;
+ _onMouseOut() {
+ if (
+ this.dragging &&
+ this.dragging._draggable &&
+ !this.dragging._draggable._moving
+ ) {
+ // Do not disable if the mouse went out while dragging
+ this._disableDragging()
}
+ },
-});
+ _enableDragging() {
+ // TODO: start dragging after 1 second on mouse down
+ if (this.map.editEnabled) {
+ if (!this.editEnabled()) this.enableEdit()
+ // Enabling dragging on the marker override the Draggable._OnDown
+ // event, which, as it stopPropagation, refrain the call of
+ // _onDown with map-pane element, which is responsible to
+ // set the _moved to false, and thus to enable the click.
+ // We should find a cleaner way to handle this.
+ this.map.dragging._draggable._moved = false
+ }
+ },
+ _disableDragging() {
+ if (this.map.editEnabled) {
+ if (this.editor && this.editor.drawing) return // when creating a new marker, the mouse can trigger the mouseover/mouseout event
+ // do not listen to them
+ this.disableEdit()
+ }
+ },
+
+ _redraw() {
+ if (this.datalayer && this.datalayer.isVisible()) {
+ this._initIcon()
+ this.update()
+ }
+ },
+
+ _initIcon() {
+ this.options.icon = this.getIcon()
+ L.Marker.prototype._initIcon.call(this)
+ this.resetTooltip()
+ },
+
+ disconnectFromDataLayer(datalayer) {
+ this.options.icon.datalayer = null
+ L.U.FeatureMixin.disconnectFromDataLayer.call(this, datalayer)
+ },
+
+ _getIconUrl(name) {
+ if (typeof name === 'undefined') name = 'icon'
+ return this.getOption(`${name}Url`)
+ },
+
+ getIconClass() {
+ return this.getOption('iconClass')
+ },
+
+ getIcon() {
+ const Class = L.U.Icon[this.getIconClass()] || L.U.Icon.Default
+ return new Class(this.map, { feature: this })
+ },
+
+ getCenter() {
+ return this._latlng
+ },
+
+ getClassName() {
+ return 'marker'
+ },
+
+ getShapeOptions() {
+ return [
+ 'properties._umap_options.color',
+ 'properties._umap_options.iconClass',
+ 'properties._umap_options.iconUrl',
+ ]
+ },
+
+ getAdvancedOptions() {
+ return ['properties._umap_options.zoomTo']
+ },
+
+ appendEditFieldsets(container) {
+ L.U.FeatureMixin.appendEditFieldsets.call(this, container)
+ const coordinatesOptions = [
+ ['_latlng.lat', { handler: 'FloatInput', label: L._('Latitude') }],
+ ['_latlng.lng', { handler: 'FloatInput', label: L._('Longitude') }],
+ ]
+ const builder = new L.U.FormBuilder(this, coordinatesOptions, {
+ callback() {
+ if (!this._latlng.isValid())
+ return this.map.ui.alert({
+ content: L._('Invalid latitude or longitude'),
+ level: 'error',
+ })
+ this._redraw()
+ this.zoomTo({ easing: false })
+ },
+ callbackContext: this,
+ })
+ const fieldset = L.DomUtil.createFieldset(container, L._('Coordinates'))
+ fieldset.appendChild(builder.build())
+ },
+
+ zoomTo(e) {
+ if (this.datalayer.isClustered() && !this._icon) {
+ // callback is mandatory for zoomToShowLayer
+ this.datalayer.layer.zoomToShowLayer(this, e.callback || (() => {}))
+ } else {
+ L.U.FeatureMixin.zoomTo.call(this, e)
+ }
+ },
+
+ isOnScreen() {
+ const bounds = this.map.getBounds()
+ return bounds.contains(this._latlng)
+ },
+
+ getPopupToolbarAnchor() {
+ return this.options.icon.options.popupAnchor
+ },
+})
L.U.PathMixin = {
+ connectToDataLayer(datalayer) {
+ L.U.FeatureMixin.connectToDataLayer.call(this, datalayer)
+ // We keep markers on their own layer on top of the paths.
+ this.options.pane = this.datalayer.pane
+ },
- connectToDataLayer: function (datalayer) {
- L.U.FeatureMixin.connectToDataLayer.call(this, datalayer);
- // We keep markers on their own layer on top of the paths.
- this.options.pane = this.datalayer.pane;
- },
-
- edit: function (e) {
- if(this.map.editEnabled) {
- if (!this.editEnabled()) this.enableEdit();
- L.U.FeatureMixin.edit.call(this, e);
- }
- },
-
- _toggleEditing: function(e) {
- if(this.map.editEnabled) {
- if(this.editEnabled()) {
- this.endEdit();
- this.map.ui.closePanel();
- }
- else {
- this.edit(e);
- }
- }
- // FIXME: disable when disabling global edit
- L.DomEvent.stop(e);
- },
-
- styleOptions: [
- 'smoothFactor',
- 'color',
- 'opacity',
- 'stroke',
- 'weight',
- 'fill',
- 'fillColor',
- 'fillOpacity',
- 'dashArray',
- 'interactive'
- ],
-
- getShapeOptions: function () {
- return [
- 'properties._umap_options.color',
- 'properties._umap_options.opacity',
- 'properties._umap_options.weight'
- ];
- },
-
- getAdvancedOptions: function () {
- return [
- 'properties._umap_options.smoothFactor',
- 'properties._umap_options.dashArray',
- 'properties._umap_options.zoomTo'
- ];
- },
-
- setStyle: function (options) {
- options = options || {};
- var option;
- for (var idx in this.styleOptions) {
- option = this.styleOptions[idx];
- options[option] = this.getOption(option);
- }
- if (options.interactive) this.options.pointerEvents = 'visiblePainted';
- else this.options.pointerEvents = 'stroke';
- this.parentClass.prototype.setStyle.call(this, options);
- },
-
- _redraw: function () {
- this.setStyle();
- this.resetTooltip();
- },
-
- onAdd: function (map) {
- this._container = null;
- this.setStyle();
- // Show tooltip again when Leaflet.label allow static label on path.
- // cf https://github.com/Leaflet/Leaflet/pull/3952
- // this.map.on('showmeasure', this.showMeasureTooltip, this);
- // this.map.on('hidemeasure', this.removeTooltip, this);
- this.parentClass.prototype.onAdd.call(this, map);
- if (this.editing && this.editing.enabled()) this.editing.addHooks();
- this.resetTooltip();
- },
-
- onRemove: function (map) {
- // this.map.off('showmeasure', this.showMeasureTooltip, this);
- // this.map.off('hidemeasure', this.removeTooltip, this);
- if (this.editing && this.editing.enabled()) this.editing.removeHooks();
- L.U.FeatureMixin.onRemove.call(this, map);
- },
-
- getBestZoom: function () {
- return this.getOption("zoomTo") || this.map.getBoundsZoom(this.getBounds(), true);
- },
-
- endEdit: function () {
- this.disableEdit();
- L.U.FeatureMixin.endEdit.call(this);
- },
-
- _onMouseOver: function () {
- if (this.map.measureTools && this.map.measureTools.enabled()) {
- this.map.ui.tooltip({content: this.getMeasure(), anchor: this});
- } else if (this.map.editEnabled && !this.map.editedFeature) {
- this.map.ui.tooltip({content: L._('Click to edit'), anchor: this});
- }
- },
-
- addInteractions: function () {
- L.U.FeatureMixin.addInteractions.call(this);
- this.on('mouseover', this._onMouseOver);
- this.on('edit', this.makeDirty);
- this.on('drag editable:drag', this._onDrag);
- },
-
- _onDrag: function () {
- if (this._tooltip) this._tooltip.setLatLng(this.getCenter());
- },
-
- transferShape: function (at, to) {
- var shape = this.enableEdit().deleteShapeAt(at);
- this.disableEdit();
- if (!shape) return;
- to.enableEdit().appendShape(shape);
- if (!this._latlngs.length || !this._latlngs[0].length) this.del();
- },
-
- isolateShape: function (at) {
- if (!this.isMulti()) return;
- var shape = this.enableEdit().deleteShapeAt(at);
- this.disableEdit();
- if (!shape) return;
- var properties = this.cloneProperties();
- var other = new (this instanceof L.U.Polyline ? L.U.Polyline : L.U.Polygon)(this.map, shape, {geojson: {properties: properties}});
- this.datalayer.addLayer(other);
- other.edit();
- return other;
- },
-
- getContextMenuItems: function (e) {
- var items = L.U.FeatureMixin.getContextMenuItems.call(this, e);
- items.push({
- text: L._('Display measure'),
- callback: function () {
- this.map.ui.alert({content: this.getMeasure(), level: 'info'})
- },
- context: this
- })
- if (this.map.editEnabled && !this.isReadOnly() && this.isMulti()) {
- items = items.concat(this.getContextMenuMultiItems(e));
- }
- return items;
- },
-
- getContextMenuMultiItems: function (e) {
- var items = ['-', {
- text: L._('Remove shape from the multi'),
- callback: function () {
- this.enableEdit().deleteShapeAt(e.latlng);
- },
- context: this
- }];
- var shape = this.shapeAt(e.latlng);
- if (this._latlngs.indexOf(shape) > 0) {
- items.push({
- text: L._('Make main shape'),
- callback: function () {
- this.enableEdit().deleteShape(shape);
- this.editor.prependShape(shape);
- },
- context: this
- });
- }
- return items;
- },
-
- getContextMenuEditItems: function (e) {
- var items = L.U.FeatureMixin.getContextMenuEditItems.call(this, e);
- if (this.map.editedFeature && this.isSameClass(this.map.editedFeature) && this.map.editedFeature !== this) {
- items.push({
- text: L._('Transfer shape to edited feature'),
- callback: function () {
- this.transferShape(e.latlng, this.map.editedFeature);
- },
- context: this
- });
- }
- if (this.isMulti()) {
- items.push({
- text: L._('Extract shape to separate feature'),
- callback: function () {
- this.isolateShape(e.latlng, this.map.editedFeature);
- },
- context: this
- });
- }
- return items;
- },
-
- getInplaceToolbarActions: function (e) {
- var items = L.U.FeatureMixin.getInplaceToolbarActions.call(this, e);
- if (this.isMulti()) {
- items.push(L.U.DeleteShapeAction);
- items.push(L.U.ExtractShapeFromMultiAction);
- }
- return items;
- },
-
- isOnScreen: function () {
- var bounds = this.map.getBounds();
- return bounds.overlaps(this.getBounds());
+ edit(e) {
+ if (this.map.editEnabled) {
+ if (!this.editEnabled()) this.enableEdit()
+ L.U.FeatureMixin.edit.call(this, e)
}
+ },
-};
+ _toggleEditing(e) {
+ if (this.map.editEnabled) {
+ if (this.editEnabled()) {
+ this.endEdit()
+ this.map.ui.closePanel()
+ } else {
+ this.edit(e)
+ }
+ }
+ // FIXME: disable when disabling global edit
+ L.DomEvent.stop(e)
+ },
+
+ styleOptions: [
+ 'smoothFactor',
+ 'color',
+ 'opacity',
+ 'stroke',
+ 'weight',
+ 'fill',
+ 'fillColor',
+ 'fillOpacity',
+ 'dashArray',
+ 'interactive',
+ ],
+
+ getShapeOptions() {
+ return [
+ 'properties._umap_options.color',
+ 'properties._umap_options.opacity',
+ 'properties._umap_options.weight',
+ ]
+ },
+
+ getAdvancedOptions() {
+ return [
+ 'properties._umap_options.smoothFactor',
+ 'properties._umap_options.dashArray',
+ 'properties._umap_options.zoomTo',
+ ]
+ },
+
+ setStyle(options = {}) {
+ let option
+ for (const idx in this.styleOptions) {
+ option = this.styleOptions[idx]
+ options[option] = this.getOption(option)
+ }
+ if (options.interactive) this.options.pointerEvents = 'visiblePainted'
+ else this.options.pointerEvents = 'stroke'
+ this.parentClass.prototype.setStyle.call(this, options)
+ },
+
+ _redraw() {
+ this.setStyle()
+ this.resetTooltip()
+ },
+
+ onAdd(map) {
+ this._container = null
+ this.setStyle()
+ // Show tooltip again when Leaflet.label allow static label on path.
+ // cf https://github.com/Leaflet/Leaflet/pull/3952
+ // this.map.on('showmeasure', this.showMeasureTooltip, this);
+ // this.map.on('hidemeasure', this.removeTooltip, this);
+ this.parentClass.prototype.onAdd.call(this, map)
+ if (this.editing && this.editing.enabled()) this.editing.addHooks()
+ this.resetTooltip()
+ },
+
+ onRemove(map) {
+ // this.map.off('showmeasure', this.showMeasureTooltip, this);
+ // this.map.off('hidemeasure', this.removeTooltip, this);
+ if (this.editing && this.editing.enabled()) this.editing.removeHooks()
+ L.U.FeatureMixin.onRemove.call(this, map)
+ },
+
+ getBestZoom() {
+ return this.getOption('zoomTo') || this.map.getBoundsZoom(this.getBounds(), true)
+ },
+
+ endEdit() {
+ this.disableEdit()
+ L.U.FeatureMixin.endEdit.call(this)
+ },
+
+ _onMouseOver() {
+ if (this.map.measureTools && this.map.measureTools.enabled()) {
+ this.map.ui.tooltip({ content: this.getMeasure(), anchor: this })
+ } else if (this.map.editEnabled && !this.map.editedFeature) {
+ this.map.ui.tooltip({ content: L._('Click to edit'), anchor: this })
+ }
+ },
+
+ addInteractions() {
+ L.U.FeatureMixin.addInteractions.call(this)
+ this.on('mouseover', this._onMouseOver)
+ this.on('edit', this.makeDirty)
+ this.on('drag editable:drag', this._onDrag)
+ },
+
+ _onDrag() {
+ if (this._tooltip) this._tooltip.setLatLng(this.getCenter())
+ },
+
+ transferShape(at, to) {
+ const shape = this.enableEdit().deleteShapeAt(at)
+ this.disableEdit()
+ if (!shape) return
+ to.enableEdit().appendShape(shape)
+ if (!this._latlngs.length || !this._latlngs[0].length) this.del()
+ },
+
+ isolateShape(at) {
+ if (!this.isMulti()) return
+ const shape = this.enableEdit().deleteShapeAt(at)
+ this.disableEdit()
+ if (!shape) return
+ const properties = this.cloneProperties()
+ const other = new (this instanceof L.U.Polyline ? L.U.Polyline : L.U.Polygon)(
+ this.map,
+ shape,
+ { geojson: { properties } }
+ )
+ this.datalayer.addLayer(other)
+ other.edit()
+ return other
+ },
+
+ getContextMenuItems(e) {
+ let items = L.U.FeatureMixin.getContextMenuItems.call(this, e)
+ items.push({
+ text: L._('Display measure'),
+ callback() {
+ this.map.ui.alert({ content: this.getMeasure(), level: 'info' })
+ },
+ context: this,
+ })
+ if (this.map.editEnabled && !this.isReadOnly() && this.isMulti()) {
+ items = items.concat(this.getContextMenuMultiItems(e))
+ }
+ return items
+ },
+
+ getContextMenuMultiItems({ latlng }) {
+ const items = [
+ '-',
+ {
+ text: L._('Remove shape from the multi'),
+ callback() {
+ this.enableEdit().deleteShapeAt(latlng)
+ },
+ context: this,
+ },
+ ]
+ const shape = this.shapeAt(latlng)
+ if (this._latlngs.indexOf(shape) > 0) {
+ items.push({
+ text: L._('Make main shape'),
+ callback() {
+ this.enableEdit().deleteShape(shape)
+ this.editor.prependShape(shape)
+ },
+ context: this,
+ })
+ }
+ return items
+ },
+
+ getContextMenuEditItems(e) {
+ const items = L.U.FeatureMixin.getContextMenuEditItems.call(this, e)
+ if (
+ this.map.editedFeature &&
+ this.isSameClass(this.map.editedFeature) &&
+ this.map.editedFeature !== this
+ ) {
+ items.push({
+ text: L._('Transfer shape to edited feature'),
+ callback() {
+ this.transferShape(e.latlng, this.map.editedFeature)
+ },
+ context: this,
+ })
+ }
+ if (this.isMulti()) {
+ items.push({
+ text: L._('Extract shape to separate feature'),
+ callback() {
+ this.isolateShape(e.latlng, this.map.editedFeature)
+ },
+ context: this,
+ })
+ }
+ return items
+ },
+
+ getInplaceToolbarActions(e) {
+ const items = L.U.FeatureMixin.getInplaceToolbarActions.call(this, e)
+ if (this.isMulti()) {
+ items.push(L.U.DeleteShapeAction)
+ items.push(L.U.ExtractShapeFromMultiAction)
+ }
+ return items
+ },
+
+ isOnScreen() {
+ const bounds = this.map.getBounds()
+ return bounds.overlaps(this.getBounds())
+ },
+}
L.U.Polyline = L.Polyline.extend({
- parentClass: L.Polyline,
- includes: [L.U.FeatureMixin, L.U.PathMixin],
+ parentClass: L.Polyline,
+ includes: [L.U.FeatureMixin, L.U.PathMixin],
- staticOptions: {
- stroke: true,
- fill: false
- },
+ staticOptions: {
+ stroke: true,
+ fill: false,
+ },
- isSameClass: function (other) {
- return other instanceof L.U.Polyline;
- },
+ isSameClass(other) {
+ return other instanceof L.U.Polyline
+ },
- getClassName: function () {
- return 'polyline';
- },
+ getClassName() {
+ return 'polyline'
+ },
- getMeasure: function (shape) {
- var length = L.GeoUtil.lineLength(this.map, shape || this._defaultShape());
- return L.GeoUtil.readableDistance(length, this.map.measureTools.getMeasureUnit());
- },
+ getMeasure(shape) {
+ const length = L.GeoUtil.lineLength(this.map, shape || this._defaultShape())
+ return L.GeoUtil.readableDistance(length, this.map.measureTools.getMeasureUnit())
+ },
- getContextMenuEditItems: function (e) {
- var items = L.U.PathMixin.getContextMenuEditItems.call(this, e),
- vertexClicked = e.vertex, index;
- if (!this.isMulti()) {
- items.push({
- text: L._('Transform to polygon'),
- callback: this.toPolygon,
- context: this
- });
- }
- if (vertexClicked) {
- index = e.vertex.getIndex();
- if (index !== 0 && index !== e.vertex.getLastIndex()) {
- items.push({
- text: L._('Split line'),
- callback: e.vertex.split,
- context: e.vertex
- });
- } else if (index === 0 || index === e.vertex.getLastIndex()) {
- items.push({
- text: L._('Continue line (Ctrl+Click)'),
- callback: e.vertex.continue,
- context: e.vertex.continue
- });
- }
- }
- return items;
- },
-
- getContextMenuMultiItems: function (e) {
- var items = L.U.PathMixin.getContextMenuMultiItems.call(this, e);
- items.push({
- text: L._('Merge lines'),
- callback: this.mergeShapes,
- context: this
- });
- return items;
- },
-
- toPolygon: function () {
- var geojson = this.toGeoJSON();
- geojson.geometry.type = 'Polygon';
- geojson.geometry.coordinates = [L.Util.flattenCoordinates(geojson.geometry.coordinates)];
- var polygon = this.datalayer.geojsonToFeatures(geojson);
- polygon.edit();
- this.del();
- },
-
- getAdvancedEditActions: function (container) {
- L.U.FeatureMixin.getAdvancedEditActions.call(this, container);
- var toPolygon = L.DomUtil.create('a', 'button umap-to-polygon', container);
- toPolygon.href = '#';
- toPolygon.textContent = L._('Transform to polygon');
- L.DomEvent.on(toPolygon, 'click', this.toPolygon, this);
- },
-
- _mergeShapes: function (from, to) {
- var toLeft = to[0],
- toRight = to[to.length - 1],
- fromLeft = from[0],
- fromRight = from[from.length - 1],
- l2ldistance = toLeft.distanceTo(fromLeft),
- l2rdistance = toLeft.distanceTo(fromRight),
- r2ldistance = toRight.distanceTo(fromLeft),
- r2rdistance = toRight.distanceTo(fromRight),
- toMerge;
- if (l2rdistance < Math.min(l2ldistance, r2ldistance, r2rdistance)) {
- toMerge = [from, to];
- } else if (r2ldistance < Math.min(l2ldistance, l2rdistance, r2rdistance)) {
- toMerge = [to, from];
- } else if (r2rdistance < Math.min(l2ldistance, l2rdistance, r2ldistance)) {
- from.reverse();
- toMerge = [to, from];
- } else {
- from.reverse();
- toMerge = [from, to];
- }
- var a = toMerge[0],
- b = toMerge[1],
- p1 = this.map.latLngToContainerPoint(a[a.length - 1]),
- p2 = this.map.latLngToContainerPoint(b[0]),
- tolerance = 5; // px on screen
- if (Math.abs(p1.x - p2.x) <= tolerance && Math.abs(p1.y - p2.y) <= tolerance) {
- a.pop();
- }
- return a.concat(b);
- },
-
- mergeShapes: function () {
- if (!this.isMulti()) return;
- var latlngs = this.getLatLngs();
- if (!latlngs.length) return;
- while (latlngs.length > 1) {
- latlngs.splice(0, 2, this._mergeShapes(latlngs[1], latlngs[0]));
- }
- this.setLatLngs(latlngs[0]);
- if (!this.editEnabled()) this.edit();
- this.editor.reset();
- this.isDirty = true;
- },
-
- isMulti: function () {
- return !L.LineUtil.isFlat(this._latlngs) && this._latlngs.length > 1;
- },
-
- getVertexActions: function (e) {
- var actions = L.U.FeatureMixin.getVertexActions.call(this, e),
- index = e.vertex.getIndex();
- if (index === 0 || index === e.vertex.getLastIndex()) actions.push(L.U.ContinueLineAction);
- else actions.push(L.U.SplitLineAction);
- return actions;
+ getContextMenuEditItems(e) {
+ const items = L.U.PathMixin.getContextMenuEditItems.call(this, e)
+ const vertexClicked = e.vertex
+ let index
+ if (!this.isMulti()) {
+ items.push({
+ text: L._('Transform to polygon'),
+ callback: this.toPolygon,
+ context: this,
+ })
}
+ if (vertexClicked) {
+ index = e.vertex.getIndex()
+ if (index !== 0 && index !== e.vertex.getLastIndex()) {
+ items.push({
+ text: L._('Split line'),
+ callback: e.vertex.split,
+ context: e.vertex,
+ })
+ } else if (index === 0 || index === e.vertex.getLastIndex()) {
+ items.push({
+ text: L._('Continue line (Ctrl+Click)'),
+ callback: e.vertex.continue,
+ context: e.vertex.continue,
+ })
+ }
+ }
+ return items
+ },
-});
+ getContextMenuMultiItems(e) {
+ const items = L.U.PathMixin.getContextMenuMultiItems.call(this, e)
+ items.push({
+ text: L._('Merge lines'),
+ callback: this.mergeShapes,
+ context: this,
+ })
+ return items
+ },
+
+ toPolygon() {
+ const geojson = this.toGeoJSON()
+ geojson.geometry.type = 'Polygon'
+ geojson.geometry.coordinates = [
+ L.Util.flattenCoordinates(geojson.geometry.coordinates),
+ ]
+ const polygon = this.datalayer.geojsonToFeatures(geojson)
+ polygon.edit()
+ this.del()
+ },
+
+ getAdvancedEditActions(container) {
+ L.U.FeatureMixin.getAdvancedEditActions.call(this, container)
+ const toPolygon = L.DomUtil.create('a', 'button umap-to-polygon', container)
+ toPolygon.href = '#'
+ toPolygon.textContent = L._('Transform to polygon')
+ L.DomEvent.on(toPolygon, 'click', this.toPolygon, this)
+ },
+
+ _mergeShapes(from, to) {
+ const toLeft = to[0]
+ const toRight = to[to.length - 1]
+ const fromLeft = from[0]
+ const fromRight = from[from.length - 1]
+ const l2ldistance = toLeft.distanceTo(fromLeft)
+ const l2rdistance = toLeft.distanceTo(fromRight)
+ const r2ldistance = toRight.distanceTo(fromLeft)
+ const r2rdistance = toRight.distanceTo(fromRight)
+ let toMerge
+ if (l2rdistance < Math.min(l2ldistance, r2ldistance, r2rdistance)) {
+ toMerge = [from, to]
+ } else if (r2ldistance < Math.min(l2ldistance, l2rdistance, r2rdistance)) {
+ toMerge = [to, from]
+ } else if (r2rdistance < Math.min(l2ldistance, l2rdistance, r2ldistance)) {
+ from.reverse()
+ toMerge = [to, from]
+ } else {
+ from.reverse()
+ toMerge = [from, to]
+ }
+ const a = toMerge[0] // px on screen
+ const b = toMerge[1]
+ const p1 = this.map.latLngToContainerPoint(a[a.length - 1])
+ const p2 = this.map.latLngToContainerPoint(b[0])
+ const tolerance = 5
+ if (Math.abs(p1.x - p2.x) <= tolerance && Math.abs(p1.y - p2.y) <= tolerance) {
+ a.pop()
+ }
+ return a.concat(b)
+ },
+
+ mergeShapes() {
+ if (!this.isMulti()) return
+ const latlngs = this.getLatLngs()
+ if (!latlngs.length) return
+ while (latlngs.length > 1) {
+ latlngs.splice(0, 2, this._mergeShapes(latlngs[1], latlngs[0]))
+ }
+ this.setLatLngs(latlngs[0])
+ if (!this.editEnabled()) this.edit()
+ this.editor.reset()
+ this.isDirty = true
+ },
+
+ isMulti() {
+ return !L.LineUtil.isFlat(this._latlngs) && this._latlngs.length > 1
+ },
+
+ getVertexActions(e) {
+ const actions = L.U.FeatureMixin.getVertexActions.call(this, e)
+ const index = e.vertex.getIndex()
+ if (index === 0 || index === e.vertex.getLastIndex())
+ actions.push(L.U.ContinueLineAction)
+ else actions.push(L.U.SplitLineAction)
+ return actions
+ },
+})
L.U.Polygon = L.Polygon.extend({
- parentClass: L.Polygon,
- includes: [L.U.FeatureMixin, L.U.PathMixin],
+ parentClass: L.Polygon,
+ includes: [L.U.FeatureMixin, L.U.PathMixin],
- isSameClass: function (other) {
- return other instanceof L.U.Polygon;
- },
+ isSameClass(other) {
+ return other instanceof L.U.Polygon
+ },
- getClassName: function () {
- return 'polygon';
- },
+ getClassName() {
+ return 'polygon'
+ },
- getShapeOptions: function () {
- var options = L.U.PathMixin.getShapeOptions();
- options.push('properties._umap_options.stroke',
- 'properties._umap_options.fill',
- 'properties._umap_options.fillColor',
- 'properties._umap_options.fillOpacity'
- );
- return options;
- },
+ getShapeOptions() {
+ const options = L.U.PathMixin.getShapeOptions()
+ options.push(
+ 'properties._umap_options.stroke',
+ 'properties._umap_options.fill',
+ 'properties._umap_options.fillColor',
+ 'properties._umap_options.fillOpacity'
+ )
+ return options
+ },
- getInteractionOptions: function () {
- var options = [
- ['properties._umap_options.interactive', {handler: 'Switch', label: L._('Allow interactions'), helpEntries: 'interactive', inheritable: true}],
- ['properties._umap_options.outlink', {label: L._('Link to…'), helpEntries: 'outlink', placeholder: 'http://...', inheritable: true}],
- ['properties._umap_options.outlinkTarget', {handler: 'OutlinkTarget', label: L._('Open link in…'), inheritable: true}]
- ];
- return options.concat(L.U.FeatureMixin.getInteractionOptions());
- },
+ getInteractionOptions() {
+ const options = [
+ [
+ 'properties._umap_options.interactive',
+ {
+ handler: 'Switch',
+ label: L._('Allow interactions'),
+ helpEntries: 'interactive',
+ inheritable: true,
+ },
+ ],
+ [
+ 'properties._umap_options.outlink',
+ {
+ label: L._('Link to…'),
+ helpEntries: 'outlink',
+ placeholder: 'http://...',
+ inheritable: true,
+ },
+ ],
+ [
+ 'properties._umap_options.outlinkTarget',
+ { handler: 'OutlinkTarget', label: L._('Open link in…'), inheritable: true },
+ ],
+ ]
+ return options.concat(L.U.FeatureMixin.getInteractionOptions())
+ },
- getMeasure: function (shape) {
- var area = L.GeoUtil.geodesicArea(shape || this._defaultShape());
- return L.GeoUtil.readableArea(area, this.map.measureTools.getMeasureUnit());
- },
+ getMeasure(shape) {
+ const area = L.GeoUtil.geodesicArea(shape || this._defaultShape())
+ return L.GeoUtil.readableArea(area, this.map.measureTools.getMeasureUnit())
+ },
- getContextMenuEditItems: function (e) {
- var items = L.U.PathMixin.getContextMenuEditItems.call(this, e),
- shape = this.shapeAt(e.latlng);
- // No multi and no holes.
- if (shape && !this.isMulti() && (L.LineUtil.isFlat(shape) || shape.length === 1)) {
- items.push({
- text: L._('Transform to lines'),
- callback: this.toPolyline,
- context: this
- });
- }
- items.push({
- text: L._('Start a hole here'),
- callback: this.startHole,
- context: this
- });
- return items;
- },
-
- startHole: function (e) {
- this.enableEdit().newHole(e.latlng);
- },
-
- toPolyline: function () {
- var geojson = this.toGeoJSON();
- geojson.geometry.type = 'LineString';
- geojson.geometry.coordinates = L.Util.flattenCoordinates(geojson.geometry.coordinates);
- var polyline = this.datalayer.geojsonToFeatures(geojson);
- polyline.edit();
- this.del();
- },
-
- getAdvancedEditActions: function (container) {
- L.U.FeatureMixin.getAdvancedEditActions.call(this, container);
- var toPolyline = L.DomUtil.create('a', 'button umap-to-polyline', container);
- toPolyline.href = '#';
- toPolyline.textContent = L._('Transform to lines');
- L.DomEvent.on(toPolyline, 'click', this.toPolyline, this);
- },
-
- isMulti: function () {
- // Change me when Leaflet#3279 is merged.
- return !L.LineUtil.isFlat(this._latlngs) && !L.LineUtil.isFlat(this._latlngs[0]) && this._latlngs.length > 1;
- },
-
- getInplaceToolbarActions: function (e) {
- var items = L.U.PathMixin.getInplaceToolbarActions.call(this, e);
- items.push(L.U.CreateHoleAction);
- return items;
+ getContextMenuEditItems(e) {
+ const items = L.U.PathMixin.getContextMenuEditItems.call(this, e)
+ const shape = this.shapeAt(e.latlng)
+ // No multi and no holes.
+ if (shape && !this.isMulti() && (L.LineUtil.isFlat(shape) || shape.length === 1)) {
+ items.push({
+ text: L._('Transform to lines'),
+ callback: this.toPolyline,
+ context: this,
+ })
}
+ items.push({
+ text: L._('Start a hole here'),
+ callback: this.startHole,
+ context: this,
+ })
+ return items
+ },
-});
+ startHole({ latlng }) {
+ this.enableEdit().newHole(latlng)
+ },
+
+ toPolyline() {
+ const geojson = this.toGeoJSON()
+ geojson.geometry.type = 'LineString'
+ geojson.geometry.coordinates = L.Util.flattenCoordinates(
+ geojson.geometry.coordinates
+ )
+ const polyline = this.datalayer.geojsonToFeatures(geojson)
+ polyline.edit()
+ this.del()
+ },
+
+ getAdvancedEditActions(container) {
+ L.U.FeatureMixin.getAdvancedEditActions.call(this, container)
+ const toPolyline = L.DomUtil.create('a', 'button umap-to-polyline', container)
+ toPolyline.href = '#'
+ toPolyline.textContent = L._('Transform to lines')
+ L.DomEvent.on(toPolyline, 'click', this.toPolyline, this)
+ },
+
+ isMulti() {
+ // Change me when Leaflet#3279 is merged.
+ return (
+ !L.LineUtil.isFlat(this._latlngs) &&
+ !L.LineUtil.isFlat(this._latlngs[0]) &&
+ this._latlngs.length > 1
+ )
+ },
+
+ getInplaceToolbarActions(e) {
+ const items = L.U.PathMixin.getInplaceToolbarActions.call(this, e)
+ items.push(L.U.CreateHoleAction)
+ return items
+ },
+})
diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js
index b3a37733..b8f04926 100644
--- a/umap/static/umap/js/umap.forms.js
+++ b/umap/static/umap/js/umap.forms.js
@@ -1,794 +1,1077 @@
L.FormBuilder.Element.include({
-
- getParentNode: function () {
- if (this.options.wrapper) {
- return L.DomUtil.create(this.options.wrapper, this.options.wrapperClass || '', this.form);
- }
- var className = 'formbox';
- if (this.options.inheritable) {
- className += this.get(true) === undefined ? ' inheritable undefined' : ' inheritable ';
- }
- className += ' umap-field-' + this.name;
- this.wrapper = L.DomUtil.create('div', className, this.form);
- this.header = L.DomUtil.create('div', 'header', this.wrapper);
- if (this.options.inheritable) {
- var undefine = L.DomUtil.add('a', 'button undefine', this.header, L._('clear'));
- var define = L.DomUtil.add('a', 'button define', this.header, L._('define'));
- L.DomEvent.on(define, 'click', function (e) {
- L.DomEvent.stop(e);
- this.fetch();
- this.fire('define');
- L.DomUtil.removeClass(this.wrapper, 'undefined');
- }, this);
- L.DomEvent.on(undefine, 'click', function (e) {
- L.DomEvent.stop(e);
- L.DomUtil.addClass(this.wrapper, 'undefined');
- this.clear();
- this.sync();
- }, this);
- }
- this.quickContainer = L.DomUtil.create('span', 'quick-actions show-on-defined', this.header);
- this.extendedContainer = L.DomUtil.create('div', 'show-on-defined', this.wrapper);
- return this.extendedContainer;
- },
-
- getLabelParent: function () {
- return this.header;
- },
-
- clear: function () {
- this.input.value = '';
- },
-
- get: function (own) {
- if (!this.options.inheritable || own) return this.builder.getter(this.field);
- var path = this.field.split('.'),
- key = path[path.length - 1];
- return this.obj.getOption(key);
- },
-
- buildLabel: function () {
- if (this.options.label) {
- this.label = L.DomUtil.create('label', '', this.getLabelParent());
- this.label.textContent = this.label.title = this.options.label;
- if (this.options.helpEntries) this.builder.map.help.button(this.label, this.options.helpEntries);
- else if (this.options.helpTooltip) {
- var info = L.DomUtil.create('i', 'info', this.label);
- L.DomEvent.on(info, 'mouseover', function () {
- this.builder.map.ui.tooltip({anchor: info, content: this.options.helpTooltip, position: 'top'});
- }, this);
- }
- }
+ getParentNode() {
+ if (this.options.wrapper) {
+ return L.DomUtil.create(
+ this.options.wrapper,
+ this.options.wrapperClass || '',
+ this.form
+ )
}
+ let className = 'formbox'
+ if (this.options.inheritable) {
+ className +=
+ this.get(true) === undefined ? ' inheritable undefined' : ' inheritable '
+ }
+ className += ` umap-field-${this.name}`
+ this.wrapper = L.DomUtil.create('div', className, this.form)
+ this.header = L.DomUtil.create('div', 'header', this.wrapper)
+ if (this.options.inheritable) {
+ const undefine = L.DomUtil.add('a', 'button undefine', this.header, L._('clear'))
+ const define = L.DomUtil.add('a', 'button define', this.header, L._('define'))
+ L.DomEvent.on(
+ define,
+ 'click',
+ function (e) {
+ L.DomEvent.stop(e)
+ this.fetch()
+ this.fire('define')
+ L.DomUtil.removeClass(this.wrapper, 'undefined')
+ },
+ this
+ )
+ L.DomEvent.on(
+ undefine,
+ 'click',
+ function (e) {
+ L.DomEvent.stop(e)
+ L.DomUtil.addClass(this.wrapper, 'undefined')
+ this.clear()
+ this.sync()
+ },
+ this
+ )
+ }
+ this.quickContainer = L.DomUtil.create(
+ 'span',
+ 'quick-actions show-on-defined',
+ this.header
+ )
+ this.extendedContainer = L.DomUtil.create('div', 'show-on-defined', this.wrapper)
+ return this.extendedContainer
+ },
-});
+ getLabelParent() {
+ return this.header
+ },
+
+ clear() {
+ this.input.value = ''
+ },
+
+ get(own) {
+ if (!this.options.inheritable || own) return this.builder.getter(this.field)
+ const path = this.field.split('.')
+ const key = path[path.length - 1]
+ return this.obj.getOption(key)
+ },
+
+ buildLabel() {
+ if (this.options.label) {
+ this.label = L.DomUtil.create('label', '', this.getLabelParent())
+ this.label.textContent = this.label.title = this.options.label
+ if (this.options.helpEntries)
+ this.builder.map.help.button(this.label, this.options.helpEntries)
+ else if (this.options.helpTooltip) {
+ const info = L.DomUtil.create('i', 'info', this.label)
+ L.DomEvent.on(
+ info,
+ 'mouseover',
+ function () {
+ this.builder.map.ui.tooltip({
+ anchor: info,
+ content: this.options.helpTooltip,
+ position: 'top',
+ })
+ },
+ this
+ )
+ }
+ }
+ },
+})
L.FormBuilder.Select.include({
+ clear() {
+ this.select.value = ''
+ },
- clear: function () {
- this.select.value = '';
- },
-
- getDefault: function () {
- if (this.options.inheritable) return undefined;
- return this.getOptions()[0][0];
- }
-
-});
+ getDefault() {
+ if (this.options.inheritable) return undefined
+ return this.getOptions()[0][0]
+ },
+})
L.FormBuilder.CheckBox.include({
+ value() {
+ return L.DomUtil.hasClass(this.wrapper, 'undefined')
+ ? undefined
+ : this.input.checked
+ },
- value: function () {
- return L.DomUtil.hasClass(this.wrapper, 'undefined') ? undefined : this.input.checked;
- },
-
- clear: function () {
- this.fetch();
- }
-
-});
+ clear() {
+ this.fetch()
+ },
+})
L.FormBuilder.ColorPicker = L.FormBuilder.Input.extend({
+ colors: [
+ 'Black',
+ 'Navy',
+ 'DarkBlue',
+ 'MediumBlue',
+ 'Blue',
+ 'DarkGreen',
+ 'Green',
+ 'Teal',
+ 'DarkCyan',
+ 'DeepSkyBlue',
+ 'DarkTurquoise',
+ 'MediumSpringGreen',
+ 'Lime',
+ 'SpringGreen',
+ 'Aqua',
+ 'Cyan',
+ 'MidnightBlue',
+ 'DodgerBlue',
+ 'LightSeaGreen',
+ 'ForestGreen',
+ 'SeaGreen',
+ 'DarkSlateGray',
+ 'DarkSlateGrey',
+ 'LimeGreen',
+ 'MediumSeaGreen',
+ 'Turquoise',
+ 'RoyalBlue',
+ 'SteelBlue',
+ 'DarkSlateBlue',
+ 'MediumTurquoise',
+ 'Indigo',
+ 'DarkOliveGreen',
+ 'CadetBlue',
+ 'CornflowerBlue',
+ 'MediumAquaMarine',
+ 'DimGray',
+ 'DimGrey',
+ 'SlateBlue',
+ 'OliveDrab',
+ 'SlateGray',
+ 'SlateGrey',
+ 'LightSlateGray',
+ 'LightSlateGrey',
+ 'MediumSlateBlue',
+ 'LawnGreen',
+ 'Chartreuse',
+ 'Aquamarine',
+ 'Maroon',
+ 'Purple',
+ 'Olive',
+ 'Gray',
+ 'Grey',
+ 'SkyBlue',
+ 'LightSkyBlue',
+ 'BlueViolet',
+ 'DarkRed',
+ 'DarkMagenta',
+ 'SaddleBrown',
+ 'DarkSeaGreen',
+ 'LightGreen',
+ 'MediumPurple',
+ 'DarkViolet',
+ 'PaleGreen',
+ 'DarkOrchid',
+ 'YellowGreen',
+ 'Sienna',
+ 'Brown',
+ 'DarkGray',
+ 'DarkGrey',
+ 'LightBlue',
+ 'GreenYellow',
+ 'PaleTurquoise',
+ 'LightSteelBlue',
+ 'PowderBlue',
+ 'FireBrick',
+ 'DarkGoldenRod',
+ 'MediumOrchid',
+ 'RosyBrown',
+ 'DarkKhaki',
+ 'Silver',
+ 'MediumVioletRed',
+ 'IndianRed',
+ 'Peru',
+ 'Chocolate',
+ 'Tan',
+ 'LightGray',
+ 'LightGrey',
+ 'Thistle',
+ 'Orchid',
+ 'GoldenRod',
+ 'PaleVioletRed',
+ 'Crimson',
+ 'Gainsboro',
+ 'Plum',
+ 'BurlyWood',
+ 'LightCyan',
+ 'Lavender',
+ 'DarkSalmon',
+ 'Violet',
+ 'PaleGoldenRod',
+ 'LightCoral',
+ 'Khaki',
+ 'AliceBlue',
+ 'HoneyDew',
+ 'Azure',
+ 'SandyBrown',
+ 'Wheat',
+ 'Beige',
+ 'WhiteSmoke',
+ 'MintCream',
+ 'GhostWhite',
+ 'Salmon',
+ 'AntiqueWhite',
+ 'Linen',
+ 'LightGoldenRodYellow',
+ 'OldLace',
+ 'Red',
+ 'Fuchsia',
+ 'Magenta',
+ 'DeepPink',
+ 'OrangeRed',
+ 'Tomato',
+ 'HotPink',
+ 'Coral',
+ 'DarkOrange',
+ 'LightSalmon',
+ 'Orange',
+ 'LightPink',
+ 'Pink',
+ 'Gold',
+ 'PeachPuff',
+ 'NavajoWhite',
+ 'Moccasin',
+ 'Bisque',
+ 'MistyRose',
+ 'BlanchedAlmond',
+ 'PapayaWhip',
+ 'LavenderBlush',
+ 'SeaShell',
+ 'Cornsilk',
+ 'LemonChiffon',
+ 'FloralWhite',
+ 'Snow',
+ 'Yellow',
+ 'LightYellow',
+ 'Ivory',
+ 'White',
+ ],
- colors: [
- 'Black', 'Navy', 'DarkBlue', 'MediumBlue', 'Blue', 'DarkGreen',
- 'Green', 'Teal', 'DarkCyan', 'DeepSkyBlue', 'DarkTurquoise',
- 'MediumSpringGreen', 'Lime', 'SpringGreen', 'Aqua', 'Cyan',
- 'MidnightBlue', 'DodgerBlue', 'LightSeaGreen', 'ForestGreen',
- 'SeaGreen', 'DarkSlateGray', 'DarkSlateGrey', 'LimeGreen',
- 'MediumSeaGreen', 'Turquoise', 'RoyalBlue', 'SteelBlue',
- 'DarkSlateBlue', 'MediumTurquoise', 'Indigo', 'DarkOliveGreen',
- 'CadetBlue', 'CornflowerBlue', 'MediumAquaMarine', 'DimGray',
- 'DimGrey', 'SlateBlue', 'OliveDrab', 'SlateGray', 'SlateGrey',
- 'LightSlateGray', 'LightSlateGrey', 'MediumSlateBlue', 'LawnGreen',
- 'Chartreuse', 'Aquamarine', 'Maroon', 'Purple', 'Olive', 'Gray',
- 'Grey', 'SkyBlue', 'LightSkyBlue', 'BlueViolet', 'DarkRed',
- 'DarkMagenta', 'SaddleBrown', 'DarkSeaGreen', 'LightGreen',
- 'MediumPurple', 'DarkViolet', 'PaleGreen', 'DarkOrchid',
- 'YellowGreen', 'Sienna', 'Brown', 'DarkGray', 'DarkGrey',
- 'LightBlue', 'GreenYellow', 'PaleTurquoise', 'LightSteelBlue',
- 'PowderBlue', 'FireBrick', 'DarkGoldenRod', 'MediumOrchid',
- 'RosyBrown', 'DarkKhaki', 'Silver', 'MediumVioletRed', 'IndianRed',
- 'Peru', 'Chocolate', 'Tan', 'LightGray', 'LightGrey', 'Thistle',
- 'Orchid', 'GoldenRod', 'PaleVioletRed', 'Crimson', 'Gainsboro',
- 'Plum', 'BurlyWood', 'LightCyan', 'Lavender', 'DarkSalmon',
- 'Violet', 'PaleGoldenRod', 'LightCoral', 'Khaki', 'AliceBlue',
- 'HoneyDew', 'Azure', 'SandyBrown', 'Wheat', 'Beige', 'WhiteSmoke',
- 'MintCream', 'GhostWhite', 'Salmon', 'AntiqueWhite', 'Linen',
- 'LightGoldenRodYellow', 'OldLace', 'Red', 'Fuchsia', 'Magenta',
- 'DeepPink', 'OrangeRed', 'Tomato', 'HotPink', 'Coral', 'DarkOrange',
- 'LightSalmon', 'Orange', 'LightPink', 'Pink', 'Gold', 'PeachPuff',
- 'NavajoWhite', 'Moccasin', 'Bisque', 'MistyRose', 'BlanchedAlmond',
- 'PapayaWhip', 'LavenderBlush', 'SeaShell', 'Cornsilk',
- 'LemonChiffon', 'FloralWhite', 'Snow', 'Yellow', 'LightYellow',
- 'Ivory', 'White'
- ],
+ getParentNode() {
+ L.FormBuilder.CheckBox.prototype.getParentNode.call(this)
+ return this.quickContainer
+ },
- getParentNode: function () {
- L.FormBuilder.CheckBox.prototype.getParentNode.call(this);
- return this.quickContainer;
- },
+ build() {
+ L.FormBuilder.Input.prototype.build.call(this)
+ this.input.placeholder = this.options.placeholder || L._('Inherit')
+ this.container = L.DomUtil.create(
+ 'div',
+ 'umap-color-picker',
+ this.extendedContainer
+ )
+ this.container.style.display = 'none'
+ for (const idx in this.colors) {
+ this.addColor(this.colors[idx])
+ }
+ this.spreadColor()
+ this.input.autocomplete = 'off'
+ L.DomEvent.on(this.input, 'focus', this.onFocus, this)
+ L.DomEvent.on(this.input, 'blur', this.onBlur, this)
+ L.DomEvent.on(this.input, 'change', this.sync, this)
+ this.on('define', this.onFocus)
+ },
- build: function () {
- L.FormBuilder.Input.prototype.build.call(this);
- this.input.placeholder = this.options.placeholder || L._('Inherit');
- this.container = L.DomUtil.create('div', 'umap-color-picker', this.extendedContainer);
- this.container.style.display = 'none';
- for (var idx in this.colors) {
- this.addColor(this.colors[idx]);
- }
- this.spreadColor();
- this.input.autocomplete = 'off';
- L.DomEvent.on(this.input, 'focus', this.onFocus, this);
- L.DomEvent.on(this.input, 'blur', this.onBlur, this);
- L.DomEvent.on(this.input, 'change', this.sync, this);
- this.on('define', this.onFocus);
- },
+ onFocus() {
+ this.container.style.display = 'block'
+ this.spreadColor()
+ },
- onFocus: function () {
- this.container.style.display = 'block';
- this.spreadColor();
- },
+ onBlur() {
+ const self = this
- onBlur: function () {
- var self = this,
- closePicker = function () {
- self.container.style.display = 'none';
- };
- // We must leave time for the click to be listened.
- window.setTimeout(closePicker, 100);
- },
-
- sync: function () {
- this.spreadColor();
- L.FormBuilder.Input.prototype.sync.call(this);
- },
-
- spreadColor: function () {
- if (this.input.value) this.input.style.backgroundColor = this.input.value;
- else this.input.style.backgroundColor = 'inherit';
- },
-
- addColor: function (colorName) {
- var span = L.DomUtil.create('span', '', this.container);
- span.style.backgroundColor = span.title = colorName;
- var updateColorInput = function () {
- this.input.value = colorName;
- this.sync();
- this.container.style.display = 'none';
- };
- L.DomEvent.on(span, 'mousedown', updateColorInput, this);
+ const closePicker = () => {
+ self.container.style.display = 'none'
}
-});
+ // We must leave time for the click to be listened.
+ window.setTimeout(closePicker, 100)
+ },
+
+ sync() {
+ this.spreadColor()
+ L.FormBuilder.Input.prototype.sync.call(this)
+ },
+
+ spreadColor() {
+ if (this.input.value) this.input.style.backgroundColor = this.input.value
+ else this.input.style.backgroundColor = 'inherit'
+ },
+
+ addColor(colorName) {
+ const span = L.DomUtil.create('span', '', this.container)
+ span.style.backgroundColor = span.title = colorName
+ const updateColorInput = function () {
+ this.input.value = colorName
+ this.sync()
+ this.container.style.display = 'none'
+ }
+ L.DomEvent.on(span, 'mousedown', updateColorInput, this)
+ },
+})
L.FormBuilder.TextColorPicker = L.FormBuilder.ColorPicker.extend({
- colors: [
- 'Black', 'DarkSlateGrey', 'DimGrey', 'SlateGrey', 'LightSlateGrey',
- 'Grey', 'DarkGrey', 'LightGrey', 'White'
- ]
-
-});
+ colors: [
+ 'Black',
+ 'DarkSlateGrey',
+ 'DimGrey',
+ 'SlateGrey',
+ 'LightSlateGrey',
+ 'Grey',
+ 'DarkGrey',
+ 'LightGrey',
+ 'White',
+ ],
+})
L.FormBuilder.IconClassSwitcher = L.FormBuilder.Select.extend({
-
- selectOptions: [
- ['Default', L._('Default')],
- ['Circle', L._('Circle')],
- ['Drop', L._('Drop')],
- ['Ball', L._('Ball')]
- ]
-
-});
+ selectOptions: [
+ ['Default', L._('Default')],
+ ['Circle', L._('Circle')],
+ ['Drop', L._('Drop')],
+ ['Ball', L._('Ball')],
+ ],
+})
L.FormBuilder.ProxyTTLSelect = L.FormBuilder.Select.extend({
-
- selectOptions: [
- [undefined, L._('No cache')],
- ['300', L._('5 min')],
- ['3600', L._('1 hour')],
- ['86400', L._('1 day')]
- ]
-
-});
+ selectOptions: [
+ [undefined, L._('No cache')],
+ ['300', L._('5 min')],
+ ['3600', L._('1 hour')],
+ ['86400', L._('1 day')],
+ ],
+})
L.FormBuilder.PopupShape = L.FormBuilder.Select.extend({
-
- selectOptions: [
- ['Default', L._('Popup')],
- ['Large', L._('Popup (large)')],
- ['Panel', L._('Side panel')],
- ]
-
-});
+ selectOptions: [
+ ['Default', L._('Popup')],
+ ['Large', L._('Popup (large)')],
+ ['Panel', L._('Side panel')],
+ ],
+})
L.FormBuilder.PopupContent = L.FormBuilder.Select.extend({
-
- selectOptions: [
- ['Default', L._('Default')],
- ['Table', L._('Table')],
- ['GeoRSSImage', L._('GeoRSS (title + image)')],
- ['GeoRSSLink', L._('GeoRSS (only link)')],
- ]
-
-});
+ selectOptions: [
+ ['Default', L._('Default')],
+ ['Table', L._('Table')],
+ ['GeoRSSImage', L._('GeoRSS (title + image)')],
+ ['GeoRSSLink', L._('GeoRSS (only link)')],
+ ],
+})
L.FormBuilder.LayerTypeChooser = L.FormBuilder.Select.extend({
-
- selectOptions: [
- ['Default', L._('Default')],
- ['Cluster', L._('Clustered')],
- ['Heat', L._('Heatmap')]
- ]
-
-});
+ selectOptions: [
+ ['Default', L._('Default')],
+ ['Cluster', L._('Clustered')],
+ ['Heat', L._('Heatmap')],
+ ],
+})
L.FormBuilder.SlideshowDelay = L.FormBuilder.IntSelect.extend({
-
- getOptions: function () {
- var options = [];
- for (var i = 1; i < 30; i++) {
- options.push([i * 1000, L._('{delay} seconds', {delay: i})]);
- }
- return options;
+ getOptions() {
+ const options = []
+ for (let i = 1; i < 30; i++) {
+ options.push([i * 1000, L._('{delay} seconds', { delay: i })])
}
-
-});
+ return options
+ },
+})
L.FormBuilder.DataLayerSwitcher = L.FormBuilder.Select.extend({
+ getOptions() {
+ const options = []
+ this.builder.map.eachDataLayerReverse((datalayer) => {
+ if (datalayer.isLoaded() && !datalayer.isRemoteLayer() && datalayer.canBrowse()) {
+ options.push([L.stamp(datalayer), datalayer.getName()])
+ }
+ })
+ return options
+ },
- getOptions: function () {
- var options = [];
- this.builder.map.eachDataLayerReverse(function (datalayer) {
- if(datalayer.isLoaded() && !datalayer.isRemoteLayer() && datalayer.canBrowse()) {
- options.push([L.stamp(datalayer), datalayer.getName()]);
- }
- });
- return options;
- },
+ toHTML() {
+ return L.stamp(this.obj.datalayer)
+ },
- toHTML: function () {
- return L.stamp(this.obj.datalayer);
- },
+ toJS() {
+ return this.builder.map.datalayers[this.value()]
+ },
- toJS: function () {
- return this.builder.map.datalayers[this.value()];
- },
-
- set: function () {
- this.builder.map.lastUsedDataLayer = this.toJS();
- this.obj.changeDataLayer(this.toJS());
- }
-
-});
+ set() {
+ this.builder.map.lastUsedDataLayer = this.toJS()
+ this.obj.changeDataLayer(this.toJS())
+ },
+})
L.FormBuilder.onLoadPanel = L.FormBuilder.Select.extend({
-
- selectOptions: [
- ['none', L._('None')],
- ['caption', L._('Caption')],
- ['databrowser', L._('Data browser')],
- ['datafilters', L._('Data filters')]
- ]
-
-});
+ selectOptions: [
+ ['none', L._('None')],
+ ['caption', L._('Caption')],
+ ['databrowser', L._('Data browser')],
+ ['datafilters', L._('Data filters')],
+ ],
+})
L.FormBuilder.DataFormat = L.FormBuilder.Select.extend({
-
- selectOptions: [
- [undefined, L._('Choose the data format')],
- ['geojson', 'geojson'],
- ['osm', 'osm'],
- ['csv', 'csv'],
- ['gpx', 'gpx'],
- ['kml', 'kml'],
- ['georss', 'georss']
- ]
-
-});
+ selectOptions: [
+ [undefined, L._('Choose the data format')],
+ ['geojson', 'geojson'],
+ ['osm', 'osm'],
+ ['csv', 'csv'],
+ ['gpx', 'gpx'],
+ ['kml', 'kml'],
+ ['georss', 'georss'],
+ ],
+})
L.FormBuilder.LabelDirection = L.FormBuilder.Select.extend({
-
- selectOptions: [
- ['auto', L._('Automatic')],
- ['left', L._('On the left')],
- ['right', L._('On the right')],
- ['top', L._('On the top')],
- ['bottom', L._('On the bottom')]
- ]
-
-});
+ selectOptions: [
+ ['auto', L._('Automatic')],
+ ['left', L._('On the left')],
+ ['right', L._('On the right')],
+ ['top', L._('On the top')],
+ ['bottom', L._('On the bottom')],
+ ],
+})
L.FormBuilder.LicenceChooser = L.FormBuilder.Select.extend({
-
- getOptions: function () {
- var licences = [],
- licencesList = this.builder.obj.options.licences,
- licence;
- for (var i in licencesList) {
- licence = licencesList[i];
- licences.push([i, licence.name]);
- }
- return licences;
- },
-
- toHTML: function () {
- return this.get().name;
- },
-
- toJS: function () {
- return this.builder.obj.options.licences[this.value()];
+ getOptions() {
+ const licences = []
+ const licencesList = this.builder.obj.options.licences
+ let licence
+ for (const i in licencesList) {
+ licence = licencesList[i]
+ licences.push([i, licence.name])
}
+ return licences
+ },
-});
+ toHTML() {
+ return this.get().name
+ },
+ toJS() {
+ return this.builder.obj.options.licences[this.value()]
+ },
+})
L.FormBuilder.NullableBoolean = L.FormBuilder.Select.extend({
- selectOptions: [
- [undefined, L._('inherit')],
- [true, L._('yes')],
- [false, L._('no')]
- ],
+ selectOptions: [
+ [undefined, L._('inherit')],
+ [true, L._('yes')],
+ [false, L._('no')],
+ ],
- toJS: function () {
- var value = this.value();
- switch (value) {
- case 'true':
- case true:
- value = true;
- break;
- case 'false':
- case false:
- value = false;
- break;
- default:
- value = undefined;
- }
- return value;
+ toJS() {
+ let value = this.value()
+ switch (value) {
+ case 'true':
+ case true:
+ value = true
+ break
+ case 'false':
+ case false:
+ value = false
+ break
+ default:
+ value = undefined
}
-
-});
-
+ return value
+ },
+})
L.FormBuilder.BlurInput.include({
-
- build: function () {
- this.options.className = 'blur';
- L.FormBuilder.Input.prototype.build.call(this);
- var button = L.DomUtil.create('span', 'button blur-button');
- L.DomUtil.after(this.input, button);
- }
-
-});
+ build() {
+ this.options.className = 'blur'
+ L.FormBuilder.Input.prototype.build.call(this)
+ const button = L.DomUtil.create('span', 'button blur-button')
+ L.DomUtil.after(this.input, button)
+ },
+})
L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
+ type() {
+ return 'hidden'
+ },
- type: function () {
- return 'hidden';
- },
+ build() {
+ this.options.helpText = this.builder.map.help.formatIconSymbol
+ L.FormBuilder.BlurInput.prototype.build.call(this)
+ this.parentContainer = L.DomUtil.create(
+ 'div',
+ 'umap-form-iconfield',
+ this.parentNode
+ )
+ this.buttonsContainer = L.DomUtil.create('div', '', this.parentContainer)
+ this.pictogramsContainer = L.DomUtil.create(
+ 'div',
+ 'umap-pictogram-list',
+ this.parentContainer
+ )
+ this.input.type = 'hidden'
+ this.input.placeholder = L._('Symbol or url')
+ this.udpatePreview()
+ this.on('define', this.fetchIconList)
+ },
- build: function () {
- this.options.helpText = this.builder.map.help.formatIconSymbol;
- L.FormBuilder.BlurInput.prototype.build.call(this);
- this.parentContainer = L.DomUtil.create('div', 'umap-form-iconfield', this.parentNode);
- this.buttonsContainer = L.DomUtil.create('div', '', this.parentContainer);
- this.pictogramsContainer = L.DomUtil.create('div', 'umap-pictogram-list', this.parentContainer);
- this.input.type = 'hidden';
- this.input.placeholder = L._('Symbol or url');
- this.udpatePreview();
- this.on('define', this.fetchIconList);
- },
+ isUrl() {
+ return this.value().includes('/')
+ },
- isUrl: function () {
- return (this.value().indexOf('/') !== -1);
- },
-
- udpatePreview: function () {
- if (this.value()&& this.value().indexOf('{') === -1) { // Do not try to render URL with variables
- if (this.isUrl()) {
- var img = L.DomUtil.create('img', '', L.DomUtil.create('div', 'umap-icon-choice', this.buttonsContainer));
- img.src = this.value();
- L.DomEvent.on(img, 'click', this.fetchIconList, this);
- } else {
- var el = L.DomUtil.create('span', '', L.DomUtil.create('div', 'umap-icon-choice', this.buttonsContainer));
- el.textContent = this.value();
- L.DomEvent.on(el, 'click', this.fetchIconList, this);
- }
- }
- this.button = L.DomUtil.create('a', '', this.buttonsContainer);
- this.button.textContent = this.value() ? L._('Change symbol') : L._('Add symbol');
- this.button.href = '#';
- L.DomEvent
- .on(this.button, 'click', L.DomEvent.stop)
- .on(this.button, 'click', this.fetchIconList, this);
- },
-
- addIconPreview: function (pictogram) {
- var baseClass = 'umap-icon-choice',
- value = pictogram.src,
- className = value === this.value() ? baseClass + ' selected' : baseClass,
- container = L.DomUtil.create('div', className, this.pictogramsContainer),
- img = L.DomUtil.create('img', '', container);
- img.src = value;
- if (pictogram.name && pictogram.attribution) {
- img.title = pictogram.name + ' — © ' + pictogram.attribution;
- }
- L.DomEvent.on(container, 'click', function (e) {
- this.input.value = value;
- this.sync();
- this.unselectAll(this.pictogramsContainer);
- L.DomUtil.addClass(container, 'selected');
- this.pictogramsContainer.innerHTML = '';
- this.udpatePreview();
- }, this);
- },
-
- clear: function () {
- this.input.value = '';
- this.unselectAll(this.pictogramsContainer);
- this.sync();
- this.pictogramsContainer.innerHTML = '';
- this.udpatePreview();
- },
-
- buildIconList: function (data) {
- this.pictogramsContainer.innerHTML = '';
- this.buttonsContainer.innerHTML = '';
- for (var idx in data.pictogram_list) {
- this.addIconPreview(data.pictogram_list[idx]);
- }
- var cancelButton = L.DomUtil.create('a', '', this.pictogramsContainer);
- cancelButton.textContent = L._('Cancel');
- cancelButton.href = '#';
- cancelButton.style.display = 'block';
- cancelButton.style.clear = 'both';
- L.DomEvent
- .on(cancelButton, 'click', L.DomEvent.stop)
- .on(cancelButton, 'click', function (e) {
- this.pictogramsContainer.innerHTML = '';
- this.udpatePreview();
- }, this);
- var customButton = L.DomUtil.create('a', '', this.pictogramsContainer);
- customButton.textContent = L._('Set symbol');
- customButton.href = '#';
- customButton.style.display = 'block';
- customButton.style.clear = 'both';
- this.builder.map.help.button(customButton, 'formatIconSymbol');
- L.DomEvent
- .on(customButton, 'click', L.DomEvent.stop)
- .on(customButton, 'click', function (e) {
- this.input.type = 'text';
- this.pictogramsContainer.innerHTML = '';
- }, this);
- },
-
- fetchIconList: function (e) {
- this.builder.map.get(this.builder.map.options.urls.pictogram_list_json, {
- callback: this.buildIconList,
- context: this
- });
- },
-
- unselectAll: function (container) {
- var els = container.querySelectorAll('div.selected');
- for (var el in els) {
- if (els.hasOwnProperty(el)) L.DomUtil.removeClass(els[el], 'selected');
- }
+ udpatePreview() {
+ if (this.value() && !this.value().includes('{')) {
+ // Do not try to render URL with variables
+ if (this.isUrl()) {
+ const img = L.DomUtil.create(
+ 'img',
+ '',
+ L.DomUtil.create('div', 'umap-icon-choice', this.buttonsContainer)
+ )
+ img.src = this.value()
+ L.DomEvent.on(img, 'click', this.fetchIconList, this)
+ } else {
+ const el = L.DomUtil.create(
+ 'span',
+ '',
+ L.DomUtil.create('div', 'umap-icon-choice', this.buttonsContainer)
+ )
+ el.textContent = this.value()
+ L.DomEvent.on(el, 'click', this.fetchIconList, this)
+ }
}
+ this.button = L.DomUtil.create('a', '', this.buttonsContainer)
+ this.button.textContent = this.value() ? L._('Change symbol') : L._('Add symbol')
+ this.button.href = '#'
+ L.DomEvent.on(this.button, 'click', L.DomEvent.stop).on(
+ this.button,
+ 'click',
+ this.fetchIconList,
+ this
+ )
+ },
-});
+ addIconPreview({ src, name, attribution }) {
+ const baseClass = 'umap-icon-choice'
+ const value = src
+ const className = value === this.value() ? `${baseClass} selected` : baseClass
+ const container = L.DomUtil.create('div', className, this.pictogramsContainer)
+ const img = L.DomUtil.create('img', '', container)
+ img.src = value
+ if (name && attribution) {
+ img.title = `${name} — © ${attribution}`
+ }
+ L.DomEvent.on(
+ container,
+ 'click',
+ function (e) {
+ this.input.value = value
+ this.sync()
+ this.unselectAll(this.pictogramsContainer)
+ L.DomUtil.addClass(container, 'selected')
+ this.pictogramsContainer.innerHTML = ''
+ this.udpatePreview()
+ },
+ this
+ )
+ },
+
+ clear() {
+ this.input.value = ''
+ this.unselectAll(this.pictogramsContainer)
+ this.sync()
+ this.pictogramsContainer.innerHTML = ''
+ this.udpatePreview()
+ },
+
+ buildIconList({ pictogram_list }) {
+ this.pictogramsContainer.innerHTML = ''
+ this.buttonsContainer.innerHTML = ''
+ for (const idx in pictogram_list) {
+ this.addIconPreview(pictogram_list[idx])
+ }
+ const cancelButton = L.DomUtil.create('a', '', this.pictogramsContainer)
+ cancelButton.textContent = L._('Cancel')
+ cancelButton.href = '#'
+ cancelButton.style.display = 'block'
+ cancelButton.style.clear = 'both'
+ L.DomEvent.on(cancelButton, 'click', L.DomEvent.stop).on(
+ cancelButton,
+ 'click',
+ function (e) {
+ this.pictogramsContainer.innerHTML = ''
+ this.udpatePreview()
+ },
+ this
+ )
+ const customButton = L.DomUtil.create('a', '', this.pictogramsContainer)
+ customButton.textContent = L._('Set symbol')
+ customButton.href = '#'
+ customButton.style.display = 'block'
+ customButton.style.clear = 'both'
+ this.builder.map.help.button(customButton, 'formatIconSymbol')
+ L.DomEvent.on(customButton, 'click', L.DomEvent.stop).on(
+ customButton,
+ 'click',
+ function (e) {
+ this.input.type = 'text'
+ this.pictogramsContainer.innerHTML = ''
+ },
+ this
+ )
+ },
+
+ fetchIconList(e) {
+ this.builder.map.get(this.builder.map.options.urls.pictogram_list_json, {
+ callback: this.buildIconList,
+ context: this,
+ })
+ },
+
+ unselectAll(container) {
+ const els = container.querySelectorAll('div.selected')
+ for (const el in els) {
+ if (els.hasOwnProperty(el)) L.DomUtil.removeClass(els[el], 'selected')
+ }
+ },
+})
L.FormBuilder.Url = L.FormBuilder.Input.extend({
-
- type: function () {
- return 'url';
- }
-
-});
+ type() {
+ return 'url'
+ },
+})
L.FormBuilder.Switch = L.FormBuilder.CheckBox.extend({
+ getParentNode() {
+ L.FormBuilder.CheckBox.prototype.getParentNode.call(this)
+ if (this.options.inheritable) return this.quickContainer
+ return this.extendedContainer
+ },
- getParentNode: function () {
- L.FormBuilder.CheckBox.prototype.getParentNode.call(this);
- if (this.options.inheritable) return this.quickContainer;
- return this.extendedContainer;
- },
-
- build: function () {
- L.FormBuilder.CheckBox.prototype.build.apply(this);
- if (this.options.inheritable) this.label = L.DomUtil.create('label', '', this.input.parentNode);
- else this.input.parentNode.appendChild(this.label);
- L.DomUtil.addClass(this.input.parentNode, 'with-switch');
- var id = (this.builder.options.id || Date.now()) + '.' + this.name;
- this.label.setAttribute('for', id);
- L.DomUtil.addClass(this.input, 'switch');
- this.input.id = id;
- }
-
-});
+ build() {
+ L.FormBuilder.CheckBox.prototype.build.apply(this)
+ if (this.options.inheritable)
+ this.label = L.DomUtil.create('label', '', this.input.parentNode)
+ else this.input.parentNode.appendChild(this.label)
+ L.DomUtil.addClass(this.input.parentNode, 'with-switch')
+ const id = `${this.builder.options.id || Date.now()}.${this.name}`
+ this.label.setAttribute('for', id)
+ L.DomUtil.addClass(this.input, 'switch')
+ this.input.id = id
+ },
+})
L.FormBuilder.MultiChoice = L.FormBuilder.Element.extend({
+ default: 'null',
+ className: 'umap-multiplechoice',
- default: 'null',
- className: 'umap-multiplechoice',
+ clear() {
+ const checked = this.container.querySelector('input[type="radio"]:checked')
+ if (checked) checked.checked = false
+ },
- clear: function () {
- var checked = this.container.querySelector('input[type="radio"]:checked');
- if (checked) checked.checked = false;
- },
+ fetch() {
+ let value = (this.backup = this.toHTML())
+ if (!this.container.querySelector(`input[type="radio"][value="${value}"]`))
+ value = this.default
+ this.container.querySelector(`input[type="radio"][value="${value}"]`).checked = true
+ },
- fetch: function () {
- var value = this.backup = this.toHTML();
- if (!this.container.querySelector('input[type="radio"][value="' + value + '"]')) value = this.default;
- this.container.querySelector('input[type="radio"][value="' + value + '"]').checked = true;
- },
+ value() {
+ const checked = this.container.querySelector('input[type="radio"]:checked')
+ if (checked) return checked.value
+ },
- value: function () {
- var checked = this.container.querySelector('input[type="radio"]:checked');
- if (checked) return checked.value;
- },
+ getChoices() {
+ return this.options.choices || this.choices
+ },
- getChoices: function () {
- return this.options.choices || this.choices;
- },
-
- build: function () {
- var choices = this.getChoices();
- this.container = L.DomUtil.create('div', this.className + ' by' + choices.length, this.parentNode);
- for (var i = 0; i < choices.length; i++) {
- this.addChoice(choices[i][0], choices[i][1], i);
- }
- this.fetch();
- },
-
- addChoice: function (value, label, counter) {
- var input = L.DomUtil.create('input', '', this.container);
- label = L.DomUtil.add('label', '', this.container, label);
- input.type = 'radio';
- input.name = this.name;
- input.value = value;
- var id = Date.now() + '.' + this.name + '.' + counter;
- label.setAttribute('for', id);
- input.id = id;
- L.DomEvent.on(input, 'change', this.sync, this);
+ build() {
+ const choices = this.getChoices()
+ this.container = L.DomUtil.create(
+ 'div',
+ `${this.className} by${choices.length}`,
+ this.parentNode
+ )
+ for (let i = 0; i < choices.length; i++) {
+ this.addChoice(choices[i][0], choices[i][1], i)
}
+ this.fetch()
+ },
-});
+ addChoice(value, label, counter) {
+ const input = L.DomUtil.create('input', '', this.container)
+ label = L.DomUtil.add('label', '', this.container, label)
+ input.type = 'radio'
+ input.name = this.name
+ input.value = value
+ const id = `${Date.now()}.${this.name}.${counter}`
+ label.setAttribute('for', id)
+ input.id = id
+ L.DomEvent.on(input, 'change', this.sync, this)
+ },
+})
L.FormBuilder.TernaryChoices = L.FormBuilder.MultiChoice.extend({
+ default: 'null',
- default: 'null',
-
- toJS: function () {
- var value = this.value();
- switch (value) {
- case 'true':
- case true:
- value = true;
- break;
- case 'false':
- case false:
- value = false;
- break;
- default:
- value = null;
- }
- return value;
+ toJS() {
+ let value = this.value()
+ switch (value) {
+ case 'true':
+ case true:
+ value = true
+ break
+ case 'false':
+ case false:
+ value = false
+ break
+ default:
+ value = null
}
-
-});
+ return value
+ },
+})
L.FormBuilder.ControlChoice = L.FormBuilder.TernaryChoices.extend({
-
- choices: [
- [true, L._('always')],
- [false, L._('never')],
- ['null', L._('hidden')]
- ]
-
-});
-
+ choices: [
+ [true, L._('always')],
+ [false, L._('never')],
+ ['null', L._('hidden')],
+ ],
+})
L.FormBuilder.LabelChoice = L.FormBuilder.TernaryChoices.extend({
+ default: false,
- default: false,
-
- choices: [
- [true, L._('always')],
- [false, L._('never')],
- ['null', L._('on hover')]
- ]
-
-});
+ choices: [
+ [true, L._('always')],
+ [false, L._('never')],
+ ['null', L._('on hover')],
+ ],
+})
L.FormBuilder.DataLayersControl = L.FormBuilder.ControlChoice.extend({
+ choices: [
+ [true, L._('collapsed')],
+ ['expanded', L._('expanded')],
+ [false, L._('never')],
+ ['null', L._('hidden')],
+ ],
- choices: [
- [true, L._('collapsed')],
- ['expanded', L._('expanded')],
- [false, L._('never')],
- ['null', L._('hidden')]
- ],
-
- toJS: function () {
- var value = this.value();
- if (value !== 'expanded') value = L.FormBuilder.ControlChoice.prototype.toJS.call(this);
- return value;
- }
-
-});
+ toJS() {
+ let value = this.value()
+ if (value !== 'expanded')
+ value = L.FormBuilder.ControlChoice.prototype.toJS.call(this)
+ return value
+ },
+})
L.FormBuilder.OutlinkTarget = L.FormBuilder.MultiChoice.extend({
+ default: 'blank',
- default: 'blank',
-
- choices: [
- ['blank', L._('new window')],
- ['self', L._('iframe')],
- ['parent', L._('parent window')]
- ]
-
-});
+ choices: [
+ ['blank', L._('new window')],
+ ['self', L._('iframe')],
+ ['parent', L._('parent window')],
+ ],
+})
L.FormBuilder.Range = L.FormBuilder.Input.extend({
+ type() {
+ return 'range'
+ },
- type: function () {
- return 'range';
- },
-
- value: function () {
- return L.DomUtil.hasClass(this.wrapper, 'undefined') ? undefined : this.input.value;
- }
-
-});
-
+ value() {
+ return L.DomUtil.hasClass(this.wrapper, 'undefined') ? undefined : this.input.value
+ },
+})
L.FormBuilder.ManageOwner = L.FormBuilder.Element.extend({
-
- build: function () {
- var options = {
- className: 'edit-owner',
- on_select: L.bind(this.onSelect, this)
- };
- this.autocomplete = new L.U.AutoComplete.Ajax.Select(this.parentNode, options);
- var owner = this.toHTML();
- if (owner) this.autocomplete.displaySelected({'item': {'value': owner.id, 'label': owner.name}});
- },
-
- value: function () {
- return this._value;
- },
-
- onSelect: function (choice) {
- this._value = {
- 'id': choice.item.value,
- 'name': choice.item.label,
- 'url': choice.item.url
- };
- this.set();
+ build() {
+ const options = {
+ className: 'edit-owner',
+ on_select: L.bind(this.onSelect, this),
}
+ this.autocomplete = new L.U.AutoComplete.Ajax.Select(this.parentNode, options)
+ const owner = this.toHTML()
+ if (owner)
+ this.autocomplete.displaySelected({
+ item: { value: owner.id, label: owner.name },
+ })
+ },
+ value() {
+ return this._value
+ },
-});
-
+ onSelect({ item }) {
+ this._value = {
+ id: item.value,
+ name: item.label,
+ url: item.url,
+ }
+ this.set()
+ },
+})
L.FormBuilder.ManageEditors = L.FormBuilder.Element.extend({
-
- build: function () {
- var options = {
- className: 'edit-editors',
- on_select: L.bind(this.onSelect, this),
- on_unselect: L.bind(this.onUnselect, this)
- };
- this.autocomplete = new L.U.AutoComplete.Ajax.SelectMultiple(this.parentNode, options);
- this._values = this.toHTML();
- if (this._values) for (var i = 0; i < this._values.length; i++) this.autocomplete.displaySelected({'item': {'value': this._values[i].id, 'label': this._values[i].name}});
- },
-
- value: function () {
- return this._values;
- },
-
- onSelect: function (choice) {
- this._values.push({
- 'id': choice.item.value,
- 'name': choice.item.label,
- 'url': choice.item.url
- });
- this.set();
- },
-
- onUnselect: function (choice) {
- var index = this._values.findIndex(function (item) {return item.id === choice.item.value});
- if (index !== -1) {
- this._values.splice(index, 1);
- this.set();
- }
+ build() {
+ const options = {
+ className: 'edit-editors',
+ on_select: L.bind(this.onSelect, this),
+ on_unselect: L.bind(this.onUnselect, this),
}
+ this.autocomplete = new L.U.AutoComplete.Ajax.SelectMultiple(
+ this.parentNode,
+ options
+ )
+ this._values = this.toHTML()
+ if (this._values)
+ for (let i = 0; i < this._values.length; i++)
+ this.autocomplete.displaySelected({
+ item: { value: this._values[i].id, label: this._values[i].name },
+ })
+ },
-});
+ value() {
+ return this._values
+ },
+
+ onSelect({ item }) {
+ this._values.push({
+ id: item.value,
+ name: item.label,
+ url: item.url,
+ })
+ this.set()
+ },
+
+ onUnselect(choice) {
+ const index = this._values.findIndex(({ id }) => id === choice.item.value)
+ if (index !== -1) {
+ this._values.splice(index, 1)
+ this.set()
+ }
+ },
+})
L.U.FormBuilder = L.FormBuilder.extend({
+ options: {
+ className: 'umap-form',
+ },
- options: {
- className: 'umap-form'
+ defaultOptions: {
+ name: { label: L._('name') },
+ description: {
+ label: L._('description'),
+ handler: 'Textarea',
+ helpEntries: 'textFormatting',
},
-
- defaultOptions: {
- name: {label: L._('name')},
- description: {label: L._('description'), handler: 'Textarea', helpEntries: 'textFormatting'},
- color: {handler: 'ColorPicker', label: L._('color'), helpEntries: 'colorValue', inheritable: true},
- opacity: {handler: 'Range', min: 0.1, max: 1, step: 0.1, label: L._('opacity'), inheritable: true},
- stroke: {handler: 'Switch', label: L._('stroke'), helpEntries: 'stroke', inheritable: true},
- weight: {handler: 'Range', min: 1, max: 20, step: 1, label: L._('weight'), inheritable: true},
- fill: {handler: 'Switch', label: L._('fill'), helpEntries: 'fill', inheritable: true},
- fillColor: {handler: 'ColorPicker', label: L._('fill color'), helpEntries: 'fillColor', inheritable: true},
- fillOpacity: {handler: 'Range', min: 0.1, max: 1, step: 0.1, label: L._('fill opacity'), inheritable: true},
- smoothFactor: {handler: 'Range', min: 0, max: 10, step: 0.5, label: L._('Simplify'), helpEntries: 'smoothFactor', inheritable: true},
- dashArray: {label: L._('dash array'), helpEntries: 'dashArray', inheritable: true},
- iconClass: {handler: 'IconClassSwitcher', label: L._('Icon shape'), inheritable: true},
- iconUrl: {handler: 'IconUrl', label: L._('Icon symbol'), inheritable: true, helpText: L.U.Help.formatIconSymbol},
- popupShape: {handler: 'PopupShape', label: L._('Popup shape'), inheritable: true},
- popupTemplate: {handler: 'PopupContent', label: L._('Popup content style'), inheritable: true},
- popupContentTemplate: {label: L._('Popup content template'), handler: 'Textarea', helpEntries: ['dynamicProperties', 'textFormatting'], placeholder: '# {name}', inheritable: true},
- datalayer: {handler: 'DataLayerSwitcher', label: L._('Choose the layer of the feature')},
- moreControl: {handler: 'Switch', label: L._('Do you want to display the «more» control?')},
- scrollWheelZoom: {handler: 'Switch', label: L._('Allow scroll wheel zoom?')},
- miniMap: {handler: 'Switch', label: L._('Do you want to display a minimap?')},
- scaleControl: {handler: 'Switch', label: L._('Do you want to display the scale control?')},
- onLoadPanel: {handler: 'onLoadPanel', label: L._('Do you want to display a panel on load?')},
- displayPopupFooter: {handler: 'Switch', label: L._('Do you want to display popup footer?')},
- captionBar: {handler: 'Switch', label: L._('Do you want to display a caption bar?')},
- captionMenus: {handler: 'Switch', label: L._('Do you want to display caption menus?')},
- zoomTo: {handler: 'IntInput', placeholder: L._('Inherit'), helpEntries: 'zoomTo', label: L._('Default zoom level'), inheritable: true},
- showLabel: {handler: 'LabelChoice', label: L._('Display label'), inheritable: true},
- labelDirection: {handler: 'LabelDirection', label: L._('Label direction'), inheritable: true},
- labelInteractive: {handler: 'Switch', label: L._('Labels are clickable'), inheritable: true},
- labelKey: {helpEntries: 'labelKey', placeholder: L._('Default: name'), label: L._('Label key'), inheritable: true},
- zoomControl: {handler: 'ControlChoice', label: L._('Display the zoom control')},
- searchControl: {handler: 'ControlChoice', label: L._('Display the search control')},
- fullscreenControl: {handler: 'ControlChoice', label: L._('Display the fullscreen control')},
- embedControl: {handler: 'ControlChoice', label: L._('Display the embed control')},
- locateControl: {handler: 'ControlChoice', label: L._('Display the locate control')},
- measureControl: {handler: 'ControlChoice', label: L._('Display the measure control')},
- tilelayersControl: {handler: 'ControlChoice', label: L._('Display the tile layers control')},
- editinosmControl: {handler: 'ControlChoice', label: L._('Display the control to open OpenStreetMap editor')},
- datalayersControl: {handler: 'DataLayersControl', label: L._('Display the data layers control')},
+ color: {
+ handler: 'ColorPicker',
+ label: L._('color'),
+ helpEntries: 'colorValue',
+ inheritable: true,
},
-
- initialize: function (obj, fields, options) {
- this.map = obj.getMap();
- L.FormBuilder.prototype.initialize.call(this, obj, fields, options);
- this.on('finish', this.finish);
+ opacity: {
+ handler: 'Range',
+ min: 0.1,
+ max: 1,
+ step: 0.1,
+ label: L._('opacity'),
+ inheritable: true,
},
-
- setter: function (field, value) {
- L.FormBuilder.prototype.setter.call(this, field, value);
- this.obj.isDirty = true;
+ stroke: {
+ handler: 'Switch',
+ label: L._('stroke'),
+ helpEntries: 'stroke',
+ inheritable: true,
},
+ weight: {
+ handler: 'Range',
+ min: 1,
+ max: 20,
+ step: 1,
+ label: L._('weight'),
+ inheritable: true,
+ },
+ fill: {
+ handler: 'Switch',
+ label: L._('fill'),
+ helpEntries: 'fill',
+ inheritable: true,
+ },
+ fillColor: {
+ handler: 'ColorPicker',
+ label: L._('fill color'),
+ helpEntries: 'fillColor',
+ inheritable: true,
+ },
+ fillOpacity: {
+ handler: 'Range',
+ min: 0.1,
+ max: 1,
+ step: 0.1,
+ label: L._('fill opacity'),
+ inheritable: true,
+ },
+ smoothFactor: {
+ handler: 'Range',
+ min: 0,
+ max: 10,
+ step: 0.5,
+ label: L._('Simplify'),
+ helpEntries: 'smoothFactor',
+ inheritable: true,
+ },
+ dashArray: {
+ label: L._('dash array'),
+ helpEntries: 'dashArray',
+ inheritable: true,
+ },
+ iconClass: {
+ handler: 'IconClassSwitcher',
+ label: L._('Icon shape'),
+ inheritable: true,
+ },
+ iconUrl: {
+ handler: 'IconUrl',
+ label: L._('Icon symbol'),
+ inheritable: true,
+ helpText: L.U.Help.formatIconSymbol,
+ },
+ popupShape: { handler: 'PopupShape', label: L._('Popup shape'), inheritable: true },
+ popupTemplate: {
+ handler: 'PopupContent',
+ label: L._('Popup content style'),
+ inheritable: true,
+ },
+ popupContentTemplate: {
+ label: L._('Popup content template'),
+ handler: 'Textarea',
+ helpEntries: ['dynamicProperties', 'textFormatting'],
+ placeholder: '# {name}',
+ inheritable: true,
+ },
+ datalayer: {
+ handler: 'DataLayerSwitcher',
+ label: L._('Choose the layer of the feature'),
+ },
+ moreControl: {
+ handler: 'Switch',
+ label: L._('Do you want to display the «more» control?'),
+ },
+ scrollWheelZoom: { handler: 'Switch', label: L._('Allow scroll wheel zoom?') },
+ miniMap: { handler: 'Switch', label: L._('Do you want to display a minimap?') },
+ scaleControl: {
+ handler: 'Switch',
+ label: L._('Do you want to display the scale control?'),
+ },
+ onLoadPanel: {
+ handler: 'onLoadPanel',
+ label: L._('Do you want to display a panel on load?'),
+ },
+ displayPopupFooter: {
+ handler: 'Switch',
+ label: L._('Do you want to display popup footer?'),
+ },
+ captionBar: {
+ handler: 'Switch',
+ label: L._('Do you want to display a caption bar?'),
+ },
+ captionMenus: {
+ handler: 'Switch',
+ label: L._('Do you want to display caption menus?'),
+ },
+ zoomTo: {
+ handler: 'IntInput',
+ placeholder: L._('Inherit'),
+ helpEntries: 'zoomTo',
+ label: L._('Default zoom level'),
+ inheritable: true,
+ },
+ showLabel: {
+ handler: 'LabelChoice',
+ label: L._('Display label'),
+ inheritable: true,
+ },
+ labelDirection: {
+ handler: 'LabelDirection',
+ label: L._('Label direction'),
+ inheritable: true,
+ },
+ labelInteractive: {
+ handler: 'Switch',
+ label: L._('Labels are clickable'),
+ inheritable: true,
+ },
+ labelKey: {
+ helpEntries: 'labelKey',
+ placeholder: L._('Default: name'),
+ label: L._('Label key'),
+ inheritable: true,
+ },
+ zoomControl: { handler: 'ControlChoice', label: L._('Display the zoom control') },
+ searchControl: {
+ handler: 'ControlChoice',
+ label: L._('Display the search control'),
+ },
+ fullscreenControl: {
+ handler: 'ControlChoice',
+ label: L._('Display the fullscreen control'),
+ },
+ embedControl: { handler: 'ControlChoice', label: L._('Display the embed control') },
+ locateControl: {
+ handler: 'ControlChoice',
+ label: L._('Display the locate control'),
+ },
+ measureControl: {
+ handler: 'ControlChoice',
+ label: L._('Display the measure control'),
+ },
+ tilelayersControl: {
+ handler: 'ControlChoice',
+ label: L._('Display the tile layers control'),
+ },
+ editinosmControl: {
+ handler: 'ControlChoice',
+ label: L._('Display the control to open OpenStreetMap editor'),
+ },
+ datalayersControl: {
+ handler: 'DataLayersControl',
+ label: L._('Display the data layers control'),
+ },
+ },
- finish: function () {
- this.map.ui.closePanel();
- }
+ initialize(obj, fields, options) {
+ this.map = obj.getMap()
+ L.FormBuilder.prototype.initialize.call(this, obj, fields, options)
+ this.on('finish', this.finish)
+ },
-});
+ setter(field, value) {
+ L.FormBuilder.prototype.setter.call(this, field, value)
+ this.obj.isDirty = true
+ },
+
+ finish() {
+ this.map.ui.closePanel()
+ },
+})
diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js
index 605598e1..33abdb86 100644
--- a/umap/static/umap/js/umap.js
+++ b/umap/static/umap/js/umap.js
@@ -1,1818 +1,2199 @@
L.Map.mergeOptions({
- overlay: null,
- datalayers: [],
- center: [4, 50],
- zoom: 6,
- hash: true,
- default_color: 'DarkBlue',
- default_smoothFactor: 1.0,
- default_opacity: 0.5,
- default_fillOpacity: 0.3,
- default_stroke: true,
- default_fill: true,
- default_weight: 3,
- default_iconClass: 'Default',
- default_popupContentTemplate: '# {name}\n{description}',
- default_interactive: true,
- default_labelDirection: 'auto',
- attributionControl: false,
- allowEdit: true,
- embedControl: true,
- zoomControl: true,
- datalayersControl: true,
- searchControl: true,
- editInOSMControl: false,
- editInOSMControlOptions: false,
- scaleControl: true,
- noControl: false, // Do not render any control.
- miniMap: false,
- name: '',
- description: '',
- displayPopupFooter: false,
- demoTileInfos: {s: 'a', z: 9, x: 265, y: 181, r: ''},
- licences: [],
- licence: '',
- enableMarkerDraw: true,
- enablePolygonDraw: true,
- enablePolylineDraw: true,
- limitBounds: {},
- importPresets: [
- // {url: 'http://localhost:8019/en/datalayer/1502/', label: 'Simplified World Countries', format: 'geojson'}
- ],
- moreControl: true,
- captionBar: false,
- captionMenus: true,
- slideshow: {},
- clickable: true,
- easing: false,
- permissions: {},
- permanentCreditBackground: true,
-});
+ overlay: null,
+ datalayers: [],
+ center: [4, 50],
+ zoom: 6,
+ hash: true,
+ default_color: 'DarkBlue',
+ default_smoothFactor: 1.0,
+ default_opacity: 0.5,
+ default_fillOpacity: 0.3,
+ default_stroke: true,
+ default_fill: true,
+ default_weight: 3,
+ default_iconClass: 'Default',
+ default_popupContentTemplate: '# {name}\n{description}',
+ default_interactive: true,
+ default_labelDirection: 'auto',
+ attributionControl: false,
+ allowEdit: true,
+ embedControl: true,
+ zoomControl: true,
+ datalayersControl: true,
+ searchControl: true,
+ editInOSMControl: false,
+ editInOSMControlOptions: false,
+ scaleControl: true,
+ noControl: false, // Do not render any control.
+ miniMap: false,
+ name: '',
+ description: '',
+ displayPopupFooter: false,
+ demoTileInfos: { s: 'a', z: 9, x: 265, y: 181, r: '' },
+ licences: [],
+ licence: '',
+ enableMarkerDraw: true,
+ enablePolygonDraw: true,
+ enablePolylineDraw: true,
+ limitBounds: {},
+ importPresets: [
+ // {url: 'http://localhost:8019/en/datalayer/1502/', label: 'Simplified World Countries', format: 'geojson'}
+ ],
+ moreControl: true,
+ captionBar: false,
+ captionMenus: true,
+ slideshow: {},
+ clickable: true,
+ easing: false,
+ permissions: {},
+ permanentCreditBackground: true,
+})
L.U.Map.include({
-
- HIDDABLE_CONTROLS: ['zoom', 'search', 'fullscreen', 'embed', 'locate', 'measure', 'tilelayers', 'editinosm', 'datalayers'],
-
- initialize: function (el, geojson) {
-
- if (geojson.properties && geojson.properties.locale) L.setLocale(geojson.properties.locale);
-
- // Don't let default autocreation of controls
- var zoomControl = typeof geojson.properties.zoomControl !== 'undefined' ? geojson.properties.zoomControl : true;
- geojson.properties.zoomControl = false;
- var fullscreenControl = typeof geojson.properties.fullscreenControl !== 'undefined' ? geojson.properties.fullscreenControl : true;
- geojson.properties.fullscreenControl = false;
- L.Util.setBooleanFromQueryString(geojson.properties, 'scrollWheelZoom');
- L.Map.prototype.initialize.call(this, el, geojson.properties);
-
- this.ui = new L.U.UI(this._container);
- this.xhr = new L.U.Xhr(this.ui);
- this.xhr.on('dataloding', function (e) {
- this.fire('dataloding', e);
- });
- this.xhr.on('datalaod', function (e) {
- this.fire('datalaod', e);
- });
-
- this.initLoader();
- this.name = this.options.name;
- this.description = this.options.description;
- this.demoTileInfos = this.options.demoTileInfos;
- if (geojson.geometry) this.options.center = geojson.geometry;
- this.options.zoomControl = zoomControl;
- this.options.fullscreenControl = fullscreenControl;
- L.Util.setBooleanFromQueryString(this.options, 'moreControl');
- L.Util.setBooleanFromQueryString(this.options, 'scaleControl');
- L.Util.setBooleanFromQueryString(this.options, 'miniMap');
- L.Util.setBooleanFromQueryString(this.options, 'allowEdit');
- L.Util.setBooleanFromQueryString(this.options, 'displayDataBrowserOnLoad');
- L.Util.setBooleanFromQueryString(this.options, 'displayCaptionOnLoad');
- L.Util.setBooleanFromQueryString(this.options, 'captionBar');
- L.Util.setBooleanFromQueryString(this.options, 'captionMenus');
- for (var i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
- L.Util.setNullableBooleanFromQueryString(this.options, this.HIDDABLE_CONTROLS[i] + 'Control');
- }
- this.datalayersOnLoad = L.Util.queryString('datalayers');
- this.options.onLoadPanel = L.Util.queryString('onLoadPanel', this.options.onLoadPanel);
- if (this.datalayersOnLoad) this.datalayersOnLoad = this.datalayersOnLoad.toString().split(',');
-
- if (L.Browser.ielt9) this.options.allowEdit = false; // TODO include ie9
-
- var editedFeature = null,
- self = this;
- try {
- Object.defineProperty(this, 'editedFeature', {
- get: function () {
- return editedFeature;
- },
- set: function (feature) {
- if (editedFeature && editedFeature !== feature) {
- editedFeature.endEdit();
- }
- editedFeature = feature;
- self.fire('seteditedfeature');
- }
- });
- }
- catch (e) {
- // Certainly IE8, which has a limited version of defineProperty
- }
-
- if (this.options.hash) this.addHash();
- this.initCenter();
- this.handleLimitBounds();
-
- this.initTileLayers(this.options.tilelayers);
-
- // Global storage for retrieving datalayers and features
- this.datalayers = {};
- this.datalayers_index = [];
- this.dirty_datalayers = [];
- this.features_index = {};
-
- // Retrocompat
- if (this.options.slideshow && this.options.slideshow.delay && this.options.slideshow.active === undefined) this.options.slideshow.active = true;
-
- this.initControls();
-
- // create datalayers
- this.initDatalayers();
-
- if (this.options.displayCaptionOnLoad) {
- // Retrocompat
- if (!this.options.onLoadPanel) {
- this.options.onLoadPanel = 'caption';
- }
- delete this.options.displayCaptionOnLoad;
- }
- if (this.options.displayDataBrowserOnLoad) {
- // Retrocompat
- if (!this.options.onLoadPanel) {
- this.options.onLoadPanel = 'databrowser';
- }
- delete this.options.displayDataBrowserOnLoad;
- }
-
- this.ui.on('panel:closed', function () {
- this.invalidateSize({pan: false});
- }, this);
-
- var isDirty = false; // global status
- try {
- Object.defineProperty(this, 'isDirty', {
- get: function () {
- return isDirty || this.dirty_datalayers.length;
- },
- set: function (status) {
- if (!isDirty && status) self.fire('isdirty');
- isDirty = status;
- self.checkDirty();
- }
- });
- }
- catch (e) {
- // Certainly IE8, which has a limited version of defineProperty
- }
- this.on('baselayerchange', function (e) {
- if (this._controls.miniMap) this._controls.miniMap.onMainMapBaseLayerChange(e);
- }, this);
-
- // Creation mode
- if (!this.options.umap_id) {
- this.isDirty = true;
- this._default_extent = true;
- this.options.name = L._('Untitled map');
- this.options.allowEdit = true;
- var datalayer = this.createDataLayer();
- datalayer.connectToMap();
- this.enableEdit();
- var dataUrl = L.Util.queryString('dataUrl', null),
- dataFormat = L.Util.queryString('dataFormat', 'geojson');
- if (dataUrl) {
- dataUrl = decodeURIComponent(dataUrl);
- dataUrl = this.localizeUrl(dataUrl);
- dataUrl = this.proxyUrl(dataUrl);
- datalayer.importFromUrl(dataUrl, dataFormat);
- }
- }
-
- this.help = new L.U.Help(this);
- this.slideshow = new L.U.Slideshow(this, this.options.slideshow);
- this.permissions = new L.U.MapPermissions(this);
- this.initCaptionBar();
- if (this.options.allowEdit) {
- this.editTools = new L.U.Editable(this);
- this.ui.on('panel:closed panel:open', function () {
- this.editedFeature = null;
- }, this);
- this.initEditBar();
- }
- this.initShortcuts();
- this.onceDatalayersLoaded(function () {
- if (this.options.onLoadPanel === 'databrowser') this.openBrowser();
- else if (this.options.onLoadPanel === 'caption') this.displayCaption();
- else if (this.options.onLoadPanel === 'datafilters') this.openFilter();
- });
- this.onceDataLoaded(function () {
- const slug = L.Util.queryString('feature');
- if (slug && this.features_index[slug]) this.features_index[slug].view();
- });
-
-
- window.onbeforeunload = function (e) {
- var msg = L._('You have unsaved changes.');
- if (self.isDirty) {
- e.returnValue = msg;
- return msg;
- }
- };
- this.backup();
- this.initContextMenu();
- this.on('click contextmenu.show', this.closeInplaceToolbar);
- },
-
- initControls: function () {
- this.helpMenuActions = {};
- this._controls = {};
-
- if (this.options.allowEdit && !this.options.noControl) {
- new L.U.EditControl(this).addTo(this);
-
- new L.U.DrawToolbar({map: this}).addTo(this);
-
- var editActions = [
- L.U.ImportAction,
- L.U.EditPropertiesAction,
- L.U.ChangeTileLayerAction,
- L.U.ManageDatalayersAction,
- L.U.UpdateExtentAction,
- L.U.UpdatePermsAction
- ];
- new L.U.SettingsToolbar({actions: editActions}).addTo(this);
- }
- this._controls.zoom = new L.Control.Zoom({zoomInTitle: L._('Zoom in'), zoomOutTitle: L._('Zoom out')});
- this._controls.datalayers = new L.U.DataLayersControl(this);
- this._controls.locate = L.control.locate({
- strings: {
- title: L._('Center map on your location'),
- },
- showPopup: false,
- // We style this control in our own CSS for consistency with other controls,
- // but the control breaks if we don't specify a class here, so a fake class
- // will do.
- icon: 'umap-fake-class',
- iconLoading: 'umap-fake-class'
- });
- this._controls.fullscreen = new L.Control.Fullscreen({title: {'false': L._('View Fullscreen'), 'true': L._('Exit Fullscreen')}});
- this._controls.search = new L.U.SearchControl();
- this._controls.embed = new L.Control.Embed(this, this.options.embedOptions);
- this._controls.tilelayers = new L.U.TileLayerControl(this);
- this._controls.editinosm = new L.Control.EditInOSM({
- position: 'topleft',
- widgetOptions: {helpText: L._('Open this map extent in a map editor to provide more accurate data to OpenStreetMap')}
- });
- this._controls.measure = (new L.MeasureControl()).initHandler(this);
- this._controls.more = new L.U.MoreControls();
- this._controls.scale = L.control.scale();
- this._controls.permanentCredit = new L.U.PermanentCreditsControl(this);
- if (this.options.scrollWheelZoom) this.scrollWheelZoom.enable();
- else this.scrollWheelZoom.disable();
- this.renderControls();
- },
-
- renderControls: function () {
- L.DomUtil.classIf(document.body, 'umap-caption-bar-enabled', this.options.captionBar || (this.options.slideshow && this.options.slideshow.active));
- L.DomUtil.classIf(document.body, 'umap-slideshow-enabled', this.options.slideshow && this.options.slideshow.active);
- for (var i in this._controls) {
- this.removeControl(this._controls[i]);
- }
- if (this.options.noControl) return;
-
- this._controls.attribution = (new L.U.AttributionControl()).addTo(this);
- if (this.options.miniMap && !this.options.noControl) {
- this.whenReady(function () {
- if (this.selected_tilelayer) {
- this._controls.miniMap = new L.Control.MiniMap(this.selected_tilelayer).addTo(this);
- this._controls.miniMap._miniMap.invalidateSize();
- }
- });
- }
- var name, status, control;
- for (var i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
- name = this.HIDDABLE_CONTROLS[i];
- status = this.options[name + 'Control'];
- if (status === false) continue;
- control = this._controls[name];
- control.addTo(this);
- if (status === undefined || status === null) L.DomUtil.addClass(control._container, 'display-on-more');
- else L.DomUtil.removeClass(control._container, 'display-on-more');
- }
- if (this.options.permanentCredit) this._controls.permanentCredit.addTo(this);
- if (this.options.moreControl) this._controls.more.addTo(this);
- if (this.options.scaleControl) this._controls.scale.addTo(this);
- },
-
- initDatalayers: function () {
- var toload = dataToload = seen = this.options.datalayers.length,
- self = this,
- datalayer;
- var loaded = function () {
- self.datalayersLoaded = true;
- self.fire('datalayersloaded');
- };
- var decrementToLoad = function () {
- toload--;
- if (toload === 0) loaded();
- };
- var dataLoaded = function () {
- self.dataLoaded = true;
- self.fire('dataloaded');
- };
- var decrementDataToLoad = function () {
- dataToload--;
- if (dataToload === 0) dataLoaded();
- };
- for (var j = 0; j < this.options.datalayers.length; j++) {
- datalayer = this.createDataLayer(this.options.datalayers[j]);
- if (datalayer.displayedOnLoad()) datalayer.onceLoaded(decrementToLoad);
- else decrementToLoad();
- if (datalayer.displayedOnLoad()) datalayer.onceDataLoaded(decrementDataToLoad);
- else decrementDataToLoad();
- }
- if (seen === 0) loaded() && dataLoaded(); // no datalayer
- },
-
- indexDatalayers: function () {
- var panes = this.getPane('overlayPane'),
- pane;
- this.datalayers_index = [];
- for (var i = 0; i < panes.children.length; i++) {
- pane = panes.children[i];
- if (!pane.dataset || !pane.dataset.id) continue;
- this.datalayers_index.push(this.datalayers[pane.dataset.id]);
- }
- this.updateDatalayersControl();
- },
-
- ensurePanesOrder: function () {
- this.eachDataLayer(function (datalayer) {
- datalayer.bringToTop();
- });
- },
-
- onceDatalayersLoaded: function (callback, context) {
- // Once datalayers **metadata** have been loaded
- if (this.datalayersLoaded) {
- callback.call(context || this, this);
- } else {
- this.once('datalayersloaded', callback, context);
- }
- return this;
- },
-
- onceDataLoaded: function (callback, context) {
- // Once datalayers **data** have been loaded
- if (this.dataLoaded) {
- callback.call(context || this, this);
- } else {
- this.once('dataloaded', callback, context);
- }
- return this;
- },
-
- updateDatalayersControl: function () {
- if (this._controls.datalayers) this._controls.datalayers.update();
- },
-
- backupOptions: function () {
- this._backupOptions = L.extend({}, this.options);
- this._backupOptions.tilelayer = L.extend({}, this.options.tilelayer);
- this._backupOptions.limitBounds = L.extend({}, this.options.limitBounds);
- this._backupOptions.permissions = L.extend({}, this.permissions.options);
- },
-
- resetOptions: function () {
- this.options = L.extend({}, this._backupOptions);
- this.options.tilelayer = L.extend({}, this._backupOptions.tilelayer);
- this.permissions.options = L.extend({}, this._backupOptions.permissions);
- },
-
- initShortcuts: function () {
- var globalShortcuts = function (e) {
- var key = e.keyCode,
- modifierKey = e.ctrlKey || e.metaKey;
-
- /* Generic shortcuts */
- if (key === L.U.Keys.F && modifierKey) {
- L.DomEvent.stop(e);
- this.search();
- } else if (e.keyCode === L.U.Keys.ESC) {
- if (this.help.visible()) this.help.hide();
- else this.ui.closePanel();
- }
-
- if (!this.options.allowEdit) return;
-
- /* Edit mode only shortcuts */
- if (key === L.U.Keys.E && modifierKey && !this.editEnabled) {
- L.DomEvent.stop(e);
- this.enableEdit();
- } else if (key === L.U.Keys.E && modifierKey && this.editEnabled && !this.isDirty) {
- L.DomEvent.stop(e);
- this.disableEdit();
- this.ui.closePanel();
- }
- if (key === L.U.Keys.S && modifierKey) {
- L.DomEvent.stop(e);
- if (this.isDirty) {
- this.save();
- }
- }
- if (key === L.U.Keys.Z && modifierKey && this.isDirty) {
- L.DomEvent.stop(e);
- this.askForReset();
- }
- if (key === L.U.Keys.M && modifierKey && this.editEnabled) {
- L.DomEvent.stop(e);
- this.editTools.startMarker();
- }
- if (key === L.U.Keys.P && modifierKey && this.editEnabled) {
- L.DomEvent.stop(e);
- this.editTools.startPolygon();
- }
- if (key === L.U.Keys.L && modifierKey && this.editEnabled) {
- L.DomEvent.stop(e);
- this.editTools.startPolyline();
- }
- if (key === L.U.Keys.I && modifierKey && this.editEnabled) {
- L.DomEvent.stop(e);
- this.importPanel();
- }
- if (key === L.U.Keys.H && modifierKey && this.editEnabled) {
- L.DomEvent.stop(e);
- this.help.show('edit');
- }
- if (e.keyCode === L.U.Keys.ESC) {
- if (this.editEnabled) this.editTools.stopDrawing();
- if (this.measureTools.enabled()) this.measureTools.stopDrawing();
- }
- };
- L.DomEvent.addListener(document, 'keydown', globalShortcuts, this);
- },
-
- initTileLayers: function () {
- this.tilelayers = [];
- for(var i in this.options.tilelayers) {
- if(this.options.tilelayers.hasOwnProperty(i)) {
- this.tilelayers.push(this.createTileLayer(this.options.tilelayers[i]));
- if (this.options.tilelayer && this.options.tilelayer.url_template === this.options.tilelayers[i].url_template) {
- // Keep control over the displayed attribution for non custom tilelayers
- this.options.tilelayer.attribution = this.options.tilelayers[i].attribution;
- }
- }
- }
- if (this.options.tilelayer && this.options.tilelayer.url_template && this.options.tilelayer.attribution) {
- this.customTilelayer = this.createTileLayer(this.options.tilelayer);
- this.selectTileLayer(this.customTilelayer);
- } else {
- this.selectTileLayer(this.tilelayers[0]);
- }
- },
-
- createTileLayer: function (tilelayer) {
- return new L.TileLayer(tilelayer.url_template, tilelayer);
- },
-
- selectTileLayer: function (tilelayer) {
- if (tilelayer === this.selected_tilelayer) { return; }
- try {
- this.addLayer(tilelayer);
- this.fire('baselayerchange', {layer: tilelayer});
- if (this.selected_tilelayer) {
- this.removeLayer(this.selected_tilelayer);
- }
- this.selected_tilelayer = tilelayer;
- if (!isNaN(this.selected_tilelayer.options.minZoom) && this.getZoom() < this.selected_tilelayer.options.minZoom) {
- this.setZoom(this.selected_tilelayer.options.minZoom);
- }
- if (!isNaN(this.selected_tilelayer.options.maxZoom) && this.getZoom() > this.selected_tilelayer.options.maxZoom) {
- this.setZoom(this.selected_tilelayer.options.maxZoom);
- }
- } catch (e) {
- this.removeLayer(tilelayer);
- this.ui.alert({content: L._('Error in the tilelayer URL') + ': ' + tilelayer._url, level: 'error'});
- // Users can put tilelayer URLs by hand, and if they add wrong {variable},
- // Leaflet throw an error, and then the map is no more editable
- }
- this.setOverlay()
- },
-
- eachTileLayer: function (method, context) {
- var urls = [];
- for (var i in this.tilelayers) {
- if (this.tilelayers.hasOwnProperty(i)) {
- method.call(context, this.tilelayers[i]);
- urls.push(this.tilelayers[i]._url);
- }
- }
- if (this.customTilelayer && (Array.prototype.indexOf && urls.indexOf(this.customTilelayer._url) === -1)) {
- method.call(context || this, this.customTilelayer);
- }
- },
-
- setOverlay: function () {
- if (!this.options.overlay || !this.options.overlay.url_template) return;
- var overlay = this.createTileLayer(this.options.overlay);
- try {
- this.addLayer(overlay);
- if (this.overlay) this.removeLayer(this.overlay);
- this.overlay = overlay;
- } catch (e) {
- this.removeLayer(overlay);
- console.error(e);
- this.ui.alert({content: L._('Error in the overlay URL') + ': ' + overlay._url, level: 'error'});
- }
- },
-
- initCenter: function () {
- if (this.options.hash && this._hash.parseHash(location.hash)) {
- // FIXME An invalid hash will cause the load to fail
- this._hash.update();
- }
- else if(this.options.locate && this.options.locate.setView) {
- // Prevent from making two setViews at init
- // which is not very fluid...
- this.locate(this.options.locate);
- }
- else {
- this.options.center = this.latLng(this.options.center);
- this.setView(this.options.center, this.options.zoom);
- }
- },
-
- latLng: function(a, b, c) {
- // manage geojson case and call original method
- if (!(a instanceof L.LatLng) && a.coordinates) {
- // Guess it's a geojson
- a = [a.coordinates[1], a.coordinates[0]];
- }
- return L.latLng(a, b, c);
- },
-
- handleLimitBounds: function () {
- var south = parseFloat(this.options.limitBounds.south),
- west = parseFloat(this.options.limitBounds.west),
- north = parseFloat(this.options.limitBounds.north),
- east = parseFloat(this.options.limitBounds.east);
- if (!isNaN(south) && !isNaN(west) && !isNaN(north) && !isNaN(east)) {
- var bounds = L.latLngBounds([[south, west], [north, east]]);
- this.options.minZoom = this.getBoundsZoom(bounds, false);
- try {
- this.setMaxBounds(bounds);
- } catch (e) {
- // Unusable bounds, like -2 -2 -2 -2?
- console.error('Error limiting bounds', e);
- }
- } else {
- this.options.minZoom = 0;
- this.setMaxBounds();
- }
- },
-
- setMaxBounds: function (bounds) {
- // Hack. Remove me when fix is released:
- // https://github.com/Leaflet/Leaflet/pull/4494
- bounds = L.latLngBounds(bounds);
-
- if (!bounds.isValid()) {
- this.options.maxBounds = null;
- return this.off('moveend', this._panInsideMaxBounds);
- }
- return L.Map.prototype.setMaxBounds.call(this, bounds);
- },
-
- createDataLayer: function(datalayer) {
- datalayer = datalayer || {name: L._('Layer') + ' ' + (this.datalayers_index.length + 1)};
- return new L.U.DataLayer(this, datalayer);
- },
-
- getDefaultOption: function (option) {
- return this.options['default_' + option];
- },
-
- getOption: function (option) {
- if (L.Util.usableOption(this.options, option)) return this.options[option];
- return this.getDefaultOption(option);
- },
-
- updateExtent: function() {
- this.options.center = this.getCenter();
- this.options.zoom = this.getZoom();
- this.isDirty = true;
- this._default_extent = false;
- this.ui.alert({content: L._('The zoom and center have been set.'), 'level': 'info'});
- },
-
- updateTileLayers: function () {
- var self = this,
- callback = function (tilelayer) {
- self.options.tilelayer = tilelayer.toJSON();
- self.isDirty = true;
- };
- if (this._controls.tilelayers) this._controls.tilelayers.openSwitcher({callback: callback, className: 'dark'});
- },
-
- manageDatalayers: function () {
- if (this._controls.datalayers) this._controls.datalayers.openPanel();
- },
-
- renderShareBox: function () {
- var container = L.DomUtil.create('div', 'umap-share'),
- embedTitle = L.DomUtil.add('h4', '', container, L._('Embed the map')),
- iframe = L.DomUtil.create('textarea', 'umap-share-iframe', container),
- option;
- var UIFields = [
- ['dimensions.width', {handler: 'Input', label: L._('width')}],
- ['dimensions.height', {handler: 'Input', label: L._('height')}],
- ['options.includeFullScreenLink', {handler: 'Switch', label: L._('Include full screen link?')}],
- ['options.currentView', {handler: 'Switch', label: L._('Current view instead of default map view?')}],
- ['options.keepCurrentDatalayers', {handler: 'Switch', label: L._('Keep current visible layers')}],
- ['options.viewCurrentFeature', {handler: 'Switch', label: L._('Open current feature on load')}],
- 'queryString.moreControl',
- 'queryString.scrollWheelZoom',
- 'queryString.miniMap',
- 'queryString.scaleControl',
- 'queryString.onLoadPanel',
- 'queryString.captionBar',
- 'queryString.captionMenus'
- ];
- for (var i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
- UIFields.push('queryString.' + this.HIDDABLE_CONTROLS[i] + 'Control');
- }
- var iframeExporter = new L.U.IframeExporter(this);
- var buildIframeCode = function () {
- iframe.innerHTML = iframeExporter.build();
- };
- buildIframeCode();
- var builder = new L.U.FormBuilder(iframeExporter, UIFields, {
- callback: buildIframeCode
- });
- var iframeOptions = L.DomUtil.createFieldset(container, L._('Iframe export options'));
- iframeOptions.appendChild(builder.build());
- if (this.options.shortUrl) {
- L.DomUtil.create('hr', '', container);
- L.DomUtil.add('h4', '', container, L._('Short URL'));
- var shortUrl = L.DomUtil.create('input', 'umap-short-url', container);
- shortUrl.type = 'text';
- shortUrl.value = this.options.shortUrl;
- }
- L.DomUtil.create('hr', '', container);
- L.DomUtil.add('h4', '', container, L._('Download data'));
- var typeInput = L.DomUtil.create('select', '', container);
- typeInput.name = 'format';
- var exportCaveat = L.DomUtil.add('small', 'help-text', container, L._('Only visible features will be downloaded.'));
- exportCaveat.id = 'export_caveat_text';
- var toggleCaveat = function () {
- if (typeInput.value === 'umap') exportCaveat.style.display = 'none';
- else exportCaveat.style.display = 'inherit';
- }
- L.DomEvent.on(typeInput, 'change', toggleCaveat);
- var types = {
- geojson: {
- formatter: function (map) {return JSON.stringify(map.toGeoJSON(), null, 2);},
- ext: '.geojson',
- filetype: 'application/json'
- },
- gpx: {
- formatter: function (map) {return togpx(map.toGeoJSON());},
- ext: '.gpx',
- filetype: 'application/xml'
- },
- kml: {
- formatter: function (map) {return tokml(map.toGeoJSON());},
- ext: '.kml',
- filetype: 'application/vnd.google-earth.kml+xml'
- },
- umap: {
- name: L._('Full map data'),
- formatter: function (map) {return map.serialize();},
- ext: '.umap',
- filetype: 'application/json',
- selected: true
- }
- };
- for (var key in types) {
- if (types.hasOwnProperty(key)) {
- option = L.DomUtil.create('option', '', typeInput);
- option.value = key;
- option.textContent = types[key].name || key;
- if (types[key].selected) option.selected = true;
- }
- }
- toggleCaveat();
- var download = L.DomUtil.create('a', 'button', container);
- download.textContent = L._('Download data');
- L.DomEvent.on(download, 'click', function () {
- var type = types[typeInput.value],
- content = type.formatter(this),
- name = this.options.name || 'data';
- name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
- download.download = name + type.ext;
- window.URL = window.URL || window.webkitURL;
- var blob = new Blob([content], {type: type.filetype});
- download.href = window.URL.createObjectURL(blob);
- }, this);
- this.ui.openPanel({data: {html: container}});
- },
-
- toGeoJSON: function () {
- var features = [];
- this.eachDataLayer(function (datalayer) {
- if (datalayer.isVisible()) {
- features = features.concat(datalayer.featuresToGeoJSON());
- }
- });
- var geojson = {
- type: 'FeatureCollection',
- features: features
- };
- return geojson;
- },
-
- importPanel: function () {
- var container = L.DomUtil.create('div', 'umap-upload'),
- title = L.DomUtil.create('h4', '', container),
- presetBox = L.DomUtil.create('div', 'formbox', container),
- presetSelect = L.DomUtil.create('select', '', presetBox),
- fileBox = L.DomUtil.create('div', 'formbox', container),
- fileInput = L.DomUtil.create('input', '', fileBox),
- urlInput = L.DomUtil.create('input', '', container),
- rawInput = L.DomUtil.create('textarea', '', container),
- typeLabel = L.DomUtil.create('label', '', container),
- layerLabel = L.DomUtil.create('label', '', container),
- clearLabel = L.DomUtil.create('label', '', container),
- submitInput = L.DomUtil.create('input', '', container),
- map = this, option,
- types = ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap'];
- title.textContent = L._('Import data');
- fileInput.type = 'file';
- fileInput.multiple = 'multiple';
- submitInput.type = 'button';
- submitInput.value = L._('Import');
- submitInput.className = 'button';
- typeLabel.textContent = L._('Choose the format of the data to import');
- this.help.button(typeLabel, 'importFormats');
- var typeInput = L.DomUtil.create('select', '', typeLabel);
- typeInput.name = 'format';
- layerLabel.textContent = L._('Choose the layer to import in');
- var layerInput = L.DomUtil.create('select', '', layerLabel);
- layerInput.name = 'datalayer';
- urlInput.type = 'text';
- urlInput.placeholder = L._('Provide an URL here');
- rawInput.placeholder = L._('Paste your data here');
- clearLabel.textContent = L._('Replace layer content');
- var clearFlag = L.DomUtil.create('input', '', clearLabel);
- clearFlag.type = 'checkbox';
- clearFlag.name = 'clear';
- this.eachDataLayerReverse(function (datalayer) {
- if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) {
- var id = L.stamp(datalayer);
- option = L.DomUtil.create('option', '', layerInput);
- option.value = id;
- option.textContent = datalayer.options.name;
- }
- });
- L.DomUtil.element('option', {value: '', textContent: L._('Import in a new layer')}, layerInput);
- L.DomUtil.element('option', {value: '', textContent: L._('Choose the data format')}, typeInput);
- for (var i = 0; i < types.length; i++) {
- option = L.DomUtil.create('option', '', typeInput);
- option.value = option.textContent = types[i];
- }
- if (this.options.importPresets.length) {
- var noPreset = L.DomUtil.create('option', '', presetSelect);
- noPreset.value = noPreset.textContent = L._('Choose a preset');
- for (var j = 0; j < this.options.importPresets.length; j++) {
- option = L.DomUtil.create('option', '', presetSelect);
- option.value = this.options.importPresets[j].url;
- option.textContent = this.options.importPresets[j].label;
- }
- } else {
- presetBox.style.display = 'none';
- }
-
- var submit = function () {
- var type = typeInput.value,
- layerId = layerInput[layerInput.selectedIndex].value,
- layer;
- if (type === 'umap') {
- this.once('postsync', function () {
- this.setView(this.latLng(this.options.center), this.options.zoom);
- });
- }
- if (layerId) layer = map.datalayers[layerId];
- if (layer && clearFlag.checked) layer.empty();
- if (fileInput.files.length) {
- var file;
- for (var i = 0, file; file = fileInput.files[i]; i++) {
- type = type || L.Util.detectFileType(file);
- if (!type) {
- this.ui.alert({content: L._('Unable to detect format of file {filename}', {filename: file.name}), level: 'error'});
- continue;
- }
- if (type === 'umap') {
- this.importFromFile(file, 'umap');
- } else {
- var importLayer = layer;
- if (!layer) importLayer = this.createDataLayer({name: file.name});
- importLayer.importFromFile(file, type);
- }
- }
- } else {
- if (!type) return this.ui.alert({content: L._('Please choose a format'), level: 'error'});
- if (rawInput.value && type === 'umap') {
- try {
- this.importRaw(rawInput.value, type);
- } catch (e) {
- this.ui.alert({content: L._('Invalid umap data'), level: 'error'});
- console.error(e);
- }
- } else {
- if (!layer) layer = this.createDataLayer();
- if (rawInput.value) layer.importRaw(rawInput.value, type);
- else if (urlInput.value) layer.importFromUrl(urlInput.value, type);
- else if (presetSelect.selectedIndex > 0) layer.importFromUrl(presetSelect[presetSelect.selectedIndex].value, type);
- }
- }
- };
- L.DomEvent.on(submitInput, 'click', submit, this);
- L.DomEvent.on(fileInput, 'change', function (e) {
- var type = '', newType;
- for (var i = 0; i < e.target.files.length; i++) {
- newType = L.Util.detectFileType(e.target.files[i]);
- if (!type && newType) type = newType;
- if (type && newType !== type) {
- type = '';
- break;
- }
- }
- typeInput.value = type;
- }, this);
- this.ui.openPanel({data: {html: container}, className: 'dark'});
- },
-
- importRaw: function(rawData) {
- var importedData = JSON.parse(rawData);
-
- var mustReindex = false;
-
- for (var i = 0; i < this.editableOptions.length; i++) {
- var option = this.editableOptions[i];
- if (typeof importedData.properties[option] !== 'undefined') {
- this.options[option] = importedData.properties[option];
- if (option === 'sortKey') mustReindex = true;
- }
- }
-
- if (importedData.geometry) this.options.center = this.latLng(importedData.geometry);
- var self = this;
- importedData.layers.forEach( function (geojson) {
- var dataLayer = self.createDataLayer();
- dataLayer.fromUmapGeoJSON(geojson);
- });
-
- this.initTileLayers();
- this.renderControls();
- this.handleLimitBounds();
- this.eachDataLayer(function (datalayer) {
- if (mustReindex) datalayer.reindex();
- datalayer.redraw();
- });
- this.fire('postsync');
- this.isDirty = true;
- },
-
- importFromFile: function (file) {
- var reader = new FileReader();
- reader.readAsText(file);
- var self = this;
- reader.onload = function (e) {
- var rawData = e.target.result;
- try {
- self.importRaw(rawData);
- } catch (e) {
- console.error('Error importing data', e);
- self.ui.alert({content: L._('Invalid umap data in {filename}', {filename: file.name}), level: 'error'});
- }
- };
- },
-
- openBrowser: function () {
- this.onceDatalayersLoaded(function () {
- this._openBrowser();
- });
- },
-
- openFilter: function () {
- this.onceDatalayersLoaded(function () {
- this._openFilter();
- });
- },
-
- displayCaption: function () {
- var container = L.DomUtil.create('div', 'umap-caption'),
- title = L.DomUtil.create('h3', '', container);
- title.textContent = this.options.name;
- this.permissions.addOwnerLink('h5', container);
- if (this.options.description) {
- var description = L.DomUtil.create('div', 'umap-map-description', container);
- description.innerHTML = L.Util.toHTML(this.options.description);
- }
- var datalayerContainer = L.DomUtil.create('div', 'datalayer-container', container);
- this.eachVisibleDataLayer(function (datalayer) {
- var p = L.DomUtil.create('p', '', datalayerContainer),
- color = L.DomUtil.create('span', 'datalayer-color', p),
- headline = L.DomUtil.create('strong', '', p),
- description = L.DomUtil.create('span', '', p);
- datalayer.onceLoaded(function () {
- color.style.backgroundColor = this.getColor();
- if (datalayer.options.description) {
- description.innerHTML = L.Util.toHTML(datalayer.options.description);
- }
- });
- datalayer.renderToolbox(headline);
- L.DomUtil.add('span', '', headline, datalayer.options.name + ' ');
- });
- var creditsContainer = L.DomUtil.create('div', 'credits-container', container),
- credits = L.DomUtil.createFieldset(creditsContainer, L._('Credits'));
- title = L.DomUtil.add('h5', '', credits, L._('User content credits'));
- if (this.options.shortCredit || this.options.longCredit) {
- L.DomUtil.add('p', '', credits, L.Util.toHTML(this.options.longCredit || this.options.shortCredit));
- }
- if (this.options.licence) {
- var licence = L.DomUtil.add('p', '', credits, L._('Map user content has been published under licence') + ' '),
- link = L.DomUtil.add('a', '', licence, this.options.licence.name);
- link.href = this.options.licence.url;
- } else {
- L.DomUtil.add('p', '', credits, L._('No licence has been set'));
- }
- L.DomUtil.create('hr', '', credits);
- title = L.DomUtil.create('h5', '', credits);
- title.textContent = L._('Map background credits');
- var tilelayerCredit = L.DomUtil.create('p', '', credits),
- name = L.DomUtil.create('strong', '', tilelayerCredit),
- attribution = L.DomUtil.create('span', '', tilelayerCredit);
- name.textContent = this.selected_tilelayer.options.name + ' ';
- attribution.innerHTML = this.selected_tilelayer.getAttribution();
- L.DomUtil.create('hr', '', credits);
- var umapCredit = L.DomUtil.create('p', '', credits),
- urls = {
- leaflet: 'http://leafletjs.com',
- django: 'https://www.djangoproject.com',
- umap: 'http://wiki.openstreetmap.org/wiki/UMap'
- };
- umapCredit.innerHTML = L._('Powered by Leaflet and Django, glued by uMap project.', urls);
- var browser = L.DomUtil.create('li', '');
- L.DomUtil.create('i', 'umap-icon-16 umap-list', browser);
- var labelBrowser = L.DomUtil.create('span', '', browser);
- labelBrowser.textContent = labelBrowser.title = L._('Browse data');
- L.DomEvent.on(browser, 'click', this.openBrowser, this);
- var actions = [browser];
- if (this.options.advancedFilterKey) {
- var filter = L.DomUtil.create('li', '');
- L.DomUtil.create('i', 'umap-icon-16 umap-add', filter);
- var labelFilter = L.DomUtil.create('span', '', filter);
- labelFilter.textContent = labelFilter.title = L._('Select data');
- L.DomEvent.on(filter, 'click', this.openFilter, this);
- actions.push(filter)
- }
- this.ui.openPanel({data: {html: container}, actions: actions});
- },
-
- eachDataLayer: function (method, context) {
- for (var i = 0; i < this.datalayers_index.length; i++) {
- method.call(context, this.datalayers_index[i]);
- }
- },
-
- eachDataLayerReverse: function (method, context, filter) {
- for (var i = this.datalayers_index.length - 1; i >= 0; i--) {
- if (filter && !filter.call(context, this.datalayers_index[i])) continue;
- method.call(context, this.datalayers_index[i]);
- }
- },
-
- eachBrowsableDataLayer: function (method, context) {
- this.eachDataLayerReverse(method, context, function (d) { return d.allowBrowse(); });
- },
-
- eachVisibleDataLayer: function (method, context) {
- this.eachDataLayerReverse(method, context, function (d) { return d.isVisible(); });
- },
-
- findDataLayer: function (method, context) {
- for (var i = this.datalayers_index.length - 1; i >= 0; i--) {
- if (method.call(context, this.datalayers_index[i])) return this.datalayers_index[i];
- }
- },
-
- backup: function () {
- this.backupOptions();
- this._datalayers_index_bk = [].concat(this.datalayers_index);
- },
-
- reset: function () {
- if (this.editTools) this.editTools.stopDrawing();
- this.resetOptions();
- this.datalayers_index = [].concat(this._datalayers_index_bk);
- this.dirty_datalayers.slice().forEach(function (datalayer) {
- if (datalayer.isDeleted) datalayer.connectToMap();
- datalayer.reset();
- });
- this.ensurePanesOrder();
- this.dirty_datalayers = [];
- this.updateDatalayersControl();
- this.initTileLayers();
- this.isDirty = false;
- },
-
- checkDirty: function () {
- L.DomUtil.classIf(this._container, 'umap-is-dirty', this.isDirty);
- },
-
- addDirtyDatalayer: function (datalayer) {
- if (this.dirty_datalayers.indexOf(datalayer) === -1) {
- this.dirty_datalayers.push(datalayer);
- this.isDirty = true;
- }
- },
-
- removeDirtyDatalayer: function (datalayer) {
- if (this.dirty_datalayers.indexOf(datalayer) !== -1) {
- this.dirty_datalayers.splice(this.dirty_datalayers.indexOf(datalayer), 1);
- this.checkDirty();
- }
- },
-
- continueSaving: function () {
- if (this.dirty_datalayers.length) this.dirty_datalayers[0].save();
- else this.fire('saved');
- },
-
- editableOptions: [
- 'zoom',
- 'scrollWheelZoom',
- 'scaleControl',
- 'moreControl',
- 'miniMap',
- 'displayPopupFooter',
- 'onLoadPanel',
- 'tilelayersControl',
- 'name',
- 'description',
- 'licence',
- 'tilelayer',
- 'overlay',
- 'limitBounds',
- 'color',
- 'iconClass',
- 'iconUrl',
- 'smoothFactor',
- 'opacity',
- 'weight',
- 'fill',
- 'fillColor',
- 'fillOpacity',
- 'dashArray',
- 'popupShape',
- 'popupTemplate',
- 'popupContentTemplate',
- 'zoomTo',
- 'captionBar',
- 'captionMenus',
- 'slideshow',
- 'sortKey',
- 'labelKey',
- 'filterKey',
- 'advancedFilterKey',
- 'slugKey',
- 'showLabel',
- 'labelDirection',
- 'labelInteractive',
- 'shortCredit',
- 'longCredit',
- 'permanentCredit',
- 'permanentCreditBackground',
- 'zoomControl',
- 'datalayersControl',
- 'searchControl',
- 'locateControl',
- 'fullscreenControl',
- 'editinosmControl',
- 'embedControl',
- 'measureControl',
- 'tilelayersControl',
- 'easing'
- ],
-
- exportOptions: function () {
- var properties = {};
- for (var i = this.editableOptions.length - 1; i >= 0; i--) {
- if (typeof this.options[this.editableOptions[i]] !== 'undefined') {
- properties[this.editableOptions[i]] = this.options[this.editableOptions[i]];
- }
- }
- return properties;
- },
-
- serialize: function () {
- var umapfile = {
- type: 'umap',
- uri: window.location.href,
- properties: this.exportOptions(),
- geometry: this.geometry(),
- layers: []
- };
-
- this.eachDataLayer(function (datalayer) {
- umapfile.layers.push(datalayer.umapGeoJSON());
- });
-
- return JSON.stringify(umapfile, null, 2);
- },
-
- save: function () {
- if (!this.isDirty) return;
- if (this._default_extent) this.updateExtent();
- var geojson = {
- type: 'Feature',
- geometry: this.geometry(),
- properties: this.exportOptions()
- };
- this.backup();
- var formData = new FormData();
- formData.append('name', this.options.name);
- formData.append('center', JSON.stringify(this.geometry()));
- formData.append('settings', JSON.stringify(geojson));
- this.post(this.getSaveUrl(), {
- data: formData,
- context: this,
- callback: function (data) {
- var duration = 3000;
- if (!this.options.umap_id) {
- duration = 100000; // we want a longer message at map creation (TODO UGLY)
- this.options.umap_id = data.id;
- this.permissions.setOptions(data.permissions)
- } else if (!this.permissions.isDirty) {
- // Do not override local changes to permissions,
- // but update in case some other editors changed them in the meantime.
- this.permissions.setOptions(data.permissions)
- }
- // Update URL in case the name has changed.
- if (history && history.pushState) history.pushState({}, this.options.name, data.url);
- else window.location = data.url;
- if (data.info) msg = data.info;
- else msg = L._('Map has been saved!');
- this.once('saved', function () {
- this.isDirty = false;
- this.ui.alert({content: msg, level: 'info', duration: duration});
- });
- this.ui.closePanel();
- this.permissions.save();
- }
- });
- },
-
- getEditUrl: function() {
- return L.Util.template(this.options.urls.map_update, {'map_id': this.options.umap_id});
- },
-
- getCreateUrl: function() {
- return L.Util.template(this.options.urls.map_create);
- },
-
- getSaveUrl: function () {
- return (this.options.umap_id && this.getEditUrl()) || this.getCreateUrl();
- },
-
- geometry: function() {
- /* Return a GeoJSON geometry Object */
- var latlng = this.latLng(this.options.center || this.getCenter());
- return {
- type: 'Point',
- coordinates: [
- latlng.lng,
- latlng.lat
- ]
- };
- },
-
- defaultDataLayer: function () {
- var datalayer, fallback;
- datalayer = this.lastUsedDataLayer;
- if (datalayer && !datalayer.isRemoteLayer() && datalayer.canBrowse() && datalayer.isVisible()) {
- return datalayer;
- }
- datalayer = this.findDataLayer(function (datalayer) {
- if (!datalayer.isRemoteLayer() && datalayer.canBrowse()) {
- fallback = datalayer;
- if (datalayer.isVisible()) return true;
- }
- });
- if (datalayer) return datalayer;
- if (fallback) {
- // No datalayer visible, let's force one
- this.addLayer(fallback.layer);
- return fallback;
- }
- return this.createDataLayer();
- },
-
- getDataLayerByUmapId: function (umap_id) {
- return this.findDataLayer(function (d) { return d.umap_id == umap_id; });
- },
-
- _editControls: function (container) {
- var UIFields = [];
- for (var i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
- UIFields.push('options.' + this.HIDDABLE_CONTROLS[i] + 'Control');
- }
- UIFields = UIFields.concat([
- 'options.moreControl',
- 'options.scrollWheelZoom',
- 'options.miniMap',
- 'options.scaleControl',
- 'options.onLoadPanel',
- 'options.displayPopupFooter',
- 'options.captionBar',
- 'options.captionMenus'
- ]);
- builder = new L.U.FormBuilder(this, UIFields, {
- callback: function() {
- this.renderControls();
- this.initCaptionBar();
- },
- callbackContext: this
- });
- var controlsOptions = L.DomUtil.createFieldset(container, L._('User interface options'));
- controlsOptions.appendChild(builder.build());
- },
-
- _editShapeProperties: function (container) {
- var shapeOptions = [
- 'options.color',
- 'options.iconClass',
- 'options.iconUrl',
- 'options.opacity',
- 'options.weight',
- 'options.fill',
- 'options.fillColor',
- 'options.fillOpacity'
- ];
-
- builder = new L.U.FormBuilder(this, shapeOptions, {
- callback: function (e) {
- this.eachDataLayer(function (datalayer) {
- datalayer.redraw();
- });
- }
- });
- var defaultShapeProperties = L.DomUtil.createFieldset(container, L._('Default shape properties'));
- defaultShapeProperties.appendChild(builder.build());
- },
-
- _editDefaultProperties: function (container) {
- var optionsFields = [
- 'options.smoothFactor',
- 'options.dashArray',
- 'options.zoomTo',
- ['options.easing', {handler: 'Switch', label: L._('Animated transitions')}],
- 'options.labelKey',
- ['options.sortKey', {handler: 'BlurInput', helpEntries: 'sortKey', placeholder: L._('Default: name'), label: L._('Sort key'), inheritable: true}],
- ['options.filterKey', {handler: 'Input', helpEntries: 'filterKey', placeholder: L._('Default: name'), label: L._('Filter keys'), inheritable: true}],
- ['options.advancedFilterKey', {handler: 'Input', helpEntries: 'advancedFilterKey', placeholder: L._('Example: key1,key2,key3'), label: L._('Advanced filter keys'), inheritable: true}],
- ['options.slugKey', {handler: 'BlurInput', helpEntries: 'slugKey', placeholder: L._('Default: name'), label: L._('Feature identifier key')}]
- ];
-
- builder = new L.U.FormBuilder(this, optionsFields, {
- callback: function (e) {
- this.initCaptionBar();
- this.eachDataLayer(function (datalayer) {
- if (e.helper.field === 'options.sortKey') datalayer.reindex();
- datalayer.redraw();
- });
- }
- });
- var defaultProperties = L.DomUtil.createFieldset(container, L._('Default properties'));
- defaultProperties.appendChild(builder.build());
- },
-
- _editInteractionsProperties: function (container) {
- var popupFields = [
- 'options.popupShape',
- 'options.popupTemplate',
- 'options.popupContentTemplate',
- 'options.showLabel',
- 'options.labelDirection',
- 'options.labelInteractive'
- ];
- builder = new L.U.FormBuilder(this, popupFields, {
- callback: function (e) {
- if (e.helper.field === 'options.popupTemplate' || e.helper.field === 'options.popupContentTemplate' || e.helper.field === 'options.popupShape') return;
- this.eachDataLayer(function (datalayer) {
- datalayer.redraw();
- })
- }
- });
- var popupFieldset = L.DomUtil.createFieldset(container, L._('Default interaction options'));
- popupFieldset.appendChild(builder.build());
- },
-
- _editTilelayer: function (container) {
- if (!L.Util.isObject(this.options.tilelayer)) {
- this.options.tilelayer = {};
- }
- var tilelayerFields = [
- ['options.tilelayer.name', {handler: 'BlurInput', placeholder: L._('display name')}],
- ['options.tilelayer.url_template', {handler: 'BlurInput', helpText: L._('Supported scheme') + ': http://{s}.domain.com/{z}/{x}/{y}.png', placeholder: 'url'}],
- ['options.tilelayer.maxZoom', {handler: 'BlurIntInput', placeholder: L._('max zoom')}],
- ['options.tilelayer.minZoom', {handler: 'BlurIntInput', placeholder: L._('min zoom')}],
- ['options.tilelayer.attribution', {handler: 'BlurInput', placeholder: L._('attribution')}],
- ['options.tilelayer.tms', {handler: 'Switch', label: L._('TMS format')}]
- ];
- var customTilelayer = L.DomUtil.createFieldset(container, L._('Custom background'));
- builder = new L.U.FormBuilder(this, tilelayerFields, {
- callback: this.initTileLayers,
- callbackContext: this
- });
- customTilelayer.appendChild(builder.build());
- },
-
- _editOverlay: function (container) {
- if (!L.Util.isObject(this.options.overlay)) {
- this.options.overlay = {};
- }
- var overlayFields = [
- ['options.overlay.url_template', {handler: 'BlurInput', helpText: L._('Supported scheme') + ': http://{s}.domain.com/{z}/{x}/{y}.png', placeholder: 'url', helpText: L._('Background overlay url')}],
- ['options.overlay.maxZoom', {handler: 'BlurIntInput', placeholder: L._('max zoom')}],
- ['options.overlay.minZoom', {handler: 'BlurIntInput', placeholder: L._('min zoom')}],
- ['options.overlay.attribution', {handler: 'BlurInput', placeholder: L._('attribution')}],
- ['options.overlay.opacity', {handler: 'Range', min: 0, max: 1, step: 'any', placeholder: L._('opacity')}],
- ['options.overlay.tms', {handler: 'Switch', label: L._('TMS format')}]
- ];
- var overlay = L.DomUtil.createFieldset(container, L._('Custom overlay'));
- builder = new L.U.FormBuilder(this, overlayFields, {
- callback: this.initTileLayers,
- callbackContext: this
- });
- overlay.appendChild(builder.build());
- },
-
- _editBounds: function (container) {
- if (!L.Util.isObject(this.options.limitBounds)) {
- this.options.limitBounds = {};
- }
- var limitBounds = L.DomUtil.createFieldset(container, L._('Limit bounds'));
- var boundsFields = [
- ['options.limitBounds.south', {handler: 'BlurFloatInput', placeholder: L._('max South')}],
- ['options.limitBounds.west', {handler: 'BlurFloatInput', placeholder: L._('max West')}],
- ['options.limitBounds.north', {handler: 'BlurFloatInput', placeholder: L._('max North')}],
- ['options.limitBounds.east', {handler: 'BlurFloatInput', placeholder: L._('max East')}]
- ];
- var boundsBuilder = new L.U.FormBuilder(this, boundsFields, {
- callback: this.handleLimitBounds,
- callbackContext: this
- });
- limitBounds.appendChild(boundsBuilder.build());
- var boundsButtons = L.DomUtil.create('div', 'button-bar half', limitBounds);
- var setCurrentButton = L.DomUtil.add('a', 'button', boundsButtons, L._('Use current bounds'));
- setCurrentButton.href = '#';
- L.DomEvent.on(setCurrentButton, 'click', function () {
- var bounds = this.getBounds();
- this.options.limitBounds.south = L.Util.formatNum(bounds.getSouth());
- this.options.limitBounds.west = L.Util.formatNum(bounds.getWest());
- this.options.limitBounds.north = L.Util.formatNum(bounds.getNorth());
- this.options.limitBounds.east = L.Util.formatNum(bounds.getEast());
- boundsBuilder.fetchAll();
- this.isDirty = true;
- this.handleLimitBounds();
- }, this);
- var emptyBounds = L.DomUtil.add('a', 'button', boundsButtons, L._('Empty'));
- emptyBounds.href = '#';
- L.DomEvent.on(emptyBounds, 'click', function () {
- this.options.limitBounds.south = null;
- this.options.limitBounds.west = null;
- this.options.limitBounds.north = null;
- this.options.limitBounds.east = null;
- boundsBuilder.fetchAll();
- this.isDirty = true;
- this.handleLimitBounds();
- }, this);
- },
-
- _editSlideshow: function (container) {
- var slideshow = L.DomUtil.createFieldset(container, L._('Slideshow'));
- var slideshowFields = [
- ['options.slideshow.active', {handler: 'Switch', label: L._('Activate slideshow mode')}],
- ['options.slideshow.delay', {handler: 'SlideshowDelay', helpText: L._('Delay between two transitions when in play mode')}],
- ['options.slideshow.easing', {handler: 'Switch', label: L._('Animated transitions'), inheritable: true}],
- ['options.slideshow.autoplay', {handler: 'Switch', label: L._('Autostart when map is loaded')}]
- ];
- var slideshowHandler = function () {
- this.slideshow.setOptions(this.options.slideshow);
- this.renderControls();
- };
- var slideshowBuilder = new L.U.FormBuilder(this, slideshowFields, {
- callback: slideshowHandler,
- callbackContext: this
- });
- slideshow.appendChild(slideshowBuilder.build());
- },
-
- _editCredits: function (container) {
- var credits = L.DomUtil.createFieldset(container, L._('Credits'));
- var creditsFields = [
- ['options.licence', {handler: 'LicenceChooser', label: L._('licence')}],
- ['options.shortCredit', {handler: 'Input', label: L._('Short credits'), helpEntries: ['shortCredit', 'textFormatting']}],
- ['options.longCredit', {handler: 'Textarea', label: L._('Long credits'), helpEntries: ['longCredit', 'textFormatting']}],
- ['options.permanentCredit', {handler: 'Textarea', label: L._('Permanent credits'), helpEntries: ['permanentCredit', 'textFormatting']}],
- ['options.permanentCreditBackground', {handler: 'Switch', label: L._('Permanent credits background')}]
- ];
- var creditsBuilder = new L.U.FormBuilder(this, creditsFields, {
- callback: this.renderControls,
- callbackContext: this
- });
- credits.appendChild(creditsBuilder.build());
- },
-
- _advancedActions: function (container) {
- var advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'));
- var advancedButtons = L.DomUtil.create('div', 'button-bar half', advancedActions);
- var del = L.DomUtil.create('a', 'button umap-delete', advancedButtons);
- del.href = '#';
- del.textContent = L._('Delete');
- L.DomEvent
- .on(del, 'click', L.DomEvent.stop)
- .on(del, 'click', this.del, this);
- var clone = L.DomUtil.create('a', 'button umap-clone', advancedButtons);
- clone.href = '#';
- clone.textContent = L._('Clone');
- clone.title = L._('Clone this map');
- L.DomEvent
- .on(clone, 'click', L.DomEvent.stop)
- .on(clone, 'click', this.clone, this);
- var empty = L.DomUtil.create('a', 'button umap-empty', advancedButtons);
- empty.href = '#';
- empty.textContent = L._('Empty');
- empty.title = L._('Delete all layers');
- L.DomEvent
- .on(empty, 'click', L.DomEvent.stop)
- .on(empty, 'click', this.empty, this);
- var download = L.DomUtil.create('a', 'button umap-download', advancedButtons);
- download.href = '#';
- download.textContent = L._('Download');
- download.title = L._('Open download panel');
- L.DomEvent
- .on(download, 'click', L.DomEvent.stop)
- .on(download, 'click', this.renderShareBox, this);
- },
-
- edit: function () {
- if(!this.editEnabled) return;
- var container = L.DomUtil.create('div','umap-edit-container'),
- metadataFields = [
- 'options.name',
- 'options.description'
- ],
- title = L.DomUtil.create('h4', '', container);
- title.textContent = L._('Edit map properties');
- var builder = new L.U.FormBuilder(this, metadataFields);
- var form = builder.build();
- container.appendChild(form);
- this._editControls(container);
- this._editShapeProperties(container);
- this._editDefaultProperties(container);
- this._editInteractionsProperties(container);
- this._editTilelayer(container);
- this._editOverlay(container);
- this._editBounds(container);
- this._editSlideshow(container);
- this._editCredits(container);
- this._advancedActions(container);
-
- this.ui.openPanel({data: {html: container}, className: 'dark'});
- },
-
- enableEdit: function() {
- L.DomUtil.addClass(document.body, 'umap-edit-enabled');
- this.editEnabled = true;
- this.fire('edit:enabled');
- },
-
- disableEdit: function() {
- if (this.isDirty) return;
- L.DomUtil.removeClass(document.body, 'umap-edit-enabled');
- this.editedFeature = null;
- this.editEnabled = false;
- this.fire('edit:disabled');
- },
-
- getDisplayName: function () {
- return this.options.name || L._('Untitled map');
- },
-
- initCaptionBar: function () {
- var container = L.DomUtil.create('div', 'umap-caption-bar', this._controlContainer),
- name = L.DomUtil.create('h3', '', container);
- L.DomEvent.disableClickPropagation(container);
- this.permissions.addOwnerLink('span', container);
- if (this.options.captionMenus) {
- var about = L.DomUtil.add('a', 'umap-about-link', container, ' — ' + L._('About'));
- about.href = '#';
- L.DomEvent.on(about, 'click', this.displayCaption, this);
- var browser = L.DomUtil.add('a', 'umap-open-browser-link', container, ' | ' + L._('Browse data'));
- browser.href = '#';
- L.DomEvent.on(browser, 'click', L.DomEvent.stop)
- .on(browser, 'click', this.openBrowser, this);
- if (this.options.advancedFilterKey) {
- var filter = L.DomUtil.add('a', 'umap-open-filter-link', container, ' | ' + L._('Select data'));
- filter.href = '#';
- L.DomEvent.on(filter, 'click', L.DomEvent.stop)
- .on(filter, 'click', this.openFilter, this);
- }
- }
- var setName = function () {
- name.textContent = this.getDisplayName();
- };
- L.bind(setName, this)();
- this.on('postsync', L.bind(setName, this));
- this.onceDatalayersLoaded(function () {
- this.slideshow.renderToolbox(container);
- });
- },
-
- initEditBar: function () {
- var container = L.DomUtil.create('div', 'umap-main-edit-toolbox with-transition dark', this._controlContainer),
- title = L.DomUtil.add('h3', '', container, L._('Editing') + ' '),
- name = L.DomUtil.create('a', 'umap-click-to-edit', title),
- setName = function () {
- name.textContent = this.getDisplayName();
- };
- L.bind(setName, this)();
- L.DomEvent.on(name, 'click', this.edit, this);
- this.on('postsync', L.bind(setName, this));
- this.help.button(title, 'edit');
- var save = L.DomUtil.create('a', 'leaflet-control-edit-save button', container);
- save.href = '#';
- save.title = L._('Save current edits') + ' (Ctrl+S)';
- save.textContent = L._('Save');
- var cancel = L.DomUtil.create('a', 'leaflet-control-edit-cancel button', container);
- cancel.href = '#';
- cancel.title = L._('Cancel edits');
- cancel.textContent = L._('Cancel');
- var disable = L.DomUtil.create('a', 'leaflet-control-edit-disable', container);
- disable.href = '#';
- disable.title = disable.textContent = L._('Disable editing');
-
-
- L.DomEvent
- .addListener(disable, 'click', L.DomEvent.stop)
- .addListener(disable, 'click', function (e) {
- this.disableEdit(e);
- this.ui.closePanel();
- }, this);
-
- L.DomEvent
- .addListener(save, 'click', L.DomEvent.stop)
- .addListener(save, 'click', this.save, this);
-
- L.DomEvent
- .addListener(cancel, 'click', L.DomEvent.stop)
- .addListener(cancel, 'click', this.askForReset, this);
- },
-
- askForReset: function (e) {
- if (!confirm(L._('Are you sure you want to cancel your changes?'))) return;
- this.reset();
- this.disableEdit(e);
- this.ui.closePanel();
- },
-
- startMarker: function () {
- return this.editTools.startMarker();
- },
-
- startPolyline: function () {
- return this.editTools.startPolyline();
- },
-
- startPolygon: function () {
- return this.editTools.startPolygon();
- },
-
- del: function () {
- if (confirm(L._('Are you sure you want to delete this map?'))) {
- var url = L.Util.template(this.options.urls.map_delete, {'map_id': this.options.umap_id});
- this.post(url);
- }
- },
-
- clone: function () {
- if (confirm(L._('Are you sure you want to clone this map and all its datalayers?'))) {
- var url = L.Util.template(this.options.urls.map_clone, {'map_id': this.options.umap_id});
- this.post(url);
- }
- },
-
- empty: function () {
- this.eachDataLayerReverse(function (datalayer) {
- datalayer._delete();
- });
- },
-
- initLoader: function () {
- this.loader = new L.Control.Loading();
- this.loader.onAdd(this);
- },
-
- post: function (url, options) {
- options = options || {};
- options.listener = this;
- this.xhr.post(url, options);
- },
-
- get: function (url, options) {
- options = options || {};
- options.listener = this;
- this.xhr.get(url, options);
- },
-
- ajax: function (options) {
- options.listener = this;
- this.xhr._ajax(options);
- },
-
- initContextMenu: function () {
- this.contextmenu = new L.U.ContextMenu(this);
- this.contextmenu.enable();
- },
-
- setContextMenuItems: function (e) {
- var items = [];
- if (this._zoom !== this.getMaxZoom()) {
- items.push({
- text: L._('Zoom in'),
- callback: function () {this.zoomIn();}
- });
- }
- if (this._zoom !== this.getMinZoom()) {
- items.push({
- text: L._('Zoom out'),
- callback: function () {this.zoomOut();}
- });
- }
- if (e && e.relatedTarget) {
- if (e.relatedTarget.getContextMenuItems) {
- items = items.concat(e.relatedTarget.getContextMenuItems(e));
- }
- }
- if (this.options.allowEdit) {
- items.push('-');
- if (this.editEnabled) {
- if (!this.isDirty) {
- items.push({
- text: L._('Stop editing') + ' (Ctrl+E)',
- callback: this.disableEdit
- });
- }
- if (this.options.enableMarkerDraw) {
- items.push(
- {
- text: L._('Draw a marker') + ' (Ctrl+M)',
- callback: this.startMarker,
- context: this
- });
- }
- if (this.options.enablePolylineDraw) {
- items.push(
- {
- text: L._('Draw a polygon') + ' (Ctrl+P)',
- callback: this.startPolygon,
- context: this
- });
- }
- if (this.options.enablePolygonDraw) {
- items.push(
- {
- text: L._('Draw a line') + ' (Ctrl+L)',
- callback: this.startPolyline,
- context: this
- });
- }
- items.push('-');
- items.push({
- text: L._('Help'),
- callback: function () { this.help.show('edit');}
- });
- } else {
- items.push({
- text: L._('Start editing') + ' (Ctrl+E)',
- callback: this.enableEdit
- });
- }
- }
- items.push('-', {
- text: L._('Browse data'),
- callback: this.openBrowser
- });
- if (this.options.advancedFilterKey) {
- items.push({
- text: L._('Select data'),
- callback: this.openFilter
- })
- }
- items.push(
- {
- text: L._('About'),
- callback: this.displayCaption
- },
- {
- text: L._('Search location'),
- callback: this.search
- }
- );
- if (this.options.urls.routing) {
- items.push('-',
- {
- text: L._('Directions from here'),
- callback: this.openExternalRouting
- }
- );
- }
- this.options.contextmenuItems = items;
- },
-
- openExternalRouting: function (e) {
- var url = this.options.urls.routing;
- if (url) {
- var params = {
- lat: e.latlng.lat,
- lng: e.latlng.lng,
- locale: L.locale,
- zoom: this.getZoom()
- };
- window.open(L.Util.template(url, params));
- }
- return;
- },
-
- getMap: function () {
- return this;
- },
-
- getGeoContext: function () {
- var context = {
- bbox: this.getBounds().toBBoxString(),
- north: this.getBounds().getNorthEast().lat,
- east: this.getBounds().getNorthEast().lng,
- south: this.getBounds().getSouthWest().lat,
- west: this.getBounds().getSouthWest().lng,
- lat: this.getCenter().lat,
- lng: this.getCenter().lng,
- zoom: this.getZoom()
- };
- context.left = context.west;
- context.bottom = context.south;
- context.right = context.east;
- context.top = context.north;
- return context;
- },
-
- localizeUrl: function (url) {
- return L.Util.greedyTemplate(url, this.getGeoContext(), true);
- },
-
- proxyUrl: function (url, ttl) {
- if (this.options.urls.ajax_proxy) {
- url = L.Util.greedyTemplate(this.options.urls.ajax_proxy, {url: encodeURIComponent(url), ttl: ttl});
- }
- return url;
- },
-
- closeInplaceToolbar: function () {
- var toolbar = this._toolbars[L.Toolbar.Popup._toolbar_class_id];
- if (toolbar) toolbar.remove();
- },
-
- search: function () {
- if (this._controls.search) this._controls.search.openPanel(this);
- },
-
- getFilterKeys: function () {
- return (this.options.filterKey || this.options.sortKey || 'name').split(',');
- },
-
- getAdvancedFilterKeys: function () {
- return (this.options.advancedFilterKey || '').split(",");
+ HIDDABLE_CONTROLS: [
+ 'zoom',
+ 'search',
+ 'fullscreen',
+ 'embed',
+ 'locate',
+ 'measure',
+ 'tilelayers',
+ 'editinosm',
+ 'datalayers',
+ ],
+
+ initialize(el, { properties, geometry }) {
+ if (properties && properties.locale) L.setLocale(properties.locale)
+
+ // Don't let default autocreation of controls
+ const zoomControl =
+ typeof properties.zoomControl !== 'undefined' ? properties.zoomControl : true
+ properties.zoomControl = false
+ const fullscreenControl =
+ typeof properties.fullscreenControl !== 'undefined'
+ ? properties.fullscreenControl
+ : true
+ properties.fullscreenControl = false
+ L.Util.setBooleanFromQueryString(properties, 'scrollWheelZoom')
+ L.Map.prototype.initialize.call(this, el, properties)
+
+ this.ui = new L.U.UI(this._container)
+ this.xhr = new L.U.Xhr(this.ui)
+ this.xhr.on('dataloding', function (e) {
+ this.fire('dataloding', e)
+ })
+ this.xhr.on('datalaod', function (e) {
+ this.fire('datalaod', e)
+ })
+
+ this.initLoader()
+ this.name = this.options.name
+ this.description = this.options.description
+ this.demoTileInfos = this.options.demoTileInfos
+ if (geometry) this.options.center = geometry
+ this.options.zoomControl = zoomControl
+ this.options.fullscreenControl = fullscreenControl
+ L.Util.setBooleanFromQueryString(this.options, 'moreControl')
+ L.Util.setBooleanFromQueryString(this.options, 'scaleControl')
+ L.Util.setBooleanFromQueryString(this.options, 'miniMap')
+ L.Util.setBooleanFromQueryString(this.options, 'allowEdit')
+ L.Util.setBooleanFromQueryString(this.options, 'displayDataBrowserOnLoad')
+ L.Util.setBooleanFromQueryString(this.options, 'displayCaptionOnLoad')
+ L.Util.setBooleanFromQueryString(this.options, 'captionBar')
+ L.Util.setBooleanFromQueryString(this.options, 'captionMenus')
+ for (let i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
+ L.Util.setNullableBooleanFromQueryString(
+ this.options,
+ `${this.HIDDABLE_CONTROLS[i]}Control`
+ )
+ }
+ this.datalayersOnLoad = L.Util.queryString('datalayers')
+ this.options.onLoadPanel = L.Util.queryString(
+ 'onLoadPanel',
+ this.options.onLoadPanel
+ )
+ if (this.datalayersOnLoad)
+ this.datalayersOnLoad = this.datalayersOnLoad.toString().split(',')
+
+ if (L.Browser.ielt9) this.options.allowEdit = false // TODO include ie9
+
+ let editedFeature = null
+ const self = this
+ try {
+ Object.defineProperty(this, 'editedFeature', {
+ get() {
+ return editedFeature
+ },
+ set(feature) {
+ if (editedFeature && editedFeature !== feature) {
+ editedFeature.endEdit()
+ }
+ editedFeature = feature
+ self.fire('seteditedfeature')
+ },
+ })
+ } catch (e) {
+ // Certainly IE8, which has a limited version of defineProperty
}
-});
+ if (this.options.hash) this.addHash()
+ this.initCenter()
+ this.handleLimitBounds()
+
+ this.initTileLayers(this.options.tilelayers)
+
+ // Global storage for retrieving datalayers and features
+ this.datalayers = {}
+ this.datalayers_index = []
+ this.dirty_datalayers = []
+ this.features_index = {}
+
+ // Retrocompat
+ if (
+ this.options.slideshow &&
+ this.options.slideshow.delay &&
+ this.options.slideshow.active === undefined
+ )
+ this.options.slideshow.active = true
+
+ this.initControls()
+
+ // create datalayers
+ this.initDatalayers()
+
+ if (this.options.displayCaptionOnLoad) {
+ // Retrocompat
+ if (!this.options.onLoadPanel) {
+ this.options.onLoadPanel = 'caption'
+ }
+ delete this.options.displayCaptionOnLoad
+ }
+ if (this.options.displayDataBrowserOnLoad) {
+ // Retrocompat
+ if (!this.options.onLoadPanel) {
+ this.options.onLoadPanel = 'databrowser'
+ }
+ delete this.options.displayDataBrowserOnLoad
+ }
+
+ this.ui.on(
+ 'panel:closed',
+ function () {
+ this.invalidateSize({ pan: false })
+ },
+ this
+ )
+
+ let isDirty = false // global status
+ try {
+ Object.defineProperty(this, 'isDirty', {
+ get() {
+ return isDirty || this.dirty_datalayers.length
+ },
+ set(status) {
+ if (!isDirty && status) self.fire('isdirty')
+ isDirty = status
+ self.checkDirty()
+ },
+ })
+ } catch (e) {
+ // Certainly IE8, which has a limited version of defineProperty
+ }
+ this.on(
+ 'baselayerchange',
+ function (e) {
+ if (this._controls.miniMap) this._controls.miniMap.onMainMapBaseLayerChange(e)
+ },
+ this
+ )
+
+ // Creation mode
+ if (!this.options.umap_id) {
+ this.isDirty = true
+ this._default_extent = true
+ this.options.name = L._('Untitled map')
+ this.options.allowEdit = true
+ const datalayer = this.createDataLayer()
+ datalayer.connectToMap()
+ this.enableEdit()
+ let dataUrl = L.Util.queryString('dataUrl', null)
+ const dataFormat = L.Util.queryString('dataFormat', 'geojson')
+ if (dataUrl) {
+ dataUrl = decodeURIComponent(dataUrl)
+ dataUrl = this.localizeUrl(dataUrl)
+ dataUrl = this.proxyUrl(dataUrl)
+ datalayer.importFromUrl(dataUrl, dataFormat)
+ }
+ }
+
+ this.help = new L.U.Help(this)
+ this.slideshow = new L.U.Slideshow(this, this.options.slideshow)
+ this.permissions = new L.U.MapPermissions(this)
+ this.initCaptionBar()
+ if (this.options.allowEdit) {
+ this.editTools = new L.U.Editable(this)
+ this.ui.on(
+ 'panel:closed panel:open',
+ function () {
+ this.editedFeature = null
+ },
+ this
+ )
+ this.initEditBar()
+ }
+ this.initShortcuts()
+ this.onceDatalayersLoaded(function () {
+ if (this.options.onLoadPanel === 'databrowser') this.openBrowser()
+ else if (this.options.onLoadPanel === 'caption') this.displayCaption()
+ else if (this.options.onLoadPanel === 'datafilters') this.openFilter()
+ })
+ this.onceDataLoaded(function () {
+ const slug = L.Util.queryString('feature')
+ if (slug && this.features_index[slug]) this.features_index[slug].view()
+ })
+
+ window.onbeforeunload = (e) => {
+ const msg = L._('You have unsaved changes.')
+ if (self.isDirty) {
+ e.returnValue = msg
+ return msg
+ }
+ }
+ this.backup()
+ this.initContextMenu()
+ this.on('click contextmenu.show', this.closeInplaceToolbar)
+ },
+
+ initControls() {
+ this.helpMenuActions = {}
+ this._controls = {}
+
+ if (this.options.allowEdit && !this.options.noControl) {
+ new L.U.EditControl(this).addTo(this)
+
+ new L.U.DrawToolbar({ map: this }).addTo(this)
+
+ const editActions = [
+ L.U.ImportAction,
+ L.U.EditPropertiesAction,
+ L.U.ChangeTileLayerAction,
+ L.U.ManageDatalayersAction,
+ L.U.UpdateExtentAction,
+ L.U.UpdatePermsAction,
+ ]
+ new L.U.SettingsToolbar({ actions: editActions }).addTo(this)
+ }
+ this._controls.zoom = new L.Control.Zoom({
+ zoomInTitle: L._('Zoom in'),
+ zoomOutTitle: L._('Zoom out'),
+ })
+ this._controls.datalayers = new L.U.DataLayersControl(this)
+ this._controls.locate = L.control.locate({
+ strings: {
+ title: L._('Center map on your location'),
+ },
+ showPopup: false,
+ // We style this control in our own CSS for consistency with other controls,
+ // but the control breaks if we don't specify a class here, so a fake class
+ // will do.
+ icon: 'umap-fake-class',
+ iconLoading: 'umap-fake-class',
+ })
+ this._controls.fullscreen = new L.Control.Fullscreen({
+ title: { false: L._('View Fullscreen'), true: L._('Exit Fullscreen') },
+ })
+ this._controls.search = new L.U.SearchControl()
+ this._controls.embed = new L.Control.Embed(this, this.options.embedOptions)
+ this._controls.tilelayers = new L.U.TileLayerControl(this)
+ this._controls.editinosm = new L.Control.EditInOSM({
+ position: 'topleft',
+ widgetOptions: {
+ helpText: L._(
+ 'Open this map extent in a map editor to provide more accurate data to OpenStreetMap'
+ ),
+ },
+ })
+ this._controls.measure = new L.MeasureControl().initHandler(this)
+ this._controls.more = new L.U.MoreControls()
+ this._controls.scale = L.control.scale()
+ this._controls.permanentCredit = new L.U.PermanentCreditsControl(this)
+ if (this.options.scrollWheelZoom) this.scrollWheelZoom.enable()
+ else this.scrollWheelZoom.disable()
+ this.renderControls()
+ },
+
+ renderControls() {
+ L.DomUtil.classIf(
+ document.body,
+ 'umap-caption-bar-enabled',
+ this.options.captionBar ||
+ (this.options.slideshow && this.options.slideshow.active)
+ )
+ L.DomUtil.classIf(
+ document.body,
+ 'umap-slideshow-enabled',
+ this.options.slideshow && this.options.slideshow.active
+ )
+ for (var i in this._controls) {
+ this.removeControl(this._controls[i])
+ }
+ if (this.options.noControl) return
+
+ this._controls.attribution = new L.U.AttributionControl().addTo(this)
+ if (this.options.miniMap && !this.options.noControl) {
+ this.whenReady(function () {
+ if (this.selected_tilelayer) {
+ this._controls.miniMap = new L.Control.MiniMap(this.selected_tilelayer).addTo(
+ this
+ )
+ this._controls.miniMap._miniMap.invalidateSize()
+ }
+ })
+ }
+ let name
+ let status
+ let control
+ for (var i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
+ name = this.HIDDABLE_CONTROLS[i]
+ status = this.options[`${name}Control`]
+ if (status === false) continue
+ control = this._controls[name]
+ control.addTo(this)
+ if (status === undefined || status === null)
+ L.DomUtil.addClass(control._container, 'display-on-more')
+ else L.DomUtil.removeClass(control._container, 'display-on-more')
+ }
+ if (this.options.permanentCredit) this._controls.permanentCredit.addTo(this)
+ if (this.options.moreControl) this._controls.more.addTo(this)
+ if (this.options.scaleControl) this._controls.scale.addTo(this)
+ },
+
+ initDatalayers() {
+ let toload = (dataToload = seen = this.options.datalayers.length)
+ const self = this
+ let datalayer
+ const loaded = () => {
+ self.datalayersLoaded = true
+ self.fire('datalayersloaded')
+ }
+ const decrementToLoad = () => {
+ toload--
+ if (toload === 0) loaded()
+ }
+ const dataLoaded = () => {
+ self.dataLoaded = true
+ self.fire('dataloaded')
+ }
+ const decrementDataToLoad = () => {
+ dataToload--
+ if (dataToload === 0) dataLoaded()
+ }
+ for (let j = 0; j < this.options.datalayers.length; j++) {
+ datalayer = this.createDataLayer(this.options.datalayers[j])
+ if (datalayer.displayedOnLoad()) datalayer.onceLoaded(decrementToLoad)
+ else decrementToLoad()
+ if (datalayer.displayedOnLoad()) datalayer.onceDataLoaded(decrementDataToLoad)
+ else decrementDataToLoad()
+ }
+ if (seen === 0) loaded() && dataLoaded() // no datalayer
+ },
+
+ indexDatalayers() {
+ const panes = this.getPane('overlayPane')
+ let pane
+ this.datalayers_index = []
+ for (let i = 0; i < panes.children.length; i++) {
+ pane = panes.children[i]
+ if (!pane.dataset || !pane.dataset.id) continue
+ this.datalayers_index.push(this.datalayers[pane.dataset.id])
+ }
+ this.updateDatalayersControl()
+ },
+
+ ensurePanesOrder() {
+ this.eachDataLayer((datalayer) => {
+ datalayer.bringToTop()
+ })
+ },
+
+ onceDatalayersLoaded(callback, context) {
+ // Once datalayers **metadata** have been loaded
+ if (this.datalayersLoaded) {
+ callback.call(context || this, this)
+ } else {
+ this.once('datalayersloaded', callback, context)
+ }
+ return this
+ },
+
+ onceDataLoaded(callback, context) {
+ // Once datalayers **data** have been loaded
+ if (this.dataLoaded) {
+ callback.call(context || this, this)
+ } else {
+ this.once('dataloaded', callback, context)
+ }
+ return this
+ },
+
+ updateDatalayersControl() {
+ if (this._controls.datalayers) this._controls.datalayers.update()
+ },
+
+ backupOptions() {
+ this._backupOptions = L.extend({}, this.options)
+ this._backupOptions.tilelayer = L.extend({}, this.options.tilelayer)
+ this._backupOptions.limitBounds = L.extend({}, this.options.limitBounds)
+ this._backupOptions.permissions = L.extend({}, this.permissions.options)
+ },
+
+ resetOptions() {
+ this.options = L.extend({}, this._backupOptions)
+ this.options.tilelayer = L.extend({}, this._backupOptions.tilelayer)
+ this.permissions.options = L.extend({}, this._backupOptions.permissions)
+ },
+
+ initShortcuts() {
+ const globalShortcuts = function (e) {
+ const key = e.keyCode
+ const modifierKey = e.ctrlKey || e.metaKey
+
+ /* Generic shortcuts */
+ if (key === L.U.Keys.F && modifierKey) {
+ L.DomEvent.stop(e)
+ this.search()
+ } else if (e.keyCode === L.U.Keys.ESC) {
+ if (this.help.visible()) this.help.hide()
+ else this.ui.closePanel()
+ }
+
+ if (!this.options.allowEdit) return
+
+ /* Edit mode only shortcuts */
+ if (key === L.U.Keys.E && modifierKey && !this.editEnabled) {
+ L.DomEvent.stop(e)
+ this.enableEdit()
+ } else if (
+ key === L.U.Keys.E &&
+ modifierKey &&
+ this.editEnabled &&
+ !this.isDirty
+ ) {
+ L.DomEvent.stop(e)
+ this.disableEdit()
+ this.ui.closePanel()
+ }
+ if (key === L.U.Keys.S && modifierKey) {
+ L.DomEvent.stop(e)
+ if (this.isDirty) {
+ this.save()
+ }
+ }
+ if (key === L.U.Keys.Z && modifierKey && this.isDirty) {
+ L.DomEvent.stop(e)
+ this.askForReset()
+ }
+ if (key === L.U.Keys.M && modifierKey && this.editEnabled) {
+ L.DomEvent.stop(e)
+ this.editTools.startMarker()
+ }
+ if (key === L.U.Keys.P && modifierKey && this.editEnabled) {
+ L.DomEvent.stop(e)
+ this.editTools.startPolygon()
+ }
+ if (key === L.U.Keys.L && modifierKey && this.editEnabled) {
+ L.DomEvent.stop(e)
+ this.editTools.startPolyline()
+ }
+ if (key === L.U.Keys.I && modifierKey && this.editEnabled) {
+ L.DomEvent.stop(e)
+ this.importPanel()
+ }
+ if (key === L.U.Keys.H && modifierKey && this.editEnabled) {
+ L.DomEvent.stop(e)
+ this.help.show('edit')
+ }
+ if (e.keyCode === L.U.Keys.ESC) {
+ if (this.editEnabled) this.editTools.stopDrawing()
+ if (this.measureTools.enabled()) this.measureTools.stopDrawing()
+ }
+ }
+ L.DomEvent.addListener(document, 'keydown', globalShortcuts, this)
+ },
+
+ initTileLayers() {
+ this.tilelayers = []
+ for (const i in this.options.tilelayers) {
+ if (this.options.tilelayers.hasOwnProperty(i)) {
+ this.tilelayers.push(this.createTileLayer(this.options.tilelayers[i]))
+ if (
+ this.options.tilelayer &&
+ this.options.tilelayer.url_template ===
+ this.options.tilelayers[i].url_template
+ ) {
+ // Keep control over the displayed attribution for non custom tilelayers
+ this.options.tilelayer.attribution = this.options.tilelayers[i].attribution
+ }
+ }
+ }
+ if (
+ this.options.tilelayer &&
+ this.options.tilelayer.url_template &&
+ this.options.tilelayer.attribution
+ ) {
+ this.customTilelayer = this.createTileLayer(this.options.tilelayer)
+ this.selectTileLayer(this.customTilelayer)
+ } else {
+ this.selectTileLayer(this.tilelayers[0])
+ }
+ },
+
+ createTileLayer(tilelayer) {
+ return new L.TileLayer(tilelayer.url_template, tilelayer)
+ },
+
+ selectTileLayer(tilelayer) {
+ if (tilelayer === this.selected_tilelayer) {
+ return
+ }
+ try {
+ this.addLayer(tilelayer)
+ this.fire('baselayerchange', { layer: tilelayer })
+ if (this.selected_tilelayer) {
+ this.removeLayer(this.selected_tilelayer)
+ }
+ this.selected_tilelayer = tilelayer
+ if (
+ !isNaN(this.selected_tilelayer.options.minZoom) &&
+ this.getZoom() < this.selected_tilelayer.options.minZoom
+ ) {
+ this.setZoom(this.selected_tilelayer.options.minZoom)
+ }
+ if (
+ !isNaN(this.selected_tilelayer.options.maxZoom) &&
+ this.getZoom() > this.selected_tilelayer.options.maxZoom
+ ) {
+ this.setZoom(this.selected_tilelayer.options.maxZoom)
+ }
+ } catch (e) {
+ this.removeLayer(tilelayer)
+ this.ui.alert({
+ content: `${L._('Error in the tilelayer URL')}: ${tilelayer._url}`,
+ level: 'error',
+ })
+ // Users can put tilelayer URLs by hand, and if they add wrong {variable},
+ // Leaflet throw an error, and then the map is no more editable
+ }
+ this.setOverlay()
+ },
+
+ eachTileLayer(method, context) {
+ const urls = []
+ for (const i in this.tilelayers) {
+ if (this.tilelayers.hasOwnProperty(i)) {
+ method.call(context, this.tilelayers[i])
+ urls.push(this.tilelayers[i]._url)
+ }
+ }
+ if (
+ this.customTilelayer &&
+ Array.prototype.indexOf &&
+ !urls.includes(this.customTilelayer._url)
+ ) {
+ method.call(context || this, this.customTilelayer)
+ }
+ },
+
+ setOverlay() {
+ if (!this.options.overlay || !this.options.overlay.url_template) return
+ const overlay = this.createTileLayer(this.options.overlay)
+ try {
+ this.addLayer(overlay)
+ if (this.overlay) this.removeLayer(this.overlay)
+ this.overlay = overlay
+ } catch (e) {
+ this.removeLayer(overlay)
+ console.error(e)
+ this.ui.alert({
+ content: `${L._('Error in the overlay URL')}: ${overlay._url}`,
+ level: 'error',
+ })
+ }
+ },
+
+ initCenter() {
+ if (this.options.hash && this._hash.parseHash(location.hash)) {
+ // FIXME An invalid hash will cause the load to fail
+ this._hash.update()
+ } else if (this.options.locate && this.options.locate.setView) {
+ // Prevent from making two setViews at init
+ // which is not very fluid...
+ this.locate(this.options.locate)
+ } else {
+ this.options.center = this.latLng(this.options.center)
+ this.setView(this.options.center, this.options.zoom)
+ }
+ },
+
+ latLng(a, b, c) {
+ // manage geojson case and call original method
+ if (!(a instanceof L.LatLng) && a.coordinates) {
+ // Guess it's a geojson
+ a = [a.coordinates[1], a.coordinates[0]]
+ }
+ return L.latLng(a, b, c)
+ },
+
+ handleLimitBounds() {
+ const south = parseFloat(this.options.limitBounds.south)
+ const west = parseFloat(this.options.limitBounds.west)
+ const north = parseFloat(this.options.limitBounds.north)
+ const east = parseFloat(this.options.limitBounds.east)
+ if (!isNaN(south) && !isNaN(west) && !isNaN(north) && !isNaN(east)) {
+ const bounds = L.latLngBounds([
+ [south, west],
+ [north, east],
+ ])
+ this.options.minZoom = this.getBoundsZoom(bounds, false)
+ try {
+ this.setMaxBounds(bounds)
+ } catch (e) {
+ // Unusable bounds, like -2 -2 -2 -2?
+ console.error('Error limiting bounds', e)
+ }
+ } else {
+ this.options.minZoom = 0
+ this.setMaxBounds()
+ }
+ },
+
+ setMaxBounds(bounds) {
+ // Hack. Remove me when fix is released:
+ // https://github.com/Leaflet/Leaflet/pull/4494
+ bounds = L.latLngBounds(bounds)
+
+ if (!bounds.isValid()) {
+ this.options.maxBounds = null
+ return this.off('moveend', this._panInsideMaxBounds)
+ }
+ return L.Map.prototype.setMaxBounds.call(this, bounds)
+ },
+
+ createDataLayer(
+ datalayer = { name: `${L._('Layer')} ${this.datalayers_index.length + 1}` }
+ ) {
+ return new L.U.DataLayer(this, datalayer)
+ },
+
+ getDefaultOption(option) {
+ return this.options[`default_${option}`]
+ },
+
+ getOption(option) {
+ if (L.Util.usableOption(this.options, option)) return this.options[option]
+ return this.getDefaultOption(option)
+ },
+
+ updateExtent() {
+ this.options.center = this.getCenter()
+ this.options.zoom = this.getZoom()
+ this.isDirty = true
+ this._default_extent = false
+ this.ui.alert({ content: L._('The zoom and center have been set.'), level: 'info' })
+ },
+
+ updateTileLayers() {
+ const self = this
+
+ const callback = (tilelayer) => {
+ self.options.tilelayer = tilelayer.toJSON()
+ self.isDirty = true
+ }
+
+ if (this._controls.tilelayers)
+ this._controls.tilelayers.openSwitcher({ callback, className: 'dark' })
+ },
+
+ manageDatalayers() {
+ if (this._controls.datalayers) this._controls.datalayers.openPanel()
+ },
+
+ renderShareBox() {
+ const container = L.DomUtil.create('div', 'umap-share')
+ const embedTitle = L.DomUtil.add('h4', '', container, L._('Embed the map'))
+ const iframe = L.DomUtil.create('textarea', 'umap-share-iframe', container)
+ let option
+ const UIFields = [
+ ['dimensions.width', { handler: 'Input', label: L._('width') }],
+ ['dimensions.height', { handler: 'Input', label: L._('height') }],
+ [
+ 'options.includeFullScreenLink',
+ { handler: 'Switch', label: L._('Include full screen link?') },
+ ],
+ [
+ 'options.currentView',
+ { handler: 'Switch', label: L._('Current view instead of default map view?') },
+ ],
+ [
+ 'options.keepCurrentDatalayers',
+ { handler: 'Switch', label: L._('Keep current visible layers') },
+ ],
+ [
+ 'options.viewCurrentFeature',
+ { handler: 'Switch', label: L._('Open current feature on load') },
+ ],
+ 'queryString.moreControl',
+ 'queryString.scrollWheelZoom',
+ 'queryString.miniMap',
+ 'queryString.scaleControl',
+ 'queryString.onLoadPanel',
+ 'queryString.captionBar',
+ 'queryString.captionMenus',
+ ]
+ for (let i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
+ UIFields.push(`queryString.${this.HIDDABLE_CONTROLS[i]}Control`)
+ }
+ const iframeExporter = new L.U.IframeExporter(this)
+ const buildIframeCode = () => {
+ iframe.innerHTML = iframeExporter.build()
+ }
+ buildIframeCode()
+ const builder = new L.U.FormBuilder(iframeExporter, UIFields, {
+ callback: buildIframeCode,
+ })
+ const iframeOptions = L.DomUtil.createFieldset(
+ container,
+ L._('Iframe export options')
+ )
+ iframeOptions.appendChild(builder.build())
+ if (this.options.shortUrl) {
+ L.DomUtil.create('hr', '', container)
+ L.DomUtil.add('h4', '', container, L._('Short URL'))
+ const shortUrl = L.DomUtil.create('input', 'umap-short-url', container)
+ shortUrl.type = 'text'
+ shortUrl.value = this.options.shortUrl
+ }
+ L.DomUtil.create('hr', '', container)
+ L.DomUtil.add('h4', '', container, L._('Download data'))
+ const typeInput = L.DomUtil.create('select', '', container)
+ typeInput.name = 'format'
+ const exportCaveat = L.DomUtil.add(
+ 'small',
+ 'help-text',
+ container,
+ L._('Only visible features will be downloaded.')
+ )
+ exportCaveat.id = 'export_caveat_text'
+ const toggleCaveat = () => {
+ if (typeInput.value === 'umap') exportCaveat.style.display = 'none'
+ else exportCaveat.style.display = 'inherit'
+ }
+ L.DomEvent.on(typeInput, 'change', toggleCaveat)
+ const types = {
+ geojson: {
+ formatter(map) {
+ return JSON.stringify(map.toGeoJSON(), null, 2)
+ },
+ ext: '.geojson',
+ filetype: 'application/json',
+ },
+ gpx: {
+ formatter(map) {
+ return togpx(map.toGeoJSON())
+ },
+ ext: '.gpx',
+ filetype: 'application/xml',
+ },
+ kml: {
+ formatter(map) {
+ return tokml(map.toGeoJSON())
+ },
+ ext: '.kml',
+ filetype: 'application/vnd.google-earth.kml+xml',
+ },
+ umap: {
+ name: L._('Full map data'),
+ formatter(map) {
+ return map.serialize()
+ },
+ ext: '.umap',
+ filetype: 'application/json',
+ selected: true,
+ },
+ }
+ for (const key in types) {
+ if (types.hasOwnProperty(key)) {
+ option = L.DomUtil.create('option', '', typeInput)
+ option.value = key
+ option.textContent = types[key].name || key
+ if (types[key].selected) option.selected = true
+ }
+ }
+ toggleCaveat()
+ const download = L.DomUtil.create('a', 'button', container)
+ download.textContent = L._('Download data')
+ L.DomEvent.on(
+ download,
+ 'click',
+ function () {
+ const type = types[typeInput.value]
+ const content = type.formatter(this)
+ let name = this.options.name || 'data'
+ name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase()
+ download.download = name + type.ext
+ window.URL = window.URL || window.webkitURL
+ const blob = new Blob([content], { type: type.filetype })
+ download.href = window.URL.createObjectURL(blob)
+ },
+ this
+ )
+ this.ui.openPanel({ data: { html: container } })
+ },
+
+ toGeoJSON() {
+ let features = []
+ this.eachDataLayer((datalayer) => {
+ if (datalayer.isVisible()) {
+ features = features.concat(datalayer.featuresToGeoJSON())
+ }
+ })
+ const geojson = {
+ type: 'FeatureCollection',
+ features,
+ }
+ return geojson
+ },
+
+ importPanel() {
+ const container = L.DomUtil.create('div', 'umap-upload')
+ const title = L.DomUtil.create('h4', '', container)
+ const presetBox = L.DomUtil.create('div', 'formbox', container)
+ const presetSelect = L.DomUtil.create('select', '', presetBox)
+ const fileBox = L.DomUtil.create('div', 'formbox', container)
+ const fileInput = L.DomUtil.create('input', '', fileBox)
+ const urlInput = L.DomUtil.create('input', '', container)
+ const rawInput = L.DomUtil.create('textarea', '', container)
+ const typeLabel = L.DomUtil.create('label', '', container)
+ const layerLabel = L.DomUtil.create('label', '', container)
+ const clearLabel = L.DomUtil.create('label', '', container)
+ const submitInput = L.DomUtil.create('input', '', container)
+ const map = this
+ let option
+ const types = ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap']
+ title.textContent = L._('Import data')
+ fileInput.type = 'file'
+ fileInput.multiple = 'multiple'
+ submitInput.type = 'button'
+ submitInput.value = L._('Import')
+ submitInput.className = 'button'
+ typeLabel.textContent = L._('Choose the format of the data to import')
+ this.help.button(typeLabel, 'importFormats')
+ const typeInput = L.DomUtil.create('select', '', typeLabel)
+ typeInput.name = 'format'
+ layerLabel.textContent = L._('Choose the layer to import in')
+ const layerInput = L.DomUtil.create('select', '', layerLabel)
+ layerInput.name = 'datalayer'
+ urlInput.type = 'text'
+ urlInput.placeholder = L._('Provide an URL here')
+ rawInput.placeholder = L._('Paste your data here')
+ clearLabel.textContent = L._('Replace layer content')
+ const clearFlag = L.DomUtil.create('input', '', clearLabel)
+ clearFlag.type = 'checkbox'
+ clearFlag.name = 'clear'
+ this.eachDataLayerReverse((datalayer) => {
+ if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) {
+ const id = L.stamp(datalayer)
+ option = L.DomUtil.create('option', '', layerInput)
+ option.value = id
+ option.textContent = datalayer.options.name
+ }
+ })
+ L.DomUtil.element(
+ 'option',
+ { value: '', textContent: L._('Import in a new layer') },
+ layerInput
+ )
+ L.DomUtil.element(
+ 'option',
+ { value: '', textContent: L._('Choose the data format') },
+ typeInput
+ )
+ for (let i = 0; i < types.length; i++) {
+ option = L.DomUtil.create('option', '', typeInput)
+ option.value = option.textContent = types[i]
+ }
+ if (this.options.importPresets.length) {
+ const noPreset = L.DomUtil.create('option', '', presetSelect)
+ noPreset.value = noPreset.textContent = L._('Choose a preset')
+ for (let j = 0; j < this.options.importPresets.length; j++) {
+ option = L.DomUtil.create('option', '', presetSelect)
+ option.value = this.options.importPresets[j].url
+ option.textContent = this.options.importPresets[j].label
+ }
+ } else {
+ presetBox.style.display = 'none'
+ }
+
+ const submit = function () {
+ let type = typeInput.value
+ const layerId = layerInput[layerInput.selectedIndex].value
+ let layer
+ if (type === 'umap') {
+ this.once('postsync', function () {
+ this.setView(this.latLng(this.options.center), this.options.zoom)
+ })
+ }
+ if (layerId) layer = map.datalayers[layerId]
+ if (layer && clearFlag.checked) layer.empty()
+ if (fileInput.files.length) {
+ var file
+ for (let i = 0, file; (file = fileInput.files[i]); i++) {
+ type = type || L.Util.detectFileType(file)
+ if (!type) {
+ this.ui.alert({
+ content: L._('Unable to detect format of file {filename}', {
+ filename: file.name,
+ }),
+ level: 'error',
+ })
+ continue
+ }
+ if (type === 'umap') {
+ this.importFromFile(file, 'umap')
+ } else {
+ let importLayer = layer
+ if (!layer) importLayer = this.createDataLayer({ name: file.name })
+ importLayer.importFromFile(file, type)
+ }
+ }
+ } else {
+ if (!type)
+ return this.ui.alert({
+ content: L._('Please choose a format'),
+ level: 'error',
+ })
+ if (rawInput.value && type === 'umap') {
+ try {
+ this.importRaw(rawInput.value, type)
+ } catch (e) {
+ this.ui.alert({ content: L._('Invalid umap data'), level: 'error' })
+ console.error(e)
+ }
+ } else {
+ if (!layer) layer = this.createDataLayer()
+ if (rawInput.value) layer.importRaw(rawInput.value, type)
+ else if (urlInput.value) layer.importFromUrl(urlInput.value, type)
+ else if (presetSelect.selectedIndex > 0)
+ layer.importFromUrl(presetSelect[presetSelect.selectedIndex].value, type)
+ }
+ }
+ }
+ L.DomEvent.on(submitInput, 'click', submit, this)
+ L.DomEvent.on(
+ fileInput,
+ 'change',
+ ({ target }) => {
+ let type = ''
+ let newType
+ for (let i = 0; i < target.files.length; i++) {
+ newType = L.Util.detectFileType(target.files[i])
+ if (!type && newType) type = newType
+ if (type && newType !== type) {
+ type = ''
+ break
+ }
+ }
+ typeInput.value = type
+ },
+ this
+ )
+ this.ui.openPanel({ data: { html: container }, className: 'dark' })
+ },
+
+ importRaw(rawData) {
+ const importedData = JSON.parse(rawData)
+
+ let mustReindex = false
+
+ for (const option of this.editableOptions) {
+ if (typeof importedData.properties[option] !== 'undefined') {
+ this.options[option] = importedData.properties[option]
+ if (option === 'sortKey') mustReindex = true
+ }
+ }
+
+ if (importedData.geometry) this.options.center = this.latLng(importedData.geometry)
+ const self = this
+ importedData.layers.forEach((geojson) => {
+ const dataLayer = self.createDataLayer()
+ dataLayer.fromUmapGeoJSON(geojson)
+ })
+
+ this.initTileLayers()
+ this.renderControls()
+ this.handleLimitBounds()
+ this.eachDataLayer((datalayer) => {
+ if (mustReindex) datalayer.reindex()
+ datalayer.redraw()
+ })
+ this.fire('postsync')
+ this.isDirty = true
+ },
+
+ importFromFile(file) {
+ const reader = new FileReader()
+ reader.readAsText(file)
+ const self = this
+ reader.onload = ({ target }) => {
+ const rawData = target.result
+ try {
+ self.importRaw(rawData)
+ } catch (e) {
+ console.error('Error importing data', e)
+ self.ui.alert({
+ content: L._('Invalid umap data in {filename}', { filename: file.name }),
+ level: 'error',
+ })
+ }
+ }
+ },
+
+ openBrowser() {
+ this.onceDatalayersLoaded(function () {
+ this._openBrowser()
+ })
+ },
+
+ openFilter() {
+ this.onceDatalayersLoaded(function () {
+ this._openFilter()
+ })
+ },
+
+ displayCaption() {
+ const container = L.DomUtil.create('div', 'umap-caption')
+ let title = L.DomUtil.create('h3', '', container)
+ title.textContent = this.options.name
+ this.permissions.addOwnerLink('h5', container)
+ if (this.options.description) {
+ const description = L.DomUtil.create('div', 'umap-map-description', container)
+ description.innerHTML = L.Util.toHTML(this.options.description)
+ }
+ const datalayerContainer = L.DomUtil.create('div', 'datalayer-container', container)
+ this.eachVisibleDataLayer((datalayer) => {
+ const p = L.DomUtil.create('p', '', datalayerContainer)
+ const color = L.DomUtil.create('span', 'datalayer-color', p)
+ const headline = L.DomUtil.create('strong', '', p)
+ const description = L.DomUtil.create('span', '', p)
+ datalayer.onceLoaded(function () {
+ color.style.backgroundColor = this.getColor()
+ if (datalayer.options.description) {
+ description.innerHTML = L.Util.toHTML(datalayer.options.description)
+ }
+ })
+ datalayer.renderToolbox(headline)
+ L.DomUtil.add('span', '', headline, `${datalayer.options.name} `)
+ })
+ const creditsContainer = L.DomUtil.create('div', 'credits-container', container)
+ const credits = L.DomUtil.createFieldset(creditsContainer, L._('Credits'))
+ title = L.DomUtil.add('h5', '', credits, L._('User content credits'))
+ if (this.options.shortCredit || this.options.longCredit) {
+ L.DomUtil.add(
+ 'p',
+ '',
+ credits,
+ L.Util.toHTML(this.options.longCredit || this.options.shortCredit)
+ )
+ }
+ if (this.options.licence) {
+ const licence = L.DomUtil.add(
+ 'p',
+ '',
+ credits,
+ `${L._('Map user content has been published under licence')} `
+ )
+ const link = L.DomUtil.add('a', '', licence, this.options.licence.name)
+ link.href = this.options.licence.url
+ } else {
+ L.DomUtil.add('p', '', credits, L._('No licence has been set'))
+ }
+ L.DomUtil.create('hr', '', credits)
+ title = L.DomUtil.create('h5', '', credits)
+ title.textContent = L._('Map background credits')
+ const tilelayerCredit = L.DomUtil.create('p', '', credits)
+ const name = L.DomUtil.create('strong', '', tilelayerCredit)
+ const attribution = L.DomUtil.create('span', '', tilelayerCredit)
+ name.textContent = `${this.selected_tilelayer.options.name} `
+ attribution.innerHTML = this.selected_tilelayer.getAttribution()
+ L.DomUtil.create('hr', '', credits)
+ const umapCredit = L.DomUtil.create('p', '', credits)
+
+ const urls = {
+ leaflet: 'http://leafletjs.com',
+ django: 'https://www.djangoproject.com',
+ umap: 'http://wiki.openstreetmap.org/wiki/UMap',
+ }
+
+ umapCredit.innerHTML = L._(
+ 'Powered by Leaflet and Django, glued by uMap project.',
+ urls
+ )
+ const browser = L.DomUtil.create('li', '')
+ L.DomUtil.create('i', 'umap-icon-16 umap-list', browser)
+ const labelBrowser = L.DomUtil.create('span', '', browser)
+ labelBrowser.textContent = labelBrowser.title = L._('Browse data')
+ L.DomEvent.on(browser, 'click', this.openBrowser, this)
+ const actions = [browser]
+ if (this.options.advancedFilterKey) {
+ const filter = L.DomUtil.create('li', '')
+ L.DomUtil.create('i', 'umap-icon-16 umap-add', filter)
+ const labelFilter = L.DomUtil.create('span', '', filter)
+ labelFilter.textContent = labelFilter.title = L._('Select data')
+ L.DomEvent.on(filter, 'click', this.openFilter, this)
+ actions.push(filter)
+ }
+ this.ui.openPanel({ data: { html: container }, actions })
+ },
+
+ eachDataLayer(method, context) {
+ for (let i = 0; i < this.datalayers_index.length; i++) {
+ method.call(context, this.datalayers_index[i])
+ }
+ },
+
+ eachDataLayerReverse(method, context, filter) {
+ for (let i = this.datalayers_index.length - 1; i >= 0; i--) {
+ if (filter && !filter.call(context, this.datalayers_index[i])) continue
+ method.call(context, this.datalayers_index[i])
+ }
+ },
+
+ eachBrowsableDataLayer(method, context) {
+ this.eachDataLayerReverse(method, context, (d) => d.allowBrowse())
+ },
+
+ eachVisibleDataLayer(method, context) {
+ this.eachDataLayerReverse(method, context, (d) => d.isVisible())
+ },
+
+ findDataLayer(method, context) {
+ for (let i = this.datalayers_index.length - 1; i >= 0; i--) {
+ if (method.call(context, this.datalayers_index[i]))
+ return this.datalayers_index[i]
+ }
+ },
+
+ backup() {
+ this.backupOptions()
+ this._datalayers_index_bk = [].concat(this.datalayers_index)
+ },
+
+ reset() {
+ if (this.editTools) this.editTools.stopDrawing()
+ this.resetOptions()
+ this.datalayers_index = [].concat(this._datalayers_index_bk)
+ this.dirty_datalayers.slice().forEach((datalayer) => {
+ if (datalayer.isDeleted) datalayer.connectToMap()
+ datalayer.reset()
+ })
+ this.ensurePanesOrder()
+ this.dirty_datalayers = []
+ this.updateDatalayersControl()
+ this.initTileLayers()
+ this.isDirty = false
+ },
+
+ checkDirty() {
+ L.DomUtil.classIf(this._container, 'umap-is-dirty', this.isDirty)
+ },
+
+ addDirtyDatalayer(datalayer) {
+ if (!this.dirty_datalayers.includes(datalayer)) {
+ this.dirty_datalayers.push(datalayer)
+ this.isDirty = true
+ }
+ },
+
+ removeDirtyDatalayer(datalayer) {
+ if (this.dirty_datalayers.includes(datalayer)) {
+ this.dirty_datalayers.splice(this.dirty_datalayers.indexOf(datalayer), 1)
+ this.checkDirty()
+ }
+ },
+
+ continueSaving() {
+ if (this.dirty_datalayers.length) this.dirty_datalayers[0].save()
+ else this.fire('saved')
+ },
+
+ editableOptions: [
+ 'zoom',
+ 'scrollWheelZoom',
+ 'scaleControl',
+ 'moreControl',
+ 'miniMap',
+ 'displayPopupFooter',
+ 'onLoadPanel',
+ 'tilelayersControl',
+ 'name',
+ 'description',
+ 'licence',
+ 'tilelayer',
+ 'overlay',
+ 'limitBounds',
+ 'color',
+ 'iconClass',
+ 'iconUrl',
+ 'smoothFactor',
+ 'opacity',
+ 'weight',
+ 'fill',
+ 'fillColor',
+ 'fillOpacity',
+ 'dashArray',
+ 'popupShape',
+ 'popupTemplate',
+ 'popupContentTemplate',
+ 'zoomTo',
+ 'captionBar',
+ 'captionMenus',
+ 'slideshow',
+ 'sortKey',
+ 'labelKey',
+ 'filterKey',
+ 'advancedFilterKey',
+ 'slugKey',
+ 'showLabel',
+ 'labelDirection',
+ 'labelInteractive',
+ 'shortCredit',
+ 'longCredit',
+ 'permanentCredit',
+ 'permanentCreditBackground',
+ 'zoomControl',
+ 'datalayersControl',
+ 'searchControl',
+ 'locateControl',
+ 'fullscreenControl',
+ 'editinosmControl',
+ 'embedControl',
+ 'measureControl',
+ 'tilelayersControl',
+ 'easing',
+ ],
+
+ exportOptions() {
+ const properties = {}
+ for (let i = this.editableOptions.length - 1; i >= 0; i--) {
+ if (typeof this.options[this.editableOptions[i]] !== 'undefined') {
+ properties[this.editableOptions[i]] = this.options[this.editableOptions[i]]
+ }
+ }
+ return properties
+ },
+
+ serialize() {
+ const umapfile = {
+ type: 'umap',
+ uri: window.location.href,
+ properties: this.exportOptions(),
+ geometry: this.geometry(),
+ layers: [],
+ }
+
+ this.eachDataLayer((datalayer) => {
+ umapfile.layers.push(datalayer.umapGeoJSON())
+ })
+
+ return JSON.stringify(umapfile, null, 2)
+ },
+
+ save() {
+ if (!this.isDirty) return
+ if (this._default_extent) this.updateExtent()
+ const geojson = {
+ type: 'Feature',
+ geometry: this.geometry(),
+ properties: this.exportOptions(),
+ }
+ this.backup()
+ const formData = new FormData()
+ formData.append('name', this.options.name)
+ formData.append('center', JSON.stringify(this.geometry()))
+ formData.append('settings', JSON.stringify(geojson))
+ this.post(this.getSaveUrl(), {
+ data: formData,
+ context: this,
+ callback({ id, permissions, url, info }) {
+ let duration = 3000
+ if (!this.options.umap_id) {
+ duration = 100000 // we want a longer message at map creation (TODO UGLY)
+ this.options.umap_id = id
+ this.permissions.setOptions(permissions)
+ } else if (!this.permissions.isDirty) {
+ // Do not override local changes to permissions,
+ // but update in case some other editors changed them in the meantime.
+ this.permissions.setOptions(permissions)
+ }
+ // Update URL in case the name has changed.
+ if (history && history.pushState) history.pushState({}, this.options.name, url)
+ else window.location = url
+ if (info) msg = info
+ else msg = L._('Map has been saved!')
+ this.once('saved', function () {
+ this.isDirty = false
+ this.ui.alert({ content: msg, level: 'info', duration })
+ })
+ this.ui.closePanel()
+ this.permissions.save()
+ },
+ })
+ },
+
+ getEditUrl() {
+ return L.Util.template(this.options.urls.map_update, {
+ map_id: this.options.umap_id,
+ })
+ },
+
+ getCreateUrl() {
+ return L.Util.template(this.options.urls.map_create)
+ },
+
+ getSaveUrl() {
+ return (this.options.umap_id && this.getEditUrl()) || this.getCreateUrl()
+ },
+
+ geometry() {
+ /* Return a GeoJSON geometry Object */
+ const latlng = this.latLng(this.options.center || this.getCenter())
+ return {
+ type: 'Point',
+ coordinates: [latlng.lng, latlng.lat],
+ }
+ },
+
+ defaultDataLayer() {
+ let datalayer
+ let fallback
+ datalayer = this.lastUsedDataLayer
+ if (
+ datalayer &&
+ !datalayer.isRemoteLayer() &&
+ datalayer.canBrowse() &&
+ datalayer.isVisible()
+ ) {
+ return datalayer
+ }
+ datalayer = this.findDataLayer((datalayer) => {
+ if (!datalayer.isRemoteLayer() && datalayer.canBrowse()) {
+ fallback = datalayer
+ if (datalayer.isVisible()) return true
+ }
+ })
+ if (datalayer) return datalayer
+ if (fallback) {
+ // No datalayer visible, let's force one
+ this.addLayer(fallback.layer)
+ return fallback
+ }
+ return this.createDataLayer()
+ },
+
+ getDataLayerByUmapId(umap_id) {
+ return this.findDataLayer((d) => d.umap_id == umap_id)
+ },
+
+ _editControls(container) {
+ let UIFields = []
+ for (let i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
+ UIFields.push(`options.${this.HIDDABLE_CONTROLS[i]}Control`)
+ }
+ UIFields = UIFields.concat([
+ 'options.moreControl',
+ 'options.scrollWheelZoom',
+ 'options.miniMap',
+ 'options.scaleControl',
+ 'options.onLoadPanel',
+ 'options.displayPopupFooter',
+ 'options.captionBar',
+ 'options.captionMenus',
+ ])
+ builder = new L.U.FormBuilder(this, UIFields, {
+ callback() {
+ this.renderControls()
+ this.initCaptionBar()
+ },
+ callbackContext: this,
+ })
+ const controlsOptions = L.DomUtil.createFieldset(
+ container,
+ L._('User interface options')
+ )
+ controlsOptions.appendChild(builder.build())
+ },
+
+ _editShapeProperties(container) {
+ const shapeOptions = [
+ 'options.color',
+ 'options.iconClass',
+ 'options.iconUrl',
+ 'options.opacity',
+ 'options.weight',
+ 'options.fill',
+ 'options.fillColor',
+ 'options.fillOpacity',
+ ]
+
+ builder = new L.U.FormBuilder(this, shapeOptions, {
+ callback(e) {
+ this.eachDataLayer((datalayer) => {
+ datalayer.redraw()
+ })
+ },
+ })
+ const defaultShapeProperties = L.DomUtil.createFieldset(
+ container,
+ L._('Default shape properties')
+ )
+ defaultShapeProperties.appendChild(builder.build())
+ },
+
+ _editDefaultProperties(container) {
+ const optionsFields = [
+ 'options.smoothFactor',
+ 'options.dashArray',
+ 'options.zoomTo',
+ ['options.easing', { handler: 'Switch', label: L._('Animated transitions') }],
+ 'options.labelKey',
+ [
+ 'options.sortKey',
+ {
+ handler: 'BlurInput',
+ helpEntries: 'sortKey',
+ placeholder: L._('Default: name'),
+ label: L._('Sort key'),
+ inheritable: true,
+ },
+ ],
+ [
+ 'options.filterKey',
+ {
+ handler: 'Input',
+ helpEntries: 'filterKey',
+ placeholder: L._('Default: name'),
+ label: L._('Filter keys'),
+ inheritable: true,
+ },
+ ],
+ [
+ 'options.advancedFilterKey',
+ {
+ handler: 'Input',
+ helpEntries: 'advancedFilterKey',
+ placeholder: L._('Example: key1,key2,key3'),
+ label: L._('Advanced filter keys'),
+ inheritable: true,
+ },
+ ],
+ [
+ 'options.slugKey',
+ {
+ handler: 'BlurInput',
+ helpEntries: 'slugKey',
+ placeholder: L._('Default: name'),
+ label: L._('Feature identifier key'),
+ },
+ ],
+ ]
+
+ builder = new L.U.FormBuilder(this, optionsFields, {
+ callback({ helper }) {
+ this.initCaptionBar()
+ this.eachDataLayer((datalayer) => {
+ if (helper.field === 'options.sortKey') datalayer.reindex()
+ datalayer.redraw()
+ })
+ },
+ })
+ const defaultProperties = L.DomUtil.createFieldset(
+ container,
+ L._('Default properties')
+ )
+ defaultProperties.appendChild(builder.build())
+ },
+
+ _editInteractionsProperties(container) {
+ const popupFields = [
+ 'options.popupShape',
+ 'options.popupTemplate',
+ 'options.popupContentTemplate',
+ 'options.showLabel',
+ 'options.labelDirection',
+ 'options.labelInteractive',
+ ]
+ builder = new L.U.FormBuilder(this, popupFields, {
+ callback({ helper }) {
+ if (
+ helper.field === 'options.popupTemplate' ||
+ helper.field === 'options.popupContentTemplate' ||
+ helper.field === 'options.popupShape'
+ )
+ return
+ this.eachDataLayer((datalayer) => {
+ datalayer.redraw()
+ })
+ },
+ })
+ const popupFieldset = L.DomUtil.createFieldset(
+ container,
+ L._('Default interaction options')
+ )
+ popupFieldset.appendChild(builder.build())
+ },
+
+ _editTilelayer(container) {
+ if (!L.Util.isObject(this.options.tilelayer)) {
+ this.options.tilelayer = {}
+ }
+ const tilelayerFields = [
+ [
+ 'options.tilelayer.name',
+ { handler: 'BlurInput', placeholder: L._('display name') },
+ ],
+ [
+ 'options.tilelayer.url_template',
+ {
+ handler: 'BlurInput',
+ helpText: `${L._('Supported scheme')}: http://{s}.domain.com/{z}/{x}/{y}.png`,
+ placeholder: 'url',
+ },
+ ],
+ [
+ 'options.tilelayer.maxZoom',
+ { handler: 'BlurIntInput', placeholder: L._('max zoom') },
+ ],
+ [
+ 'options.tilelayer.minZoom',
+ { handler: 'BlurIntInput', placeholder: L._('min zoom') },
+ ],
+ [
+ 'options.tilelayer.attribution',
+ { handler: 'BlurInput', placeholder: L._('attribution') },
+ ],
+ ['options.tilelayer.tms', { handler: 'Switch', label: L._('TMS format') }],
+ ]
+ const customTilelayer = L.DomUtil.createFieldset(
+ container,
+ L._('Custom background')
+ )
+ builder = new L.U.FormBuilder(this, tilelayerFields, {
+ callback: this.initTileLayers,
+ callbackContext: this,
+ })
+ customTilelayer.appendChild(builder.build())
+ },
+
+ _editOverlay(container) {
+ if (!L.Util.isObject(this.options.overlay)) {
+ this.options.overlay = {}
+ }
+ const overlayFields = [
+ [
+ 'options.overlay.url_template',
+ {
+ handler: 'BlurInput',
+ helpText: `${L._('Supported scheme')}: http://{s}.domain.com/{z}/{x}/{y}.png`,
+ placeholder: 'url',
+ helpText: L._('Background overlay url'),
+ },
+ ],
+ [
+ 'options.overlay.maxZoom',
+ { handler: 'BlurIntInput', placeholder: L._('max zoom') },
+ ],
+ [
+ 'options.overlay.minZoom',
+ { handler: 'BlurIntInput', placeholder: L._('min zoom') },
+ ],
+ [
+ 'options.overlay.attribution',
+ { handler: 'BlurInput', placeholder: L._('attribution') },
+ ],
+ [
+ 'options.overlay.opacity',
+ { handler: 'Range', min: 0, max: 1, step: 'any', placeholder: L._('opacity') },
+ ],
+ ['options.overlay.tms', { handler: 'Switch', label: L._('TMS format') }],
+ ]
+ const overlay = L.DomUtil.createFieldset(container, L._('Custom overlay'))
+ builder = new L.U.FormBuilder(this, overlayFields, {
+ callback: this.initTileLayers,
+ callbackContext: this,
+ })
+ overlay.appendChild(builder.build())
+ },
+
+ _editBounds(container) {
+ if (!L.Util.isObject(this.options.limitBounds)) {
+ this.options.limitBounds = {}
+ }
+ const limitBounds = L.DomUtil.createFieldset(container, L._('Limit bounds'))
+ const boundsFields = [
+ [
+ 'options.limitBounds.south',
+ { handler: 'BlurFloatInput', placeholder: L._('max South') },
+ ],
+ [
+ 'options.limitBounds.west',
+ { handler: 'BlurFloatInput', placeholder: L._('max West') },
+ ],
+ [
+ 'options.limitBounds.north',
+ { handler: 'BlurFloatInput', placeholder: L._('max North') },
+ ],
+ [
+ 'options.limitBounds.east',
+ { handler: 'BlurFloatInput', placeholder: L._('max East') },
+ ],
+ ]
+ const boundsBuilder = new L.U.FormBuilder(this, boundsFields, {
+ callback: this.handleLimitBounds,
+ callbackContext: this,
+ })
+ limitBounds.appendChild(boundsBuilder.build())
+ const boundsButtons = L.DomUtil.create('div', 'button-bar half', limitBounds)
+ const setCurrentButton = L.DomUtil.add(
+ 'a',
+ 'button',
+ boundsButtons,
+ L._('Use current bounds')
+ )
+ setCurrentButton.href = '#'
+ L.DomEvent.on(
+ setCurrentButton,
+ 'click',
+ function () {
+ const bounds = this.getBounds()
+ this.options.limitBounds.south = L.Util.formatNum(bounds.getSouth())
+ this.options.limitBounds.west = L.Util.formatNum(bounds.getWest())
+ this.options.limitBounds.north = L.Util.formatNum(bounds.getNorth())
+ this.options.limitBounds.east = L.Util.formatNum(bounds.getEast())
+ boundsBuilder.fetchAll()
+ this.isDirty = true
+ this.handleLimitBounds()
+ },
+ this
+ )
+ const emptyBounds = L.DomUtil.add('a', 'button', boundsButtons, L._('Empty'))
+ emptyBounds.href = '#'
+ L.DomEvent.on(
+ emptyBounds,
+ 'click',
+ function () {
+ this.options.limitBounds.south = null
+ this.options.limitBounds.west = null
+ this.options.limitBounds.north = null
+ this.options.limitBounds.east = null
+ boundsBuilder.fetchAll()
+ this.isDirty = true
+ this.handleLimitBounds()
+ },
+ this
+ )
+ },
+
+ _editSlideshow(container) {
+ const slideshow = L.DomUtil.createFieldset(container, L._('Slideshow'))
+ const slideshowFields = [
+ [
+ 'options.slideshow.active',
+ { handler: 'Switch', label: L._('Activate slideshow mode') },
+ ],
+ [
+ 'options.slideshow.delay',
+ {
+ handler: 'SlideshowDelay',
+ helpText: L._('Delay between two transitions when in play mode'),
+ },
+ ],
+ [
+ 'options.slideshow.easing',
+ { handler: 'Switch', label: L._('Animated transitions'), inheritable: true },
+ ],
+ [
+ 'options.slideshow.autoplay',
+ { handler: 'Switch', label: L._('Autostart when map is loaded') },
+ ],
+ ]
+ const slideshowHandler = function () {
+ this.slideshow.setOptions(this.options.slideshow)
+ this.renderControls()
+ }
+ const slideshowBuilder = new L.U.FormBuilder(this, slideshowFields, {
+ callback: slideshowHandler,
+ callbackContext: this,
+ })
+ slideshow.appendChild(slideshowBuilder.build())
+ },
+
+ _editCredits(container) {
+ const credits = L.DomUtil.createFieldset(container, L._('Credits'))
+ const creditsFields = [
+ ['options.licence', { handler: 'LicenceChooser', label: L._('licence') }],
+ [
+ 'options.shortCredit',
+ {
+ handler: 'Input',
+ label: L._('Short credits'),
+ helpEntries: ['shortCredit', 'textFormatting'],
+ },
+ ],
+ [
+ 'options.longCredit',
+ {
+ handler: 'Textarea',
+ label: L._('Long credits'),
+ helpEntries: ['longCredit', 'textFormatting'],
+ },
+ ],
+ [
+ 'options.permanentCredit',
+ {
+ handler: 'Textarea',
+ label: L._('Permanent credits'),
+ helpEntries: ['permanentCredit', 'textFormatting'],
+ },
+ ],
+ [
+ 'options.permanentCreditBackground',
+ { handler: 'Switch', label: L._('Permanent credits background') },
+ ],
+ ]
+ const creditsBuilder = new L.U.FormBuilder(this, creditsFields, {
+ callback: this.renderControls,
+ callbackContext: this,
+ })
+ credits.appendChild(creditsBuilder.build())
+ },
+
+ _advancedActions(container) {
+ const advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'))
+ const advancedButtons = L.DomUtil.create('div', 'button-bar half', advancedActions)
+ const del = L.DomUtil.create('a', 'button umap-delete', advancedButtons)
+ del.href = '#'
+ del.textContent = L._('Delete')
+ L.DomEvent.on(del, 'click', L.DomEvent.stop).on(del, 'click', this.del, this)
+ const clone = L.DomUtil.create('a', 'button umap-clone', advancedButtons)
+ clone.href = '#'
+ clone.textContent = L._('Clone')
+ clone.title = L._('Clone this map')
+ L.DomEvent.on(clone, 'click', L.DomEvent.stop).on(clone, 'click', this.clone, this)
+ const empty = L.DomUtil.create('a', 'button umap-empty', advancedButtons)
+ empty.href = '#'
+ empty.textContent = L._('Empty')
+ empty.title = L._('Delete all layers')
+ L.DomEvent.on(empty, 'click', L.DomEvent.stop).on(empty, 'click', this.empty, this)
+ const download = L.DomUtil.create('a', 'button umap-download', advancedButtons)
+ download.href = '#'
+ download.textContent = L._('Download')
+ download.title = L._('Open download panel')
+ L.DomEvent.on(download, 'click', L.DomEvent.stop).on(
+ download,
+ 'click',
+ this.renderShareBox,
+ this
+ )
+ },
+
+ edit() {
+ if (!this.editEnabled) return
+ const container = L.DomUtil.create('div', 'umap-edit-container')
+
+ const metadataFields = ['options.name', 'options.description']
+
+ const title = L.DomUtil.create('h4', '', container)
+ title.textContent = L._('Edit map properties')
+ const builder = new L.U.FormBuilder(this, metadataFields)
+ const form = builder.build()
+ container.appendChild(form)
+ this._editControls(container)
+ this._editShapeProperties(container)
+ this._editDefaultProperties(container)
+ this._editInteractionsProperties(container)
+ this._editTilelayer(container)
+ this._editOverlay(container)
+ this._editBounds(container)
+ this._editSlideshow(container)
+ this._editCredits(container)
+ this._advancedActions(container)
+
+ this.ui.openPanel({ data: { html: container }, className: 'dark' })
+ },
+
+ enableEdit() {
+ L.DomUtil.addClass(document.body, 'umap-edit-enabled')
+ this.editEnabled = true
+ this.fire('edit:enabled')
+ },
+
+ disableEdit() {
+ if (this.isDirty) return
+ L.DomUtil.removeClass(document.body, 'umap-edit-enabled')
+ this.editedFeature = null
+ this.editEnabled = false
+ this.fire('edit:disabled')
+ },
+
+ getDisplayName() {
+ return this.options.name || L._('Untitled map')
+ },
+
+ initCaptionBar() {
+ const container = L.DomUtil.create(
+ 'div',
+ 'umap-caption-bar',
+ this._controlContainer
+ )
+ const name = L.DomUtil.create('h3', '', container)
+ L.DomEvent.disableClickPropagation(container)
+ this.permissions.addOwnerLink('span', container)
+ if (this.options.captionMenus) {
+ const about = L.DomUtil.add(
+ 'a',
+ 'umap-about-link',
+ container,
+ ` — ${L._('About')}`
+ )
+ about.href = '#'
+ L.DomEvent.on(about, 'click', this.displayCaption, this)
+ const browser = L.DomUtil.add(
+ 'a',
+ 'umap-open-browser-link',
+ container,
+ ` | ${L._('Browse data')}`
+ )
+ browser.href = '#'
+ L.DomEvent.on(browser, 'click', L.DomEvent.stop).on(
+ browser,
+ 'click',
+ this.openBrowser,
+ this
+ )
+ if (this.options.advancedFilterKey) {
+ const filter = L.DomUtil.add(
+ 'a',
+ 'umap-open-filter-link',
+ container,
+ ` | ${L._('Select data')}`
+ )
+ filter.href = '#'
+ L.DomEvent.on(filter, 'click', L.DomEvent.stop).on(
+ filter,
+ 'click',
+ this.openFilter,
+ this
+ )
+ }
+ }
+ const setName = function () {
+ name.textContent = this.getDisplayName()
+ }
+ L.bind(setName, this)()
+ this.on('postsync', L.bind(setName, this))
+ this.onceDatalayersLoaded(function () {
+ this.slideshow.renderToolbox(container)
+ })
+ },
+
+ initEditBar() {
+ const container = L.DomUtil.create(
+ 'div',
+ 'umap-main-edit-toolbox with-transition dark',
+ this._controlContainer
+ )
+ const title = L.DomUtil.add('h3', '', container, `${L._('Editing')} `)
+ const name = L.DomUtil.create('a', 'umap-click-to-edit', title)
+
+ const setName = function () {
+ name.textContent = this.getDisplayName()
+ }
+
+ L.bind(setName, this)()
+ L.DomEvent.on(name, 'click', this.edit, this)
+ this.on('postsync', L.bind(setName, this))
+ this.help.button(title, 'edit')
+ const save = L.DomUtil.create('a', 'leaflet-control-edit-save button', container)
+ save.href = '#'
+ save.title = `${L._('Save current edits')} (Ctrl+S)`
+ save.textContent = L._('Save')
+ const cancel = L.DomUtil.create(
+ 'a',
+ 'leaflet-control-edit-cancel button',
+ container
+ )
+ cancel.href = '#'
+ cancel.title = L._('Cancel edits')
+ cancel.textContent = L._('Cancel')
+ const disable = L.DomUtil.create('a', 'leaflet-control-edit-disable', container)
+ disable.href = '#'
+ disable.title = disable.textContent = L._('Disable editing')
+
+ L.DomEvent.addListener(disable, 'click', L.DomEvent.stop).addListener(
+ disable,
+ 'click',
+ function (e) {
+ this.disableEdit(e)
+ this.ui.closePanel()
+ },
+ this
+ )
+
+ L.DomEvent.addListener(save, 'click', L.DomEvent.stop).addListener(
+ save,
+ 'click',
+ this.save,
+ this
+ )
+
+ L.DomEvent.addListener(cancel, 'click', L.DomEvent.stop).addListener(
+ cancel,
+ 'click',
+ this.askForReset,
+ this
+ )
+ },
+
+ askForReset(e) {
+ if (!confirm(L._('Are you sure you want to cancel your changes?'))) return
+ this.reset()
+ this.disableEdit(e)
+ this.ui.closePanel()
+ },
+
+ startMarker() {
+ return this.editTools.startMarker()
+ },
+
+ startPolyline() {
+ return this.editTools.startPolyline()
+ },
+
+ startPolygon() {
+ return this.editTools.startPolygon()
+ },
+
+ del() {
+ if (confirm(L._('Are you sure you want to delete this map?'))) {
+ const url = L.Util.template(this.options.urls.map_delete, {
+ map_id: this.options.umap_id,
+ })
+ this.post(url)
+ }
+ },
+
+ clone() {
+ if (
+ confirm(L._('Are you sure you want to clone this map and all its datalayers?'))
+ ) {
+ const url = L.Util.template(this.options.urls.map_clone, {
+ map_id: this.options.umap_id,
+ })
+ this.post(url)
+ }
+ },
+
+ empty() {
+ this.eachDataLayerReverse((datalayer) => {
+ datalayer._delete()
+ })
+ },
+
+ initLoader() {
+ this.loader = new L.Control.Loading()
+ this.loader.onAdd(this)
+ },
+
+ post(url, options = {}) {
+ options.listener = this
+ this.xhr.post(url, options)
+ },
+
+ get(url, options = {}) {
+ options.listener = this
+ this.xhr.get(url, options)
+ },
+
+ ajax(options) {
+ options.listener = this
+ this.xhr._ajax(options)
+ },
+
+ initContextMenu() {
+ this.contextmenu = new L.U.ContextMenu(this)
+ this.contextmenu.enable()
+ },
+
+ setContextMenuItems(e) {
+ let items = []
+ if (this._zoom !== this.getMaxZoom()) {
+ items.push({
+ text: L._('Zoom in'),
+ callback() {
+ this.zoomIn()
+ },
+ })
+ }
+ if (this._zoom !== this.getMinZoom()) {
+ items.push({
+ text: L._('Zoom out'),
+ callback() {
+ this.zoomOut()
+ },
+ })
+ }
+ if (e && e.relatedTarget) {
+ if (e.relatedTarget.getContextMenuItems) {
+ items = items.concat(e.relatedTarget.getContextMenuItems(e))
+ }
+ }
+ if (this.options.allowEdit) {
+ items.push('-')
+ if (this.editEnabled) {
+ if (!this.isDirty) {
+ items.push({
+ text: `${L._('Stop editing')} (Ctrl+E)`,
+ callback: this.disableEdit,
+ })
+ }
+ if (this.options.enableMarkerDraw) {
+ items.push({
+ text: `${L._('Draw a marker')} (Ctrl+M)`,
+ callback: this.startMarker,
+ context: this,
+ })
+ }
+ if (this.options.enablePolylineDraw) {
+ items.push({
+ text: `${L._('Draw a polygon')} (Ctrl+P)`,
+ callback: this.startPolygon,
+ context: this,
+ })
+ }
+ if (this.options.enablePolygonDraw) {
+ items.push({
+ text: `${L._('Draw a line')} (Ctrl+L)`,
+ callback: this.startPolyline,
+ context: this,
+ })
+ }
+ items.push('-')
+ items.push({
+ text: L._('Help'),
+ callback() {
+ this.help.show('edit')
+ },
+ })
+ } else {
+ items.push({
+ text: `${L._('Start editing')} (Ctrl+E)`,
+ callback: this.enableEdit,
+ })
+ }
+ }
+ items.push('-', {
+ text: L._('Browse data'),
+ callback: this.openBrowser,
+ })
+ if (this.options.advancedFilterKey) {
+ items.push({
+ text: L._('Select data'),
+ callback: this.openFilter,
+ })
+ }
+ items.push(
+ {
+ text: L._('About'),
+ callback: this.displayCaption,
+ },
+ {
+ text: L._('Search location'),
+ callback: this.search,
+ }
+ )
+ if (this.options.urls.routing) {
+ items.push('-', {
+ text: L._('Directions from here'),
+ callback: this.openExternalRouting,
+ })
+ }
+ this.options.contextmenuItems = items
+ },
+
+ openExternalRouting({ latlng }) {
+ const url = this.options.urls.routing
+ if (url) {
+ const params = {
+ lat: latlng.lat,
+ lng: latlng.lng,
+ locale: L.locale,
+ zoom: this.getZoom(),
+ }
+ window.open(L.Util.template(url, params))
+ }
+ return
+ },
+
+ getMap() {
+ return this
+ },
+
+ getGeoContext() {
+ const context = {
+ bbox: this.getBounds().toBBoxString(),
+ north: this.getBounds().getNorthEast().lat,
+ east: this.getBounds().getNorthEast().lng,
+ south: this.getBounds().getSouthWest().lat,
+ west: this.getBounds().getSouthWest().lng,
+ lat: this.getCenter().lat,
+ lng: this.getCenter().lng,
+ zoom: this.getZoom(),
+ }
+ context.left = context.west
+ context.bottom = context.south
+ context.right = context.east
+ context.top = context.north
+ return context
+ },
+
+ localizeUrl(url) {
+ return L.Util.greedyTemplate(url, this.getGeoContext(), true)
+ },
+
+ proxyUrl(url, ttl) {
+ if (this.options.urls.ajax_proxy) {
+ url = L.Util.greedyTemplate(this.options.urls.ajax_proxy, {
+ url: encodeURIComponent(url),
+ ttl,
+ })
+ }
+ return url
+ },
+
+ closeInplaceToolbar() {
+ const toolbar = this._toolbars[L.Toolbar.Popup._toolbar_class_id]
+ if (toolbar) toolbar.remove()
+ },
+
+ search() {
+ if (this._controls.search) this._controls.search.openPanel(this)
+ },
+
+ getFilterKeys() {
+ return (this.options.filterKey || this.options.sortKey || 'name').split(',')
+ },
+
+ getAdvancedFilterKeys() {
+ return (this.options.advancedFilterKey || '').split(',')
+ },
+})
diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js
index 208ff571..d3a3b1b2 100644
--- a/umap/static/umap/js/umap.layer.js
+++ b/umap/static/umap/js/umap.layer.js
@@ -1,1080 +1,1224 @@
L.U.Layer = {
- canBrowse: true,
+ canBrowse: true,
- getFeatures: function () {
- return this._layers;
- },
+ getFeatures() {
+ return this._layers
+ },
- getEditableOptions: function () {return [];},
+ getEditableOptions() {
+ return []
+ },
- postUpdate: function () {}
-
-};
+ postUpdate() {},
+}
L.U.Layer.Default = L.FeatureGroup.extend({
- _type: 'Default',
- includes: [L.U.Layer],
-
- initialize: function (datalayer) {
- this.datalayer = datalayer;
- L.FeatureGroup.prototype.initialize.call(this);
- }
-
-});
+ _type: 'Default',
+ includes: [L.U.Layer],
+ initialize(datalayer) {
+ this.datalayer = datalayer
+ L.FeatureGroup.prototype.initialize.call(this)
+ },
+})
L.U.MarkerCluster = L.MarkerCluster.extend({
-// Custom class so we can call computeTextColor
-// when element is already on the DOM.
+ // Custom class so we can call computeTextColor
+ // when element is already on the DOM.
- _initIcon: function () {
- L.MarkerCluster.prototype._initIcon.call(this);
- var div = this._icon.querySelector('div');
- // Compute text color only when icon is added to the DOM.
- div.style.color = this._iconObj.computeTextColor(div);
- }
-
-});
+ _initIcon() {
+ L.MarkerCluster.prototype._initIcon.call(this)
+ const div = this._icon.querySelector('div')
+ // Compute text color only when icon is added to the DOM.
+ div.style.color = this._iconObj.computeTextColor(div)
+ },
+})
L.U.Layer.Cluster = L.MarkerClusterGroup.extend({
- _type: 'Cluster',
- includes: [L.U.Layer],
+ _type: 'Cluster',
+ includes: [L.U.Layer],
- initialize: function (datalayer) {
- this.datalayer = datalayer;
- var options = {
- polygonOptions: {
- color: this.datalayer.getColor()
- },
- iconCreateFunction: function (cluster) {
- return new L.U.Icon.Cluster(datalayer, cluster);
- }
- };
- if (this.datalayer.options.cluster && this.datalayer.options.cluster.radius) {
- options.maxClusterRadius = this.datalayer.options.cluster.radius;
- }
- L.MarkerClusterGroup.prototype.initialize.call(this, options);
- this._markerCluster = L.U.MarkerCluster;
- },
-
- getEditableOptions: function () {
- if (!L.Util.isObject(this.datalayer.options.cluster)) {
- this.datalayer.options.cluster = {};
- }
- return [
- ['options.cluster.radius', {handler: 'BlurIntInput', placeholder: L._('Clustering radius'), helpText: L._('Override clustering radius (default 80)')}],
- ['options.cluster.textColor', {handler: 'TextColorPicker', placeholder: L._('Auto'), helpText: L._('Text color for the cluster label')}],
- ];
-
- },
-
- postUpdate: function (e) {
- if (e.helper.field === 'options.cluster.radius') {
- // No way to reset radius of an already instanciated MarkerClusterGroup...
- this.datalayer.resetLayer(true);
- return;
- }
- if (e.helper.field === 'options.color') {
- this.options.polygonOptions.color = this.datalayer.getColor();
- }
+ initialize(datalayer) {
+ this.datalayer = datalayer
+ const options = {
+ polygonOptions: {
+ color: this.datalayer.getColor(),
+ },
+ iconCreateFunction(cluster) {
+ return new L.U.Icon.Cluster(datalayer, cluster)
+ },
}
+ if (this.datalayer.options.cluster && this.datalayer.options.cluster.radius) {
+ options.maxClusterRadius = this.datalayer.options.cluster.radius
+ }
+ L.MarkerClusterGroup.prototype.initialize.call(this, options)
+ this._markerCluster = L.U.MarkerCluster
+ },
-});
+ getEditableOptions() {
+ if (!L.Util.isObject(this.datalayer.options.cluster)) {
+ this.datalayer.options.cluster = {}
+ }
+ return [
+ [
+ 'options.cluster.radius',
+ {
+ handler: 'BlurIntInput',
+ placeholder: L._('Clustering radius'),
+ helpText: L._('Override clustering radius (default 80)'),
+ },
+ ],
+ [
+ 'options.cluster.textColor',
+ {
+ handler: 'TextColorPicker',
+ placeholder: L._('Auto'),
+ helpText: L._('Text color for the cluster label'),
+ },
+ ],
+ ]
+ },
+
+ postUpdate({ helper }) {
+ if (helper.field === 'options.cluster.radius') {
+ // No way to reset radius of an already instanciated MarkerClusterGroup...
+ this.datalayer.resetLayer(true)
+ return
+ }
+ if (helper.field === 'options.color') {
+ this.options.polygonOptions.color = this.datalayer.getColor()
+ }
+ },
+})
L.U.Layer.Heat = L.HeatLayer.extend({
- _type: 'Heat',
- includes: [L.U.Layer],
- canBrowse: false,
+ _type: 'Heat',
+ includes: [L.U.Layer],
+ canBrowse: false,
- initialize: function (datalayer) {
- this.datalayer = datalayer;
- L.HeatLayer.prototype.initialize.call(this, [], this.datalayer.options.heat);
- },
+ initialize(datalayer) {
+ this.datalayer = datalayer
+ L.HeatLayer.prototype.initialize.call(this, [], this.datalayer.options.heat)
+ },
- addLayer: function (layer) {
- if (layer instanceof L.Marker) {
- var latlng = layer.getLatLng(), alt;
- if (this.datalayer.options.heat && this.datalayer.options.heat.intensityProperty) {
- alt = parseFloat(layer.properties[this.datalayer.options.heat.intensityProperty || 0]);
- latlng = new L.LatLng(latlng.lat, latlng.lng, alt);
- }
- this.addLatLng(latlng);
- }
- },
-
- clearLayers: function () {
- this.setLatLngs([]);
- },
-
- redraw: function () {
- // setlalngs call _redraw through setAnimFrame, thus async, so this
- // can ends with race condition if we remove the layer very faslty after.
- // Remove me when https://github.com/Leaflet/Leaflet.heat/pull/53 is released.
- if (!this._map) return;
- L.HeatLayer.prototype.redraw.call(this);
- },
-
- getFeatures: function () {
- return {};
- },
-
- getBounds: function () {
- return L.latLngBounds(this._latlngs);
- },
-
- getEditableOptions: function () {
- if (!L.Util.isObject(this.datalayer.options.heat)) {
- this.datalayer.options.heat = {};
- }
- return [
- ['options.heat.radius', {handler: 'BlurIntInput', placeholder: L._('Heatmap radius'), helpText: L._('Override heatmap radius (default 25)')}],
- ['options.heat.intensityProperty', {handler: 'BlurInput', placeholder: L._('Heatmap intensity property'), helpText: L._('Optional intensity property for heatmap')}]
- ];
-
- },
-
- postUpdate: function (e) {
- if (e.helper.field === 'options.heat.intensityProperty') {
- this.datalayer.resetLayer(true); // We need to repopulate the latlngs
- return;
- }
- if (e.helper.field === 'options.heat.radius') {
- this.options.radius = this.datalayer.options.heat.radius;
- }
- this._updateOptions();
+ addLayer(layer) {
+ if (layer instanceof L.Marker) {
+ let latlng = layer.getLatLng()
+ let alt
+ if (
+ this.datalayer.options.heat &&
+ this.datalayer.options.heat.intensityProperty
+ ) {
+ alt = parseFloat(
+ layer.properties[this.datalayer.options.heat.intensityProperty || 0]
+ )
+ latlng = new L.LatLng(latlng.lat, latlng.lng, alt)
+ }
+ this.addLatLng(latlng)
}
+ },
-});
+ clearLayers() {
+ this.setLatLngs([])
+ },
+
+ redraw() {
+ // setlalngs call _redraw through setAnimFrame, thus async, so this
+ // can ends with race condition if we remove the layer very faslty after.
+ // Remove me when https://github.com/Leaflet/Leaflet.heat/pull/53 is released.
+ if (!this._map) return
+ L.HeatLayer.prototype.redraw.call(this)
+ },
+
+ getFeatures() {
+ return {}
+ },
+
+ getBounds() {
+ return L.latLngBounds(this._latlngs)
+ },
+
+ getEditableOptions() {
+ if (!L.Util.isObject(this.datalayer.options.heat)) {
+ this.datalayer.options.heat = {}
+ }
+ return [
+ [
+ 'options.heat.radius',
+ {
+ handler: 'BlurIntInput',
+ placeholder: L._('Heatmap radius'),
+ helpText: L._('Override heatmap radius (default 25)'),
+ },
+ ],
+ [
+ 'options.heat.intensityProperty',
+ {
+ handler: 'BlurInput',
+ placeholder: L._('Heatmap intensity property'),
+ helpText: L._('Optional intensity property for heatmap'),
+ },
+ ],
+ ]
+ },
+
+ postUpdate({ helper }) {
+ if (helper.field === 'options.heat.intensityProperty') {
+ this.datalayer.resetLayer(true) // We need to repopulate the latlngs
+ return
+ }
+ if (helper.field === 'options.heat.radius') {
+ this.options.radius = this.datalayer.options.heat.radius
+ }
+ this._updateOptions()
+ },
+})
L.U.DataLayer = L.Evented.extend({
-
- options: {
- displayOnLoad: true,
- browsable: true
- },
-
- initialize: function (map, data) {
- this.map = map;
- this._index = Array();
- this._layers = {};
- this._geojson = null;
- this._propertiesIndex = [];
-
- this.parentPane = this.map.getPane('overlayPane');
- this.pane = this.map.createPane('datalayer' + L.stamp(this), this.parentPane);
- this.pane.dataset.id = L.stamp(this);
- this.renderer = L.svg({pane: this.pane});
-
- var isDirty = false,
- isDeleted = false,
- self = this;
- try {
- Object.defineProperty(this, 'isDirty', {
- get: function () {
- return isDirty;
- },
- set: function (status) {
- if (!isDirty && status) self.fire('dirty');
- isDirty = status;
- if (status) {
- self.map.addDirtyDatalayer(self);
- // A layer can be made dirty by indirect action (like dragging layers)
- // we need to have it loaded before saving it.
- if (!self.isLoaded()) self.fetchData();
- } else {
- self.map.removeDirtyDatalayer(self);
- self.isDeleted = false;
- }
- }
- });
- }
- catch (e) {
- // Certainly IE8, which has a limited version of defineProperty
- }
- try {
- Object.defineProperty(this, 'isDeleted', {
- get: function () {
- return isDeleted;
- },
- set: function (status) {
- if (!isDeleted && status) self.fire('deleted');
- isDeleted = status;
- if (status) self.isDirty = status;
- }
- });
- }
- catch (e) {
- // Certainly IE8, which has a limited version of defineProperty
- }
- this.setUmapId(data.id);
- this.setOptions(data);
- this.backupOptions();
- this.connectToMap();
- if (this.displayedOnLoad()) this.show();
- if (!this.umap_id) this.isDirty = true;
- this.onceLoaded(function () {
- this.map.on('moveend', this.fetchRemoteData, this);
- });
- },
-
- displayedOnLoad: function () {
- return ((this.map.datalayersOnLoad && this.umap_id && this.map.datalayersOnLoad.indexOf(this.umap_id.toString()) !== -1) ||
- (!this.map.datalayersOnLoad && this.options.displayOnLoad));
- },
-
- insertBefore: function (other) {
- if (!other) return;
- this.parentPane.insertBefore(this.pane, other.pane);
- },
-
- insertAfter: function (other) {
- if (!other) return;
- this.parentPane.insertBefore(this.pane, other.pane.nextSibling);
- },
-
- bringToTop: function () {
- this.parentPane.appendChild(this.pane);
- },
-
- resetLayer: function (force) {
- if (this.layer && this.options.type === this.layer._type && !force) return;
- var visible = this.isVisible();
- if (this.layer) this.layer.clearLayers();
- // delete this.layer?
- if (visible) this.map.removeLayer(this.layer);
- var Class = L.U.Layer[this.options.type] || L.U.Layer.Default;
- this.layer = new Class(this);
- var filterKeys = this.map.getFilterKeys(),
- filter = this.map.options.filter;
- this.eachLayer(function (layer) {
- if (filter && !layer.matchFilter(filter, filterKeys)) return;
- this.layer.addLayer(layer);
- });
- if (visible) this.map.addLayer(this.layer);
- this.propagateRemote();
- },
-
- eachLayer: function (method, context) {
- for (var i in this._layers) {
- method.call(context || this, this._layers[i]);
- }
- return this;
- },
-
- eachFeature: function (method, context) {
- if (this.layer && this.layer.canBrowse) {
- for (var i = 0; i < this._index.length; i++) {
- method.call(context || this, this._layers[this._index[i]]);
- }
- }
- return this;
- },
-
- fetchData: function () {
- if (!this.umap_id) return;
- this.map.get(this._dataUrl(), {
- callback: function (geojson, response) {
- this._last_modified = response.getResponseHeader('Last-Modified');
- this.fromUmapGeoJSON(geojson);
- this.backupOptions();
- this.fire('loaded');
- },
- context: this
- });
- },
-
- fromGeoJSON: function (geojson) {
- this.addData(geojson);
- this._geojson = geojson;
- this.fire('dataloaded');
- this.fire('datachanged');
- },
-
- fromUmapGeoJSON: function (geojson) {
- if (geojson._storage) geojson._umap_options = geojson._storage; // Retrocompat
- if (geojson._umap_options) this.setOptions(geojson._umap_options);
- if (this.isRemoteLayer()) this.fetchRemoteData();
- else this.fromGeoJSON(geojson);
- this._loaded = true;
- },
-
- clear: function () {
- this.layer.clearLayers();
- this._layers = {};
- this._index = Array();
- if (this._geojson) {
- this.backupData();
- this._geojson = null;
- }
- },
-
- backupData: function () {
- this._geojson_bk = L.Util.CopyJSON(this._geojson);
- },
-
- reindex: function () {
- var features = [];
- this.eachFeature(function (feature) {
- features.push(feature);
- });
- L.Util.sortFeatures(features, this.map.getOption('sortKey'));
- this._index = [];
- for (var i = 0; i < features.length; i++) {
- this._index.push(L.Util.stamp(features[i]));
- }
- },
-
- fetchRemoteData: function () {
- if (!this.isRemoteLayer()) return;
- var from = parseInt(this.options.remoteData.from, 10),
- to = parseInt(this.options.remoteData.to, 10);
- if ((!isNaN(from) && this.map.getZoom() < from) ||
- (!isNaN(to) && this.map.getZoom() > to) ) {
- this.clear();
- return;
- }
- if (!this.options.remoteData.dynamic && this.hasDataLoaded()) return;
- if (!this.isVisible()) return;
- var self = this,
- url = this.map.localizeUrl(this.options.remoteData.url);
- if (this.options.remoteData.proxy) url = this.map.proxyUrl(url, this.options.remoteData.ttl);
- this.map.ajax({
- uri: url,
- verb: 'GET',
- callback: function (raw) {
- self.clear();
- self.rawToGeoJSON(raw, self.options.remoteData.format, function (geojson) {self.fromGeoJSON(geojson);});
- }
- });
- },
-
- onceLoaded: function (callback, context) {
- if (this.isLoaded()) callback.call(context || this, this);
- else this.once('loaded', callback, context);
- return this;
- },
-
- onceDataLoaded: function (callback, context) {
- if (this.hasDataLoaded()) callback.call(context || this, this);
- else this.once('dataloaded', callback, context);
- return this;
- },
-
- isLoaded: function () {
- return !this.umap_id || this._loaded;
- },
-
- hasDataLoaded: function () {
- return this._geojson !== null;
- },
-
- setUmapId: function (id) {
- // Datalayer is null when listening creation form
- if (!this.umap_id && id) this.umap_id = id;
- },
-
- backupOptions: function () {
- this._backupOptions = L.Util.CopyJSON(this.options);
- },
-
- resetOptions: function () {
- this.options = L.Util.CopyJSON(this._backupOptions);
- },
-
- setOptions: function (options) {
- this.options = L.Util.CopyJSON(L.U.DataLayer.prototype.options); // Start from fresh.
- this.updateOptions(options);
- },
-
- updateOptions: function (options) {
- L.Util.setOptions(this, options);
- this.resetLayer();
- },
-
- connectToMap: function () {
- var id = L.stamp(this);
- if (!this.map.datalayers[id]) {
- this.map.datalayers[id] = this;
- if (L.Util.indexOf(this.map.datalayers_index, this) === -1) this.map.datalayers_index.push(this);
- }
- this.map.updateDatalayersControl();
- },
-
- _dataUrl: function() {
- var template = this.map.options.urls.datalayer_view,
- url = L.Util.template(template, {'pk': this.umap_id, 'map_id': this.map.options.umap_id});
- // No browser cache for owners/editors.
- if (this.map.options.allowEdit) url = url + '?' + Date.now();
- return url;
- },
-
- isRemoteLayer: function () {
- return !!(this.options.remoteData && this.options.remoteData.url && this.options.remoteData.format);
- },
-
- isClustered: function () {
- return this.options.type === 'Cluster';
- },
-
- addLayer: function (feature) {
- var id = L.stamp(feature);
- feature.connectToDataLayer(this);
- this._index.push(id);
- this._layers[id] = feature;
- this.layer.addLayer(feature);
- this.indexProperties(feature);
- if (this.hasDataLoaded()) this.fire('datachanged');
- this.map.features_index[feature.getSlug()] = feature;
- },
-
- removeLayer: function (feature) {
- var id = L.stamp(feature);
- feature.disconnectFromDataLayer(this);
- this._index.splice(this._index.indexOf(id), 1);
- delete this._layers[id];
- this.layer.removeLayer(feature);
- delete this.map.features_index[feature.getSlug()];
- if (this.hasDataLoaded()) this.fire('datachanged');
- },
-
- indexProperties: function (feature) {
- for (var i in feature.properties) if (typeof feature.properties[i] !== 'object') this.indexProperty(i);
- },
-
- indexProperty: function (name) {
- if (!name) return;
- if (name.indexOf('_') === 0) return;
- if (L.Util.indexOf(this._propertiesIndex, name) !== -1) return;
- this._propertiesIndex.push(name);
- },
-
- deindexProperty: function (name) {
- var idx = this._propertiesIndex.indexOf(name);
- if (idx !== -1) this._propertiesIndex.splice(idx, 1);
- },
-
- addData: function (geojson) {
- try {
- // Do not fail if remote data is somehow invalid,
- // otherwise the layer becomes uneditable.
- this.geojsonToFeatures(geojson);
- } catch (err) {
- console.log("Error with DataLayer", this.umap_id);
- console.error(err);
- }
- },
-
- addRawData: function (c, type) {
- var self = this;
- this.rawToGeoJSON(c, type, function (geojson) {
- self.addData(geojson);
- });
- },
-
- rawToGeoJSON: function (c, type, callback) {
- var self = this;
- var toDom = function (x) {
- return (new DOMParser()).parseFromString(x, 'text/xml');
- };
-
- // TODO add a duck typing guessType
- if (type === 'csv') {
- csv2geojson.csv2geojson(c, {
- delimiter: 'auto',
- includeLatLon: false
- }, function(err, result) {
- if (err) {
- var message;
- if (err.type === 'Error') {
- message = err.message;
- } else {
- message = L._('{count} errors during import: {message}', {count: err.length, message: err[0].message});
- }
- self.map.ui.alert({content: message, level: 'error', duration: 10000});
- console.log(err);
- }
- if (result && result.features.length) {
- callback(result);
- }
- });
- } else if (type === 'gpx') {
- callback(toGeoJSON.gpx(toDom(c)));
- } else if (type === 'georss') {
- callback(GeoRSSToGeoJSON(toDom(c)));
- } else if (type === 'kml') {
- callback(toGeoJSON.kml(toDom(c)));
- } else if (type === 'osm') {
- var d;
- try {
- d = JSON.parse(c);
- } catch (e) {
- d = toDom(c);
- }
- callback(osmtogeojson(d, {flatProperties: true}));
- } else if (type === 'geojson') {
- try {
- var gj = JSON.parse(c);
- callback(gj);
- } catch(err) {
- self.map.ui.alert({content: 'Invalid JSON file: ' + err});
- return;
- }
- }
- },
-
- geojsonToFeatures: function (geojson) {
- if (!geojson) return;
- var features = geojson instanceof Array ? geojson : geojson.features,
- i, len, latlng, latlngs;
-
- if (features) {
- L.Util.sortFeatures(features, this.map.getOption('sortKey'));
- for (i = 0, len = features.length; i < len; i++) {
- this.geojsonToFeatures(features[i]);
- }
- return this;
- }
-
- var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson;
- if (!geometry) return; // null geometry is valid geojson.
- var coords = geometry.coordinates,
- layer, tmp;
-
- switch (geometry.type) {
- case 'Point':
- try {
- latlng = L.GeoJSON.coordsToLatLng(coords);
- } catch (e) {
- console.error('Invalid latlng object from', coords);
- break;
- }
- layer = this._pointToLayer(geojson, latlng);
- break;
-
- case 'MultiLineString':
- case 'LineString':
- latlngs = L.GeoJSON.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1);
- if (!latlngs.length) break;
- layer = this._lineToLayer(geojson, latlngs);
- break;
-
- case 'MultiPolygon':
- case 'Polygon':
- latlngs = L.GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2);
- layer = this._polygonToLayer(geojson, latlngs);
- break;
- case 'GeometryCollection':
- return this.geojsonToFeatures(geometry.geometries);
-
- default:
- this.map.ui.alert({content: L._('Skipping unknown geometry.type: {type}', {type: geometry.type || 'undefined'}), level: 'error'});
- }
- if (layer) {
- this.addLayer(layer);
- return layer;
- }
- },
-
- _pointToLayer: function(geojson, latlng) {
- return new L.U.Marker(
- this.map,
- latlng,
- {'geojson': geojson, 'datalayer': this}
- );
- },
-
- _lineToLayer: function(geojson, latlngs) {
- return new L.U.Polyline(
- this.map,
- latlngs,
- {'geojson': geojson, 'datalayer': this, color: null}
- );
- },
-
- _polygonToLayer: function(geojson, latlngs) {
- // Ensure no empty hole
- // for (var i = latlngs.length - 1; i > 0; i--) {
- // if (!latlngs.slice()[i].length) latlngs.splice(i, 1);
- // }
- return new L.U.Polygon(
- this.map,
- latlngs,
- {'geojson': geojson, 'datalayer': this}
- );
- },
-
- importRaw: function (raw, type) {
- this.addRawData(raw, type);
- this.isDirty = true;
- this.zoomTo();
- },
-
- importFromFiles: function (files, type) {
- for (var i = 0, f; f = files[i]; i++) {
- this.importFromFile(f, type);
- }
- },
-
- importFromFile: function (f, type) {
- var reader = new FileReader(),
- self = this;
- type = type || L.Util.detectFileType(f);
- reader.readAsText(f);
- reader.onload = function (e) {
- self.importRaw(e.target.result, type);
- };
- },
-
- importFromUrl: function (url, type) {
- url = this.map.localizeUrl(url);
- var self = this;
- this.map.xhr._ajax({verb: 'GET', uri: url, callback: function (data) {
- self.importRaw(data, type);
- }});
- },
-
- getEditUrl: function() {
- return L.Util.template(this.map.options.urls.datalayer_update, {'map_id': this.map.options.umap_id, 'pk': this.umap_id});
- },
-
- getCreateUrl: function() {
- return L.Util.template(this.map.options.urls.datalayer_create, {'map_id': this.map.options.umap_id});
- },
-
- getSaveUrl: function () {
- return (this.umap_id && this.getEditUrl()) || this.getCreateUrl();
- },
-
- getColor: function () {
- return this.options.color || this.map.getOption('color');
- },
-
- getDeleteUrl: function () {
- return L.Util.template(this.map.options.urls.datalayer_delete, {'pk': this.umap_id, 'map_id': this.map.options.umap_id});
-
- },
-
- getVersionsUrl: function () {
- return L.Util.template(this.map.options.urls.datalayer_versions, {'pk': this.umap_id, 'map_id': this.map.options.umap_id});
- },
-
- getVersionUrl: function (name) {
- return L.Util.template(this.map.options.urls.datalayer_version, {'pk': this.umap_id, 'map_id': this.map.options.umap_id, name: name});
- },
-
- _delete: function () {
- this.isDeleted = true;
- this.erase();
- },
-
- empty: function () {
- if (this.isRemoteLayer()) return;
- this.clear();
- this.isDirty = true;
- },
-
- clone: function () {
- var options = L.Util.CopyJSON(this.options);
- options.name = L._('Clone of {name}', {name: this.options.name});
- delete options.id;
- var geojson = L.Util.CopyJSON(this._geojson),
- datalayer = this.map.createDataLayer(options);
- datalayer.fromGeoJSON(geojson);
- return datalayer;
- },
-
- erase: function () {
- this.hide();
- delete this.map.datalayers[L.stamp(this)];
- this.map.datalayers_index.splice(this.getRank(), 1);
- this.parentPane.removeChild(this.pane);
- this.map.updateDatalayersControl();
- this.fire('erase');
- this._leaflet_events_bk = this._leaflet_events;
- this.off();
- this.clear();
- delete this._loaded;
- },
-
- reset: function () {
- if (!this.umap_id) this.erase();
-
- this.resetOptions();
- this.parentPane.appendChild(this.pane);
- if (this._leaflet_events_bk && !this._leaflet_events) {
- this._leaflet_events = this._leaflet_events_bk;
- }
- this.clear();
- this.hide();
- if (this.isRemoteLayer()) this.fetchRemoteData();
- else if (this._geojson_bk) this.fromGeoJSON(this._geojson_bk);
- this._loaded = true;
- this.show();
- this.isDirty = false;
- },
-
- redraw: function () {
- this.hide();
- this.show();
- },
-
- edit: function () {
- if(!this.map.editEnabled || !this.isLoaded()) {return;}
- var container = L.DomUtil.create('div', 'umap-layer-properties-container'),
- metadataFields = [
- 'options.name',
- 'options.description',
- ['options.type', {handler: 'LayerTypeChooser', label: L._('Type of layer')}],
- ['options.displayOnLoad', {label: L._('Display on load'), handler: 'Switch'}],
- ['options.browsable', {label: L._('Data is browsable'), handler: 'Switch', helpEntries: 'browsable'}]
- ];
- var title = L.DomUtil.add('h3', '', container, L._('Layer properties'));
- var builder = new L.U.FormBuilder(this, metadataFields, {
- callback: function (e) {
- this.map.updateDatalayersControl();
- if (e.helper.field === 'options.type') {
- this.resetLayer();
- this.edit();
- }
- }
- });
- container.appendChild(builder.build());
-
- var shapeOptions = [
- 'options.color',
- 'options.iconClass',
- 'options.iconUrl',
- 'options.opacity',
- 'options.stroke',
- 'options.weight',
- 'options.fill',
- 'options.fillColor',
- 'options.fillOpacity',
- ];
-
- shapeOptions = shapeOptions.concat(this.layer.getEditableOptions());
-
- var redrawCallback = function (field) {
- this.hide();
- this.layer.postUpdate(field);
- this.show();
- };
-
- builder = new L.U.FormBuilder(this, shapeOptions, {
- id: 'datalayer-advanced-properties',
- callback: redrawCallback
- });
- var shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties'));
- shapeProperties.appendChild(builder.build());
-
- var optionsFields = [
- 'options.smoothFactor',
- 'options.dashArray',
- 'options.zoomTo',
- 'options.labelKey'
- ];
-
- optionsFields = optionsFields.concat(this.layer.getEditableOptions());
-
- builder = new L.U.FormBuilder(this, optionsFields, {
- id: 'datalayer-advanced-properties',
- callback: redrawCallback
- });
- var advancedProperties = L.DomUtil.createFieldset(container, L._('Advanced properties'));
- advancedProperties.appendChild(builder.build());
-
- var popupFields = [
- 'options.popupShape',
- 'options.popupTemplate',
- 'options.popupContentTemplate',
- 'options.showLabel',
- 'options.labelDirection',
- 'options.labelInteractive',
- ];
- builder = new L.U.FormBuilder(this, popupFields, {callback: redrawCallback});
- var popupFieldset = L.DomUtil.createFieldset(container, L._('Interaction options'));
- popupFieldset.appendChild(builder.build());
-
- if (!L.Util.isObject(this.options.remoteData)) {
- this.options.remoteData = {};
- }
- var remoteDataFields = [
- ['options.remoteData.url', {handler: 'Url', label: L._('Url'), helpEntries: 'formatURL'}],
- ['options.remoteData.format', {handler: 'DataFormat', label: L._('Format')}],
- ['options.remoteData.from', {label: L._('From zoom'), helpText: L._('Optional.')}],
- ['options.remoteData.to', {label: L._('To zoom'), helpText: L._('Optional.')}],
- ['options.remoteData.dynamic', {handler: 'Switch', label: L._('Dynamic'), helpEntries: 'dynamicRemoteData'}],
- ['options.remoteData.licence', {label: L._('Licence'), helpText: L._('Please be sure the licence is compliant with your use.')}]
- ];
- if (this.map.options.urls.ajax_proxy) {
- remoteDataFields.push(['options.remoteData.proxy', {handler: 'Switch', label: L._('Proxy request'), helpEntries: 'proxyRemoteData'}]);
- remoteDataFields.push(['options.remoteData.ttl', {handler: 'ProxyTTLSelect', label: L._('Cache proxied request')}]);
- }
-
- var remoteDataContainer = L.DomUtil.createFieldset(container, L._('Remote data'));
- builder = new L.U.FormBuilder(this, remoteDataFields);
- remoteDataContainer.appendChild(builder.build());
-
- if (this.map.options.urls.datalayer_versions) this.buildVersionsFieldset(container);
-
- var advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'));
- var advancedButtons = L.DomUtil.create('div', 'button-bar half', advancedActions);
- var deleteLink = L.DomUtil.create('a', 'button delete_datalayer_button umap-delete', advancedButtons);
- deleteLink.textContent = L._('Delete');
- deleteLink.href = '#';
- L.DomEvent.on(deleteLink, 'click', L.DomEvent.stop)
- .on(deleteLink, 'click', function () {
- this._delete();
- this.map.ui.closePanel();
- }, this);
- if (!this.isRemoteLayer()) {
- var emptyLink = L.DomUtil.create('a', 'button umap-empty', advancedButtons);
- emptyLink.textContent = L._('Empty');
- emptyLink.href = '#';
- L.DomEvent.on(emptyLink, 'click', L.DomEvent.stop)
- .on(emptyLink, 'click', this.empty, this);
- }
- var cloneLink = L.DomUtil.create('a', 'button umap-clone', advancedButtons);
- cloneLink.textContent = L._('Clone');
- cloneLink.href = '#';
- L.DomEvent.on(cloneLink, 'click', L.DomEvent.stop)
- .on(cloneLink, 'click', function () {
- var datalayer = this.clone();
- datalayer.edit();
- }, this);
- if (this.umap_id) {
- var download = L.DomUtil.create('a', 'button umap-download', advancedButtons);
- download.textContent = L._('Download');
- download.href = this._dataUrl();
- download.target = '_blank';
- }
- this.map.ui.openPanel({data: {html: container}, className: 'dark'});
-
- },
-
- getOption: function (option) {
- if (L.Util.usableOption(this.options, option)) return this.options[option];
- else return this.map.getOption(option);
- },
-
- buildVersionsFieldset: function (container) {
-
- var appendVersion = function (data) {
- var date = new Date(parseInt(data.at, 10));
- var content = date.toLocaleDateString(L.locale) + ' (' + parseInt(data.size) / 1000 + 'Kb)';
- var el = L.DomUtil.create('div', 'umap-datalayer-version', versionsContainer);
- var a = L.DomUtil.create('a', '', el);
- L.DomUtil.add('span', '', el, content);
- a.href = '#';
- a.title = L._('Restore this version');
- L.DomEvent.on(a, 'click', L.DomEvent.stop)
- .on(a, 'click', function () {
- this.restore(data.name);
- }, this);
- };
-
- var versionsContainer = L.DomUtil.createFieldset(container, L._('Versions'), {callback: function () {
- this.map.xhr.get(this.getVersionsUrl(), {
- callback: function (data) {
- for (var i = 0; i < data.versions.length; i++) {
- appendVersion.call(this, data.versions[i]);
- };
- },
- context: this
- });
- }, context: this});
- },
-
- restore: function (version) {
- if (!this.map.editEnabled) return;
- if (!confirm(L._('Are you sure you want to restore this version?'))) return;
- this.map.xhr.get(this.getVersionUrl(version), {
- callback: function (geojson) {
- if (geojson._storage) geojson._umap_options = geojson._storage; // Retrocompat.
- if (geojson._umap_options) this.setOptions(geojson._umap_options);
- this.empty();
- if (this.isRemoteLayer()) this.fetchRemoteData();
- else this.addData(geojson);
- this.isDirty = true;
- },
- context: this
+ options: {
+ displayOnLoad: true,
+ browsable: true,
+ },
+
+ initialize(map, data) {
+ this.map = map
+ this._index = Array()
+ this._layers = {}
+ this._geojson = null
+ this._propertiesIndex = []
+
+ this.parentPane = this.map.getPane('overlayPane')
+ this.pane = this.map.createPane(`datalayer${L.stamp(this)}`, this.parentPane)
+ this.pane.dataset.id = L.stamp(this)
+ this.renderer = L.svg({ pane: this.pane })
+
+ let isDirty = false
+ let isDeleted = false
+ const self = this
+ try {
+ Object.defineProperty(this, 'isDirty', {
+ get() {
+ return isDirty
+ },
+ set(status) {
+ if (!isDirty && status) self.fire('dirty')
+ isDirty = status
+ if (status) {
+ self.map.addDirtyDatalayer(self)
+ // A layer can be made dirty by indirect action (like dragging layers)
+ // we need to have it loaded before saving it.
+ if (!self.isLoaded()) self.fetchData()
+ } else {
+ self.map.removeDirtyDatalayer(self)
+ self.isDeleted = false
+ }
+ },
+ })
+ } catch (e) {
+ // Certainly IE8, which has a limited version of defineProperty
+ }
+ try {
+ Object.defineProperty(this, 'isDeleted', {
+ get() {
+ return isDeleted
+ },
+ set(status) {
+ if (!isDeleted && status) self.fire('deleted')
+ isDeleted = status
+ if (status) self.isDirty = status
+ },
+ })
+ } catch (e) {
+ // Certainly IE8, which has a limited version of defineProperty
+ }
+ this.setUmapId(data.id)
+ this.setOptions(data)
+ this.backupOptions()
+ this.connectToMap()
+ if (this.displayedOnLoad()) this.show()
+ if (!this.umap_id) this.isDirty = true
+ this.onceLoaded(function () {
+ this.map.on('moveend', this.fetchRemoteData, this)
+ })
+ },
+
+ displayedOnLoad() {
+ return (
+ (this.map.datalayersOnLoad &&
+ this.umap_id &&
+ this.map.datalayersOnLoad.includes(this.umap_id.toString())) ||
+ (!this.map.datalayersOnLoad && this.options.displayOnLoad)
+ )
+ },
+
+ insertBefore(other) {
+ if (!other) return
+ this.parentPane.insertBefore(this.pane, other.pane)
+ },
+
+ insertAfter(other) {
+ if (!other) return
+ this.parentPane.insertBefore(this.pane, other.pane.nextSibling)
+ },
+
+ bringToTop() {
+ this.parentPane.appendChild(this.pane)
+ },
+
+ resetLayer(force) {
+ if (this.layer && this.options.type === this.layer._type && !force) return
+ const visible = this.isVisible()
+ if (this.layer) this.layer.clearLayers()
+ // delete this.layer?
+ if (visible) this.map.removeLayer(this.layer)
+ const Class = L.U.Layer[this.options.type] || L.U.Layer.Default
+ this.layer = new Class(this)
+ const filterKeys = this.map.getFilterKeys()
+ const filter = this.map.options.filter
+ this.eachLayer(function (layer) {
+ if (filter && !layer.matchFilter(filter, filterKeys)) return
+ this.layer.addLayer(layer)
+ })
+ if (visible) this.map.addLayer(this.layer)
+ this.propagateRemote()
+ },
+
+ eachLayer(method, context) {
+ for (const i in this._layers) {
+ method.call(context || this, this._layers[i])
+ }
+ return this
+ },
+
+ eachFeature(method, context) {
+ if (this.layer && this.layer.canBrowse) {
+ for (let i = 0; i < this._index.length; i++) {
+ method.call(context || this, this._layers[this._index[i]])
+ }
+ }
+ return this
+ },
+
+ fetchData() {
+ if (!this.umap_id) return
+ this.map.get(this._dataUrl(), {
+ callback(geojson, response) {
+ this._last_modified = response.getResponseHeader('Last-Modified')
+ this.fromUmapGeoJSON(geojson)
+ this.backupOptions()
+ this.fire('loaded')
+ },
+ context: this,
+ })
+ },
+
+ fromGeoJSON(geojson) {
+ this.addData(geojson)
+ this._geojson = geojson
+ this.fire('dataloaded')
+ this.fire('datachanged')
+ },
+
+ fromUmapGeoJSON(geojson) {
+ if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat
+ if (geojson._umap_options) this.setOptions(geojson._umap_options)
+ if (this.isRemoteLayer()) this.fetchRemoteData()
+ else this.fromGeoJSON(geojson)
+ this._loaded = true
+ },
+
+ clear() {
+ this.layer.clearLayers()
+ this._layers = {}
+ this._index = Array()
+ if (this._geojson) {
+ this.backupData()
+ this._geojson = null
+ }
+ },
+
+ backupData() {
+ this._geojson_bk = L.Util.CopyJSON(this._geojson)
+ },
+
+ reindex() {
+ const features = []
+ this.eachFeature((feature) => {
+ features.push(feature)
+ })
+ L.Util.sortFeatures(features, this.map.getOption('sortKey'))
+ this._index = []
+ for (let i = 0; i < features.length; i++) {
+ this._index.push(L.Util.stamp(features[i]))
+ }
+ },
+
+ fetchRemoteData() {
+ if (!this.isRemoteLayer()) return
+ const from = parseInt(this.options.remoteData.from, 10)
+ const to = parseInt(this.options.remoteData.to, 10)
+ if (
+ (!isNaN(from) && this.map.getZoom() < from) ||
+ (!isNaN(to) && this.map.getZoom() > to)
+ ) {
+ this.clear()
+ return
+ }
+ if (!this.options.remoteData.dynamic && this.hasDataLoaded()) return
+ if (!this.isVisible()) return
+ const self = this
+ let url = this.map.localizeUrl(this.options.remoteData.url)
+ if (this.options.remoteData.proxy)
+ url = this.map.proxyUrl(url, this.options.remoteData.ttl)
+ this.map.ajax({
+ uri: url,
+ verb: 'GET',
+ callback(raw) {
+ self.clear()
+ self.rawToGeoJSON(raw, self.options.remoteData.format, (geojson) => {
+ self.fromGeoJSON(geojson)
})
- },
+ },
+ })
+ },
- featuresToGeoJSON: function () {
- var features = [];
- this.eachLayer(function (layer) {
- features.push(layer.toGeoJSON());
- });
- return features;
- },
+ onceLoaded(callback, context) {
+ if (this.isLoaded()) callback.call(context || this, this)
+ else this.once('loaded', callback, context)
+ return this
+ },
- show: function () {
- if(!this.isLoaded()) this.fetchData();
- this.map.addLayer(this.layer);
- this.fire('show');
- },
+ onceDataLoaded(callback, context) {
+ if (this.hasDataLoaded()) callback.call(context || this, this)
+ else this.once('dataloaded', callback, context)
+ return this
+ },
- hide: function () {
- this.map.removeLayer(this.layer);
- this.fire('hide');
- },
+ isLoaded() {
+ return !this.umap_id || this._loaded
+ },
- toggle: function () {
- if (!this.isVisible()) this.show();
- else this.hide();
- },
+ hasDataLoaded() {
+ return this._geojson !== null
+ },
- zoomTo: function () {
- if (!this.isVisible()) return;
- var bounds = this.layer.getBounds();
- if (bounds.isValid()) this.map.fitBounds(bounds);
- },
+ setUmapId(id) {
+ // Datalayer is null when listening creation form
+ if (!this.umap_id && id) this.umap_id = id
+ },
- allowBrowse: function () {
- return !!this.options.browsable && this.canBrowse() && this.isVisible();
- },
+ backupOptions() {
+ this._backupOptions = L.Util.CopyJSON(this.options)
+ },
- hasData: function () {
- return !!this._index.length;
- },
+ resetOptions() {
+ this.options = L.Util.CopyJSON(this._backupOptions)
+ },
- isVisible: function () {
- return this.map.hasLayer(this.layer);
- },
+ setOptions(options) {
+ this.options = L.Util.CopyJSON(L.U.DataLayer.prototype.options) // Start from fresh.
+ this.updateOptions(options)
+ },
- canBrowse: function () {
- return this.layer && this.layer.canBrowse;
- },
+ updateOptions(options) {
+ L.Util.setOptions(this, options)
+ this.resetLayer()
+ },
- getFeatureByIndex: function (index) {
- if (index === -1) index = this._index.length - 1;
- var id = this._index[index];
- return this._layers[id];
- },
+ connectToMap() {
+ const id = L.stamp(this)
+ if (!this.map.datalayers[id]) {
+ this.map.datalayers[id] = this
+ if (L.Util.indexOf(this.map.datalayers_index, this) === -1)
+ this.map.datalayers_index.push(this)
+ }
+ this.map.updateDatalayersControl()
+ },
- getNextFeature: function (feature) {
- var id = this._index.indexOf(L.stamp(feature)),
- nextId = this._index[id + 1];
- return nextId? this._layers[nextId]: this.getNextBrowsable().getFeatureByIndex(0);
- },
+ _dataUrl() {
+ const template = this.map.options.urls.datalayer_view
+ let url = L.Util.template(template, {
+ pk: this.umap_id,
+ map_id: this.map.options.umap_id,
+ })
+ // No browser cache for owners/editors.
+ if (this.map.options.allowEdit) url = `${url}?${Date.now()}`
+ return url
+ },
- getPreviousFeature: function (feature) {
- if (this._index <= 1) { return null; }
- var id = this._index.indexOf(L.stamp(feature)),
- previousId = this._index[id - 1];
- return previousId? this._layers[previousId]: this.getPreviousBrowsable().getFeatureByIndex(-1);
- },
+ isRemoteLayer() {
+ return !!(
+ this.options.remoteData &&
+ this.options.remoteData.url &&
+ this.options.remoteData.format
+ )
+ },
- getPreviousBrowsable: function () {
- var id = this.getRank(), next, index = this.map.datalayers_index;
- while(id = index[++id] ? id : 0, next = index[id]) {
- if (next === this || (next.allowBrowse() && next.hasData())) break;
+ isClustered() {
+ return this.options.type === 'Cluster'
+ },
+
+ addLayer(feature) {
+ const id = L.stamp(feature)
+ feature.connectToDataLayer(this)
+ this._index.push(id)
+ this._layers[id] = feature
+ this.layer.addLayer(feature)
+ this.indexProperties(feature)
+ if (this.hasDataLoaded()) this.fire('datachanged')
+ this.map.features_index[feature.getSlug()] = feature
+ },
+
+ removeLayer(feature) {
+ const id = L.stamp(feature)
+ feature.disconnectFromDataLayer(this)
+ this._index.splice(this._index.indexOf(id), 1)
+ delete this._layers[id]
+ this.layer.removeLayer(feature)
+ delete this.map.features_index[feature.getSlug()]
+ if (this.hasDataLoaded()) this.fire('datachanged')
+ },
+
+ indexProperties({ properties }) {
+ for (const i in properties)
+ if (typeof properties[i] !== 'object') this.indexProperty(i)
+ },
+
+ indexProperty(name) {
+ if (!name) return
+ if (name.indexOf('_') === 0) return
+ if (L.Util.indexOf(this._propertiesIndex, name) !== -1) return
+ this._propertiesIndex.push(name)
+ },
+
+ deindexProperty(name) {
+ const idx = this._propertiesIndex.indexOf(name)
+ if (idx !== -1) this._propertiesIndex.splice(idx, 1)
+ },
+
+ addData(geojson) {
+ try {
+ // Do not fail if remote data is somehow invalid,
+ // otherwise the layer becomes uneditable.
+ this.geojsonToFeatures(geojson)
+ } catch (err) {
+ console.log('Error with DataLayer', this.umap_id)
+ console.error(err)
+ }
+ },
+
+ addRawData(c, type) {
+ const self = this
+ this.rawToGeoJSON(c, type, (geojson) => {
+ self.addData(geojson)
+ })
+ },
+
+ rawToGeoJSON(c, type, callback) {
+ const self = this
+ const toDom = (x) => new DOMParser().parseFromString(x, 'text/xml')
+
+ // TODO add a duck typing guessType
+ if (type === 'csv') {
+ csv2geojson.csv2geojson(
+ c,
+ {
+ delimiter: 'auto',
+ includeLatLon: false,
+ },
+ (err, result) => {
+ if (err) {
+ let message
+ if (err.type === 'Error') {
+ message = err.message
+ } else {
+ message = L._('{count} errors during import: {message}', {
+ count: err.length,
+ message: err[0].message,
+ })
+ }
+ self.map.ui.alert({ content: message, level: 'error', duration: 10000 })
+ console.log(err)
+ }
+ if (result && result.features.length) {
+ callback(result)
+ }
}
- return next;
- },
+ )
+ } else if (type === 'gpx') {
+ callback(toGeoJSON.gpx(toDom(c)))
+ } else if (type === 'georss') {
+ callback(GeoRSSToGeoJSON(toDom(c)))
+ } else if (type === 'kml') {
+ callback(toGeoJSON.kml(toDom(c)))
+ } else if (type === 'osm') {
+ let d
+ try {
+ d = JSON.parse(c)
+ } catch (e) {
+ d = toDom(c)
+ }
+ callback(osmtogeojson(d, { flatProperties: true }))
+ } else if (type === 'geojson') {
+ try {
+ const gj = JSON.parse(c)
+ callback(gj)
+ } catch (err) {
+ self.map.ui.alert({ content: `Invalid JSON file: ${err}` })
+ return
+ }
+ }
+ },
- getNextBrowsable: function () {
- var id = this.getRank(), prev, index = this.map.datalayers_index;
- while(id = index[--id] ? id : index.length - 1, prev = index[id]) {
- if (prev === this || (prev.allowBrowse() && prev.hasData())) break;
- }
- return prev;
- },
+ geojsonToFeatures(geojson) {
+ if (!geojson) return
+ const features = geojson instanceof Array ? geojson : geojson.features
+ let i
+ let len
+ let latlng
+ let latlngs
- umapGeoJSON: function () {
- return {
- type: 'FeatureCollection',
- features: this.isRemoteLayer() ? [] : this.featuresToGeoJSON(),
- _umap_options: this.options
- };
- },
-
- metadata: function () {
- return {
- id: this.umap_id,
- name: this.options.name,
- displayOnLoad: this.options.displayOnLoad
- }
- },
-
- getRank: function () {
- return this.map.datalayers_index.indexOf(this);
- },
-
- save: function () {
- if (this.isDeleted) return this.saveDelete();
- if (!this.isLoaded()) {return;}
- var geojson = this.umapGeoJSON();
- var formData = new FormData();
- formData.append('name', this.options.name);
- formData.append('display_on_load', !!this.options.displayOnLoad);
- formData.append('rank', this.getRank());
- // Filename support is shaky, don't do it for now.
- var blob = new Blob([JSON.stringify(geojson)], {type: 'application/json'});
- formData.append('geojson', blob);
- this.map.post(this.getSaveUrl(), {
- data: formData,
- callback: function (data, response) {
- this._geojson = geojson;
- this._last_modified = response.getResponseHeader('Last-Modified');
- this.setUmapId(data.id);
- this.updateOptions(data);
- this.backupOptions();
- this.connectToMap();
- this._loaded = true;
- this.redraw(); // Needed for reordering features
- this.isDirty = false;
- this.map.continueSaving();
- },
- context: this,
- headers: this._last_modified ? {'If-Unmodified-Since': this._last_modified} : {}
- });
- },
-
- saveDelete: function () {
- var callback = function () {
- this.isDirty = false;
- this.map.continueSaving();
- }
- if (!this.umap_id) return callback.call(this);
- this.map.xhr.post(this.getDeleteUrl(), {
- callback: callback,
- context: this
- });
- },
-
- getMap: function () {
- return this.map;
- },
-
- getName: function () {
- return this.options.name || L._('Untitled layer');
- },
-
- tableEdit: function () {
- if (this.isRemoteLayer() || !this.isVisible()) return;
- var editor = new L.U.TableEditor(this);
- editor.edit();
+ if (features) {
+ L.Util.sortFeatures(features, this.map.getOption('sortKey'))
+ for (i = 0, len = features.length; i < len; i++) {
+ this.geojsonToFeatures(features[i])
+ }
+ return this
}
-});
+ const geometry = geojson.type === 'Feature' ? geojson.geometry : geojson
+ if (!geometry) return // null geometry is valid geojson.
+ const coords = geometry.coordinates
+ let layer
+ let tmp
+
+ switch (geometry.type) {
+ case 'Point':
+ try {
+ latlng = L.GeoJSON.coordsToLatLng(coords)
+ } catch (e) {
+ console.error('Invalid latlng object from', coords)
+ break
+ }
+ layer = this._pointToLayer(geojson, latlng)
+ break
+
+ case 'MultiLineString':
+ case 'LineString':
+ latlngs = L.GeoJSON.coordsToLatLngs(
+ coords,
+ geometry.type === 'LineString' ? 0 : 1
+ )
+ if (!latlngs.length) break
+ layer = this._lineToLayer(geojson, latlngs)
+ break
+
+ case 'MultiPolygon':
+ case 'Polygon':
+ latlngs = L.GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2)
+ layer = this._polygonToLayer(geojson, latlngs)
+ break
+ case 'GeometryCollection':
+ return this.geojsonToFeatures(geometry.geometries)
+
+ default:
+ this.map.ui.alert({
+ content: L._('Skipping unknown geometry.type: {type}', {
+ type: geometry.type || 'undefined',
+ }),
+ level: 'error',
+ })
+ }
+ if (layer) {
+ this.addLayer(layer)
+ return layer
+ }
+ },
+
+ _pointToLayer(geojson, latlng) {
+ return new L.U.Marker(this.map, latlng, { geojson: geojson, datalayer: this })
+ },
+
+ _lineToLayer(geojson, latlngs) {
+ return new L.U.Polyline(this.map, latlngs, {
+ geojson: geojson,
+ datalayer: this,
+ color: null,
+ })
+ },
+
+ _polygonToLayer(geojson, latlngs) {
+ // Ensure no empty hole
+ // for (var i = latlngs.length - 1; i > 0; i--) {
+ // if (!latlngs.slice()[i].length) latlngs.splice(i, 1);
+ // }
+ return new L.U.Polygon(this.map, latlngs, { geojson: geojson, datalayer: this })
+ },
+
+ importRaw(raw, type) {
+ this.addRawData(raw, type)
+ this.isDirty = true
+ this.zoomTo()
+ },
+
+ importFromFiles(files, type) {
+ for (let i = 0, f; (f = files[i]); i++) {
+ this.importFromFile(f, type)
+ }
+ },
+
+ importFromFile(f, type) {
+ const reader = new FileReader()
+ const self = this
+ type = type || L.Util.detectFileType(f)
+ reader.readAsText(f)
+ reader.onload = ({ target }) => {
+ self.importRaw(target.result, type)
+ }
+ },
+
+ importFromUrl(url, type) {
+ url = this.map.localizeUrl(url)
+ const self = this
+ this.map.xhr._ajax({
+ verb: 'GET',
+ uri: url,
+ callback(data) {
+ self.importRaw(data, type)
+ },
+ })
+ },
+
+ getEditUrl() {
+ return L.Util.template(this.map.options.urls.datalayer_update, {
+ map_id: this.map.options.umap_id,
+ pk: this.umap_id,
+ })
+ },
+
+ getCreateUrl() {
+ return L.Util.template(this.map.options.urls.datalayer_create, {
+ map_id: this.map.options.umap_id,
+ })
+ },
+
+ getSaveUrl() {
+ return (this.umap_id && this.getEditUrl()) || this.getCreateUrl()
+ },
+
+ getColor() {
+ return this.options.color || this.map.getOption('color')
+ },
+
+ getDeleteUrl() {
+ return L.Util.template(this.map.options.urls.datalayer_delete, {
+ pk: this.umap_id,
+ map_id: this.map.options.umap_id,
+ })
+ },
+
+ getVersionsUrl() {
+ return L.Util.template(this.map.options.urls.datalayer_versions, {
+ pk: this.umap_id,
+ map_id: this.map.options.umap_id,
+ })
+ },
+
+ getVersionUrl(name) {
+ return L.Util.template(this.map.options.urls.datalayer_version, {
+ pk: this.umap_id,
+ map_id: this.map.options.umap_id,
+ name,
+ })
+ },
+
+ _delete() {
+ this.isDeleted = true
+ this.erase()
+ },
+
+ empty() {
+ if (this.isRemoteLayer()) return
+ this.clear()
+ this.isDirty = true
+ },
+
+ clone() {
+ const options = L.Util.CopyJSON(this.options)
+ options.name = L._('Clone of {name}', { name: this.options.name })
+ delete options.id
+ const geojson = L.Util.CopyJSON(this._geojson)
+ const datalayer = this.map.createDataLayer(options)
+ datalayer.fromGeoJSON(geojson)
+ return datalayer
+ },
+
+ erase() {
+ this.hide()
+ delete this.map.datalayers[L.stamp(this)]
+ this.map.datalayers_index.splice(this.getRank(), 1)
+ this.parentPane.removeChild(this.pane)
+ this.map.updateDatalayersControl()
+ this.fire('erase')
+ this._leaflet_events_bk = this._leaflet_events
+ this.off()
+ this.clear()
+ delete this._loaded
+ },
+
+ reset() {
+ if (!this.umap_id) this.erase()
+
+ this.resetOptions()
+ this.parentPane.appendChild(this.pane)
+ if (this._leaflet_events_bk && !this._leaflet_events) {
+ this._leaflet_events = this._leaflet_events_bk
+ }
+ this.clear()
+ this.hide()
+ if (this.isRemoteLayer()) this.fetchRemoteData()
+ else if (this._geojson_bk) this.fromGeoJSON(this._geojson_bk)
+ this._loaded = true
+ this.show()
+ this.isDirty = false
+ },
+
+ redraw() {
+ this.hide()
+ this.show()
+ },
+
+ edit() {
+ if (!this.map.editEnabled || !this.isLoaded()) {
+ return
+ }
+ const container = L.DomUtil.create('div', 'umap-layer-properties-container')
+
+ const metadataFields = [
+ 'options.name',
+ 'options.description',
+ ['options.type', { handler: 'LayerTypeChooser', label: L._('Type of layer') }],
+ ['options.displayOnLoad', { label: L._('Display on load'), handler: 'Switch' }],
+ [
+ 'options.browsable',
+ {
+ label: L._('Data is browsable'),
+ handler: 'Switch',
+ helpEntries: 'browsable',
+ },
+ ],
+ ]
+
+ const title = L.DomUtil.add('h3', '', container, L._('Layer properties'))
+ let builder = new L.U.FormBuilder(this, metadataFields, {
+ callback({ helper }) {
+ this.map.updateDatalayersControl()
+ if (helper.field === 'options.type') {
+ this.resetLayer()
+ this.edit()
+ }
+ },
+ })
+ container.appendChild(builder.build())
+
+ let shapeOptions = [
+ 'options.color',
+ 'options.iconClass',
+ 'options.iconUrl',
+ 'options.opacity',
+ 'options.stroke',
+ 'options.weight',
+ 'options.fill',
+ 'options.fillColor',
+ 'options.fillOpacity',
+ ]
+
+ shapeOptions = shapeOptions.concat(this.layer.getEditableOptions())
+
+ const redrawCallback = function (field) {
+ this.hide()
+ this.layer.postUpdate(field)
+ this.show()
+ }
+
+ builder = new L.U.FormBuilder(this, shapeOptions, {
+ id: 'datalayer-advanced-properties',
+ callback: redrawCallback,
+ })
+ const shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties'))
+ shapeProperties.appendChild(builder.build())
+
+ let optionsFields = [
+ 'options.smoothFactor',
+ 'options.dashArray',
+ 'options.zoomTo',
+ 'options.labelKey',
+ ]
+
+ optionsFields = optionsFields.concat(this.layer.getEditableOptions())
+
+ builder = new L.U.FormBuilder(this, optionsFields, {
+ id: 'datalayer-advanced-properties',
+ callback: redrawCallback,
+ })
+ const advancedProperties = L.DomUtil.createFieldset(
+ container,
+ L._('Advanced properties')
+ )
+ advancedProperties.appendChild(builder.build())
+
+ const popupFields = [
+ 'options.popupShape',
+ 'options.popupTemplate',
+ 'options.popupContentTemplate',
+ 'options.showLabel',
+ 'options.labelDirection',
+ 'options.labelInteractive',
+ ]
+ builder = new L.U.FormBuilder(this, popupFields, { callback: redrawCallback })
+ const popupFieldset = L.DomUtil.createFieldset(
+ container,
+ L._('Interaction options')
+ )
+ popupFieldset.appendChild(builder.build())
+
+ if (!L.Util.isObject(this.options.remoteData)) {
+ this.options.remoteData = {}
+ }
+ const remoteDataFields = [
+ [
+ 'options.remoteData.url',
+ { handler: 'Url', label: L._('Url'), helpEntries: 'formatURL' },
+ ],
+ ['options.remoteData.format', { handler: 'DataFormat', label: L._('Format') }],
+ [
+ 'options.remoteData.from',
+ { label: L._('From zoom'), helpText: L._('Optional.') },
+ ],
+ ['options.remoteData.to', { label: L._('To zoom'), helpText: L._('Optional.') }],
+ [
+ 'options.remoteData.dynamic',
+ { handler: 'Switch', label: L._('Dynamic'), helpEntries: 'dynamicRemoteData' },
+ ],
+ [
+ 'options.remoteData.licence',
+ {
+ label: L._('Licence'),
+ helpText: L._('Please be sure the licence is compliant with your use.'),
+ },
+ ],
+ ]
+ if (this.map.options.urls.ajax_proxy) {
+ remoteDataFields.push([
+ 'options.remoteData.proxy',
+ {
+ handler: 'Switch',
+ label: L._('Proxy request'),
+ helpEntries: 'proxyRemoteData',
+ },
+ ])
+ remoteDataFields.push([
+ 'options.remoteData.ttl',
+ { handler: 'ProxyTTLSelect', label: L._('Cache proxied request') },
+ ])
+ }
+
+ const remoteDataContainer = L.DomUtil.createFieldset(container, L._('Remote data'))
+ builder = new L.U.FormBuilder(this, remoteDataFields)
+ remoteDataContainer.appendChild(builder.build())
+
+ if (this.map.options.urls.datalayer_versions) this.buildVersionsFieldset(container)
+
+ const advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'))
+ const advancedButtons = L.DomUtil.create('div', 'button-bar half', advancedActions)
+ const deleteLink = L.DomUtil.create(
+ 'a',
+ 'button delete_datalayer_button umap-delete',
+ advancedButtons
+ )
+ deleteLink.textContent = L._('Delete')
+ deleteLink.href = '#'
+ L.DomEvent.on(deleteLink, 'click', L.DomEvent.stop).on(
+ deleteLink,
+ 'click',
+ function () {
+ this._delete()
+ this.map.ui.closePanel()
+ },
+ this
+ )
+ if (!this.isRemoteLayer()) {
+ const emptyLink = L.DomUtil.create('a', 'button umap-empty', advancedButtons)
+ emptyLink.textContent = L._('Empty')
+ emptyLink.href = '#'
+ L.DomEvent.on(emptyLink, 'click', L.DomEvent.stop).on(
+ emptyLink,
+ 'click',
+ this.empty,
+ this
+ )
+ }
+ const cloneLink = L.DomUtil.create('a', 'button umap-clone', advancedButtons)
+ cloneLink.textContent = L._('Clone')
+ cloneLink.href = '#'
+ L.DomEvent.on(cloneLink, 'click', L.DomEvent.stop).on(
+ cloneLink,
+ 'click',
+ function () {
+ const datalayer = this.clone()
+ datalayer.edit()
+ },
+ this
+ )
+ if (this.umap_id) {
+ const download = L.DomUtil.create('a', 'button umap-download', advancedButtons)
+ download.textContent = L._('Download')
+ download.href = this._dataUrl()
+ download.target = '_blank'
+ }
+ this.map.ui.openPanel({ data: { html: container }, className: 'dark' })
+ },
+
+ getOption(option) {
+ if (L.Util.usableOption(this.options, option)) return this.options[option]
+ else return this.map.getOption(option)
+ },
+
+ buildVersionsFieldset(container) {
+ const appendVersion = function ({ at, size, name }) {
+ const date = new Date(parseInt(at, 10))
+ const content = `${date.toLocaleDateString(L.locale)} (${
+ parseInt(size) / 1000
+ }Kb)`
+ const el = L.DomUtil.create('div', 'umap-datalayer-version', versionsContainer)
+ const a = L.DomUtil.create('a', '', el)
+ L.DomUtil.add('span', '', el, content)
+ a.href = '#'
+ a.title = L._('Restore this version')
+ L.DomEvent.on(a, 'click', L.DomEvent.stop).on(
+ a,
+ 'click',
+ function () {
+ this.restore(name)
+ },
+ this
+ )
+ }
+
+ var versionsContainer = L.DomUtil.createFieldset(container, L._('Versions'), {
+ callback() {
+ this.map.xhr.get(this.getVersionsUrl(), {
+ callback({ versions }) {
+ for (let i = 0; i < versions.length; i++) {
+ appendVersion.call(this, versions[i])
+ }
+ },
+ context: this,
+ })
+ },
+ context: this,
+ })
+ },
+
+ restore(version) {
+ if (!this.map.editEnabled) return
+ if (!confirm(L._('Are you sure you want to restore this version?'))) return
+ this.map.xhr.get(this.getVersionUrl(version), {
+ callback(geojson) {
+ if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat.
+ if (geojson._umap_options) this.setOptions(geojson._umap_options)
+ this.empty()
+ if (this.isRemoteLayer()) this.fetchRemoteData()
+ else this.addData(geojson)
+ this.isDirty = true
+ },
+ context: this,
+ })
+ },
+
+ featuresToGeoJSON() {
+ const features = []
+ this.eachLayer((layer) => {
+ features.push(layer.toGeoJSON())
+ })
+ return features
+ },
+
+ show() {
+ if (!this.isLoaded()) this.fetchData()
+ this.map.addLayer(this.layer)
+ this.fire('show')
+ },
+
+ hide() {
+ this.map.removeLayer(this.layer)
+ this.fire('hide')
+ },
+
+ toggle() {
+ if (!this.isVisible()) this.show()
+ else this.hide()
+ },
+
+ zoomTo() {
+ if (!this.isVisible()) return
+ const bounds = this.layer.getBounds()
+ if (bounds.isValid()) this.map.fitBounds(bounds)
+ },
+
+ allowBrowse() {
+ return !!this.options.browsable && this.canBrowse() && this.isVisible()
+ },
+
+ hasData() {
+ return !!this._index.length
+ },
+
+ isVisible() {
+ return this.map.hasLayer(this.layer)
+ },
+
+ canBrowse() {
+ return this.layer && this.layer.canBrowse
+ },
+
+ getFeatureByIndex(index) {
+ if (index === -1) index = this._index.length - 1
+ const id = this._index[index]
+ return this._layers[id]
+ },
+
+ getNextFeature(feature) {
+ const id = this._index.indexOf(L.stamp(feature))
+ const nextId = this._index[id + 1]
+ return nextId ? this._layers[nextId] : this.getNextBrowsable().getFeatureByIndex(0)
+ },
+
+ getPreviousFeature(feature) {
+ if (this._index <= 1) {
+ return null
+ }
+ const id = this._index.indexOf(L.stamp(feature))
+ const previousId = this._index[id - 1]
+ return previousId
+ ? this._layers[previousId]
+ : this.getPreviousBrowsable().getFeatureByIndex(-1)
+ },
+
+ getPreviousBrowsable() {
+ let id = this.getRank()
+ let next
+ const index = this.map.datalayers_index
+ while (((id = index[++id] ? id : 0), (next = index[id]))) {
+ if (next === this || (next.allowBrowse() && next.hasData())) break
+ }
+ return next
+ },
+
+ getNextBrowsable() {
+ let id = this.getRank()
+ let prev
+ const index = this.map.datalayers_index
+ while (((id = index[--id] ? id : index.length - 1), (prev = index[id]))) {
+ if (prev === this || (prev.allowBrowse() && prev.hasData())) break
+ }
+ return prev
+ },
+
+ umapGeoJSON() {
+ return {
+ type: 'FeatureCollection',
+ features: this.isRemoteLayer() ? [] : this.featuresToGeoJSON(),
+ _umap_options: this.options,
+ }
+ },
+
+ metadata() {
+ return {
+ id: this.umap_id,
+ name: this.options.name,
+ displayOnLoad: this.options.displayOnLoad,
+ }
+ },
+
+ getRank() {
+ return this.map.datalayers_index.indexOf(this)
+ },
+
+ save() {
+ if (this.isDeleted) return this.saveDelete()
+ if (!this.isLoaded()) {
+ return
+ }
+ const geojson = this.umapGeoJSON()
+ const formData = new FormData()
+ formData.append('name', this.options.name)
+ formData.append('display_on_load', !!this.options.displayOnLoad)
+ formData.append('rank', this.getRank())
+ // Filename support is shaky, don't do it for now.
+ const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
+ formData.append('geojson', blob)
+ this.map.post(this.getSaveUrl(), {
+ data: formData,
+ callback(data, response) {
+ this._geojson = geojson
+ this._last_modified = response.getResponseHeader('Last-Modified')
+ this.setUmapId(data.id)
+ this.updateOptions(data)
+ this.backupOptions()
+ this.connectToMap()
+ this._loaded = true
+ this.redraw() // Needed for reordering features
+ this.isDirty = false
+ this.map.continueSaving()
+ },
+ context: this,
+ headers: this._last_modified
+ ? { 'If-Unmodified-Since': this._last_modified }
+ : {},
+ })
+ },
+
+ saveDelete() {
+ const callback = function () {
+ this.isDirty = false
+ this.map.continueSaving()
+ }
+ if (!this.umap_id) return callback.call(this)
+ this.map.xhr.post(this.getDeleteUrl(), {
+ callback,
+ context: this,
+ })
+ },
+
+ getMap() {
+ return this.map
+ },
+
+ getName() {
+ return this.options.name || L._('Untitled layer')
+ },
+
+ tableEdit() {
+ if (this.isRemoteLayer() || !this.isVisible()) return
+ const editor = new L.U.TableEditor(this)
+ editor.edit()
+ },
+})
L.TileLayer.include({
-
- toJSON: function () {
- return {
- minZoom: this.options.minZoom,
- maxZoom: this.options.maxZoom,
- attribution: this.options.attribution,
- url_template: this._url,
- name: this.options.name,
- tms: this.options.tms
- };
- },
-
- getAttribution: function () {
- return L.Util.toHTML(this.options.attribution);
+ toJSON() {
+ return {
+ minZoom: this.options.minZoom,
+ maxZoom: this.options.maxZoom,
+ attribution: this.options.attribution,
+ url_template: this._url,
+ name: this.options.name,
+ tms: this.options.tms,
}
+ },
-});
+ getAttribution() {
+ return L.Util.toHTML(this.options.attribution)
+ },
+})
diff --git a/umap/static/umap/js/umap.permissions.js b/umap/static/umap/js/umap.permissions.js
index 6d6cc977..76cff829 100644
--- a/umap/static/umap/js/umap.permissions.js
+++ b/umap/static/umap/js/umap.permissions.js
@@ -1,143 +1,197 @@
// Dedicated object so we can deal with a separate dirty status, and thus
// call the endpoint only when needed, saving one call at each save.
L.U.MapPermissions = L.Class.extend({
+ options: {
+ owner: null,
+ editors: [],
+ share_status: null,
+ edit_status: null,
+ },
- options: {
- owner: null,
- editors: [],
- share_status: null,
- edit_status: null
- },
-
- initialize: function (map) {
- this.setOptions(map.options.permissions);
- this.map = map;
- var isDirty = false,
- self = this;
- try {
- Object.defineProperty(this, 'isDirty', {
- get: function () {
- return isDirty;
- },
- set: function (status) {
- isDirty = status;
- if (status) self.map.isDirty = status;
- }
- });
- }
- catch (e) {
- // Certainly IE8, which has a limited version of defineProperty
- }
-
- },
-
- setOptions: function (options) {
- this.options = L.Util.setOptions(this, options);
- },
-
- isOwner: function () {
- return this.map.options.user && this.map.options.permissions.owner && this.map.options.user.id == this.map.options.permissions.owner.id;
- },
-
- isAnonymousMap: function () {
- return !this.map.options.permissions.owner;
- },
-
- getMap: function () {
- return this.map;
- },
-
- edit: function () {
- if (!this.map.options.umap_id) return this.map.ui.alert({content: L._('Please save the map first'), level: 'info'});
- var container = L.DomUtil.create('div', 'permissions-panel'),
- fields = [],
- title = L.DomUtil.create('h4', '', container);
- if (this.isAnonymousMap()) {
- if (this.options.anonymous_edit_url) {
- var helpText = L._('Secret edit link is:
{link}', {link: this.options.anonymous_edit_url});
- fields.push(['options.edit_status', {handler: 'IntSelect', label: L._('Who can edit'), selectOptions: this.map.options.anonymous_edit_statuses, helpText: helpText}]);
- }
- } else {
- if (this.isOwner()) {
- fields.push(['options.edit_status', {handler: 'IntSelect', label: L._('Who can edit'), selectOptions: this.map.options.edit_statuses}]);
- fields.push(['options.share_status', {handler: 'IntSelect', label: L._('Who can view'), selectOptions: this.map.options.share_statuses}]);
- fields.push(['options.owner', {handler: 'ManageOwner', label: L._("Map's owner")}]);
- }
- fields.push(['options.editors', {handler: 'ManageEditors', label: L._("Map's editors")}]);
- }
- title.textContent = L._('Update permissions');
- var builder = new L.U.FormBuilder(this, fields);
- var form = builder.build();
- container.appendChild(form);
- if (this.isAnonymousMap() && this.map.options.user) {
- // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map.
- // Note: real check is made on the back office anyway.
- var advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'));
- var advancedButtons = L.DomUtil.create('div', 'button-bar', advancedActions);
- var download = L.DomUtil.create('a', 'button', advancedButtons);
- download.href = '#';
- download.textContent = L._('Attach the map to my account');
- L.DomEvent
- .on(download, 'click', L.DomEvent.stop)
- .on(download, 'click', this.attach, this);
- }
- this.map.ui.openPanel({data: {html: container}, className: 'dark'});
- },
-
- attach: function () {
- this.map.post(this.getAttachUrl(), {
- callback: function () {
- this.options.owner = this.map.options.user;
- this.map.ui.alert({content: L._("Map has been attached to your account"), level: 'info'});
- this.map.ui.closePanel();
- },
- context: this
- })
- },
-
- save: function () {
- if (!this.isDirty) return this.map.continueSaving();
- var formData = new FormData();
- if (!this.isAnonymousMap() && this.options.editors) {
- var editors = this.options.editors.map(function (u) {return u.id});
- for (var i = 0; i < this.options.editors.length; i++) formData.append('editors', this.options.editors[i].id);
- }
- if (this.isOwner() || this.isAnonymousMap()) formData.append('edit_status', this.options.edit_status);
- if (this.isOwner()) {
- formData.append('owner', this.options.owner && this.options.owner.id);
- formData.append('share_status', this.options.share_status);
- }
- this.map.post(this.getUrl(), {
- data: formData,
- context: this,
- callback: function (data) {
- this.commit();
- this.isDirty = false;
- this.map.continueSaving();
- }
- });
- },
-
- getUrl: function () {
- return L.Util.template(this.map.options.urls.map_update_permissions, {'map_id': this.map.options.umap_id});
- },
-
- getAttachUrl: function () {
- return L.Util.template(this.map.options.urls.map_attach_owner, {'map_id': this.map.options.umap_id});
- },
-
- addOwnerLink: function (element, container) {
- if (this.options.owner && this.options.owner.name && this.options.owner.url) {
- var ownerContainer = L.DomUtil.add(element, 'umap-map-owner', container, ' ' + L._('by') + ' '),
- owner = L.DomUtil.create('a');
- owner.href = this.options.owner.url;
- owner.textContent = this.options.owner.name;
- ownerContainer.appendChild(owner);
- }
- },
-
- commit: function () {
- L.Util.extend(this.map.options.permissions, this.options);
+ initialize(map) {
+ this.setOptions(map.options.permissions)
+ this.map = map
+ let isDirty = false
+ const self = this
+ try {
+ Object.defineProperty(this, 'isDirty', {
+ get() {
+ return isDirty
+ },
+ set(status) {
+ isDirty = status
+ if (status) self.map.isDirty = status
+ },
+ })
+ } catch (e) {
+ // Certainly IE8, which has a limited version of defineProperty
}
+ },
-});
+ setOptions(options) {
+ this.options = L.Util.setOptions(this, options)
+ },
+
+ isOwner() {
+ return (
+ this.map.options.user &&
+ this.map.options.permissions.owner &&
+ this.map.options.user.id == this.map.options.permissions.owner.id
+ )
+ },
+
+ isAnonymousMap() {
+ return !this.map.options.permissions.owner
+ },
+
+ getMap() {
+ return this.map
+ },
+
+ edit() {
+ if (!this.map.options.umap_id)
+ return this.map.ui.alert({
+ content: L._('Please save the map first'),
+ level: 'info',
+ })
+ const container = L.DomUtil.create('div', 'permissions-panel')
+ const fields = []
+ const title = L.DomUtil.create('h4', '', container)
+ if (this.isAnonymousMap()) {
+ if (this.options.anonymous_edit_url) {
+ const helpText = L._('Secret edit link is:
{link}', {
+ link: this.options.anonymous_edit_url,
+ })
+ fields.push([
+ 'options.edit_status',
+ {
+ handler: 'IntSelect',
+ label: L._('Who can edit'),
+ selectOptions: this.map.options.anonymous_edit_statuses,
+ helpText,
+ },
+ ])
+ }
+ } else {
+ if (this.isOwner()) {
+ fields.push([
+ 'options.edit_status',
+ {
+ handler: 'IntSelect',
+ label: L._('Who can edit'),
+ selectOptions: this.map.options.edit_statuses,
+ },
+ ])
+ fields.push([
+ 'options.share_status',
+ {
+ handler: 'IntSelect',
+ label: L._('Who can view'),
+ selectOptions: this.map.options.share_statuses,
+ },
+ ])
+ fields.push([
+ 'options.owner',
+ { handler: 'ManageOwner', label: L._("Map's owner") },
+ ])
+ }
+ fields.push([
+ 'options.editors',
+ { handler: 'ManageEditors', label: L._("Map's editors") },
+ ])
+ }
+ title.textContent = L._('Update permissions')
+ const builder = new L.U.FormBuilder(this, fields)
+ const form = builder.build()
+ container.appendChild(form)
+ if (this.isAnonymousMap() && this.map.options.user) {
+ // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map.
+ // Note: real check is made on the back office anyway.
+ const advancedActions = L.DomUtil.createFieldset(
+ container,
+ L._('Advanced actions')
+ )
+ const advancedButtons = L.DomUtil.create('div', 'button-bar', advancedActions)
+ const download = L.DomUtil.create('a', 'button', advancedButtons)
+ download.href = '#'
+ download.textContent = L._('Attach the map to my account')
+ L.DomEvent.on(download, 'click', L.DomEvent.stop).on(
+ download,
+ 'click',
+ this.attach,
+ this
+ )
+ }
+ this.map.ui.openPanel({ data: { html: container }, className: 'dark' })
+ },
+
+ attach() {
+ this.map.post(this.getAttachUrl(), {
+ callback() {
+ this.options.owner = this.map.options.user
+ this.map.ui.alert({
+ content: L._('Map has been attached to your account'),
+ level: 'info',
+ })
+ this.map.ui.closePanel()
+ },
+ context: this,
+ })
+ },
+
+ save() {
+ if (!this.isDirty) return this.map.continueSaving()
+ const formData = new FormData()
+ if (!this.isAnonymousMap() && this.options.editors) {
+ const editors = this.options.editors.map(({ id }) => id)
+ for (let i = 0; i < this.options.editors.length; i++)
+ formData.append('editors', this.options.editors[i].id)
+ }
+ if (this.isOwner() || this.isAnonymousMap())
+ formData.append('edit_status', this.options.edit_status)
+ if (this.isOwner()) {
+ formData.append('owner', this.options.owner && this.options.owner.id)
+ formData.append('share_status', this.options.share_status)
+ }
+ this.map.post(this.getUrl(), {
+ data: formData,
+ context: this,
+ callback(data) {
+ this.commit()
+ this.isDirty = false
+ this.map.continueSaving()
+ },
+ })
+ },
+
+ getUrl() {
+ return L.Util.template(this.map.options.urls.map_update_permissions, {
+ map_id: this.map.options.umap_id,
+ })
+ },
+
+ getAttachUrl() {
+ return L.Util.template(this.map.options.urls.map_attach_owner, {
+ map_id: this.map.options.umap_id,
+ })
+ },
+
+ addOwnerLink(element, container) {
+ if (this.options.owner && this.options.owner.name && this.options.owner.url) {
+ const ownerContainer = L.DomUtil.add(
+ element,
+ 'umap-map-owner',
+ container,
+ ` ${L._('by')} `
+ )
+ const owner = L.DomUtil.create('a')
+ owner.href = this.options.owner.url
+ owner.textContent = this.options.owner.name
+ ownerContainer.appendChild(owner)
+ }
+ },
+
+ commit() {
+ L.Util.extend(this.map.options.permissions, this.options)
+ },
+})
diff --git a/umap/static/umap/js/umap.popup.js b/umap/static/umap/js/umap.popup.js
index aca9d533..3ea657df 100644
--- a/umap/static/umap/js/umap.popup.js
+++ b/umap/static/umap/js/umap.popup.js
@@ -1,224 +1,239 @@
/* Shapes */
L.U.Popup = L.Popup.extend({
+ options: {
+ parseTemplate: true,
+ },
- options: {
- parseTemplate: true
- },
+ initialize(feature) {
+ this.feature = feature
+ this.container = L.DomUtil.create('div', 'umap-popup')
+ this.format()
+ L.Popup.prototype.initialize.call(this, {}, feature)
+ this.setContent(this.container)
+ },
- initialize: function (feature) {
- this.feature = feature;
- this.container = L.DomUtil.create('div', 'umap-popup');
- this.format();
- L.Popup.prototype.initialize.call(this, {}, feature);
- this.setContent(this.container);
- },
-
- format: function () {
- var mode = this.feature.getOption('popupTemplate') || 'Default',
- klass = L.U.PopupTemplate[mode] || L.U.PopupTemplate.Default;
- this.content = new klass(this.feature, this.container);
- this.content.render();
- var els = this.container.querySelectorAll('img,iframe');
- for (var i = 0; i < els.length; i++) {
- this.onElementLoaded(els[i]);
- }
- if (!els.length && this.container.textContent.replace('\n', '') === '') {
- this.container.innerHTML = '';
- L.DomUtil.add('h3', '', this.container, this.feature.getDisplayName());
- }
- },
-
- onElementLoaded: function (el) {
- L.DomEvent.on(el, 'load', function () {
- this._updateLayout();
- this._updatePosition();
- this._adjustPan();
- }, this);
+ format() {
+ const mode = this.feature.getOption('popupTemplate') || 'Default'
+ const klass = L.U.PopupTemplate[mode] || L.U.PopupTemplate.Default
+ this.content = new klass(this.feature, this.container)
+ this.content.render()
+ const els = this.container.querySelectorAll('img,iframe')
+ for (let i = 0; i < els.length; i++) {
+ this.onElementLoaded(els[i])
}
+ if (!els.length && this.container.textContent.replace('\n', '') === '') {
+ this.container.innerHTML = ''
+ L.DomUtil.add('h3', '', this.container, this.feature.getDisplayName())
+ }
+ },
-});
+ onElementLoaded(el) {
+ L.DomEvent.on(
+ el,
+ 'load',
+ function () {
+ this._updateLayout()
+ this._updatePosition()
+ this._adjustPan()
+ },
+ this
+ )
+ },
+})
L.U.Popup.Large = L.U.Popup.extend({
- options: {
- maxWidth: 500,
- className: 'umap-popup-large'
- }
-});
-
+ options: {
+ maxWidth: 500,
+ className: 'umap-popup-large',
+ },
+})
L.U.Popup.Panel = L.U.Popup.extend({
+ options: {
+ zoomAnimation: false,
+ },
- options: {
- zoomAnimation: false
- },
+ allButton() {
+ const button = L.DomUtil.create('li', '')
+ L.DomUtil.create('i', 'umap-icon-16 umap-list', button)
+ const label = L.DomUtil.create('span', '', button)
+ label.textContent = label.title = L._('See all')
+ L.DomEvent.on(button, 'click', this.feature.map.openBrowser, this.feature.map)
+ return button
+ },
- allButton: function () {
- var button = L.DomUtil.create('li', '');
- L.DomUtil.create('i', 'umap-icon-16 umap-list', button);
- var label = L.DomUtil.create('span', '', button);
- label.textContent = label.title = L._('See all');
- L.DomEvent.on(button, 'click', this.feature.map.openBrowser, this.feature.map);
- return button;
- },
+ update() {
+ this.feature.map.ui.openPanel({
+ data: { html: this._content },
+ actions: [this.allButton()],
+ })
+ },
- update: function () {
- this.feature.map.ui.openPanel({data: {html: this._content}, actions: [this.allButton()]});
- },
+ onRemove(map) {
+ map.ui.closePanel()
+ L.U.Popup.prototype.onRemove.call(this, map)
+ },
- onRemove: function (map) {
- map.ui.closePanel();
- L.U.Popup.prototype.onRemove.call(this, map);
- },
-
- _initLayout: function () {this._container = L.DomUtil.create('span');},
- _updateLayout: function () {},
- _updatePosition: function () {},
- _adjustPan: function () {}
-
-});
-L.U.Popup.SimplePanel = L.U.Popup.Panel; // Retrocompat.
+ _initLayout() {
+ this._container = L.DomUtil.create('span')
+ },
+ _updateLayout() {},
+ _updatePosition() {},
+ _adjustPan() {},
+})
+L.U.Popup.SimplePanel = L.U.Popup.Panel // Retrocompat.
/* Content templates */
-L.U.PopupTemplate = {};
+L.U.PopupTemplate = {}
L.U.PopupTemplate.Default = L.Class.extend({
+ initialize(feature, container) {
+ this.feature = feature
+ this.container = container
+ },
- initialize: function (feature, container) {
- this.feature = feature;
- this.container = container;
- },
+ renderTitle() {},
- renderTitle: function () {},
+ renderBody() {
+ const template = this.feature.getOption('popupContentTemplate')
+ const container = L.DomUtil.create('div', 'umap-popup-container')
+ let content = ''
+ let properties
+ let center
+ properties = this.feature.extendedProperties()
+ // Resolve properties inside description
+ properties.description = L.Util.greedyTemplate(
+ this.feature.properties.description || '',
+ properties
+ )
+ content = L.Util.greedyTemplate(template, properties)
+ content = L.Util.toHTML(content)
+ container.innerHTML = content
+ return container
+ },
- renderBody: function () {
- var template = this.feature.getOption('popupContentTemplate'),
- container = L.DomUtil.create('div', 'umap-popup-container'),
- content = '', properties, center;
- properties = this.feature.extendedProperties();
- // Resolve properties inside description
- properties.description = L.Util.greedyTemplate(this.feature.properties.description || '', properties);
- content = L.Util.greedyTemplate(template, properties);
- content = L.Util.toHTML(content);
- container.innerHTML = content;
- return container;
- },
-
- renderFooter: function () {
- if (this.feature.hasPopupFooter()) {
- var footerContainer = L.DomUtil.create('div', 'umap-footer-container', this.container),
- footer = L.DomUtil.create('ul', 'umap-popup-footer', footerContainer),
- previousLi = L.DomUtil.create('li', 'previous', footer),
- zoomLi = L.DomUtil.create('li', 'zoom', footer),
- nextLi = L.DomUtil.create('li', 'next', footer),
- next = this.feature.getNext(),
- prev = this.feature.getPrevious();
- if (next) nextLi.title = L._('Go to «{feature}»', {feature: next.properties.name || L._('next')});
- if (prev) previousLi.title = L._('Go to «{feature}»', {feature: prev.properties.name || L._('previous')});
- zoomLi.title = L._('Zoom to this feature');
- L.DomEvent.on(nextLi, 'click', function () {
- if (next) next.zoomTo({callback: next.view});
- });
- L.DomEvent.on(previousLi, 'click', function () {
- if (prev) prev.zoomTo({callback: prev.view});
- });
- L.DomEvent.on(zoomLi, 'click', function () {
- this.zoomTo();
- }, this.feature);
- }
- },
-
- render: function () {
- var title = this.renderTitle();
- if (title) this.container.appendChild(title);
- var body = this.renderBody();
- if (body) L.DomUtil.add('div', 'umap-popup-content', this.container, body);
- this.renderFooter();
+ renderFooter() {
+ if (this.feature.hasPopupFooter()) {
+ const footerContainer = L.DomUtil.create(
+ 'div',
+ 'umap-footer-container',
+ this.container
+ )
+ const footer = L.DomUtil.create('ul', 'umap-popup-footer', footerContainer)
+ const previousLi = L.DomUtil.create('li', 'previous', footer)
+ const zoomLi = L.DomUtil.create('li', 'zoom', footer)
+ const nextLi = L.DomUtil.create('li', 'next', footer)
+ const next = this.feature.getNext()
+ const prev = this.feature.getPrevious()
+ if (next)
+ nextLi.title = L._('Go to «{feature}»', {
+ feature: next.properties.name || L._('next'),
+ })
+ if (prev)
+ previousLi.title = L._('Go to «{feature}»', {
+ feature: prev.properties.name || L._('previous'),
+ })
+ zoomLi.title = L._('Zoom to this feature')
+ L.DomEvent.on(nextLi, 'click', () => {
+ if (next) next.zoomTo({ callback: next.view })
+ })
+ L.DomEvent.on(previousLi, 'click', () => {
+ if (prev) prev.zoomTo({ callback: prev.view })
+ })
+ L.DomEvent.on(
+ zoomLi,
+ 'click',
+ function () {
+ this.zoomTo()
+ },
+ this.feature
+ )
}
+ },
-});
+ render() {
+ const title = this.renderTitle()
+ if (title) this.container.appendChild(title)
+ const body = this.renderBody()
+ if (body) L.DomUtil.add('div', 'umap-popup-content', this.container, body)
+ this.renderFooter()
+ },
+})
L.U.PopupTemplate.BaseWithTitle = L.U.PopupTemplate.Default.extend({
-
- renderTitle: function () {
- var title;
- if (this.feature.getDisplayName()) {
- title = L.DomUtil.create('h3', 'popup-title');
- title.textContent = this.feature.getDisplayName();
- }
- return title;
+ renderTitle() {
+ let title
+ if (this.feature.getDisplayName()) {
+ title = L.DomUtil.create('h3', 'popup-title')
+ title.textContent = this.feature.getDisplayName()
}
-
-});
-
+ return title
+ },
+})
L.U.PopupTemplate.Table = L.U.PopupTemplate.BaseWithTitle.extend({
-
- formatRow: function (key, value) {
- if (value.indexOf('http') === 0) {
- value = '' + value + '';
- }
- return value;
- },
-
- addRow: function (container, key, value) {
- var tr = L.DomUtil.create('tr', '', container);
- L.DomUtil.add('th', '', tr, key);
- L.DomUtil.add('td', '', tr, this.formatRow(key, value));
- },
-
- renderBody: function () {
- var table = L.DomUtil.create('table');
-
- for (var key in this.feature.properties) {
- if (typeof this.feature.properties[key] === 'object' || key === 'name') continue;
- // TODO, manage links (url, mailto, wikipedia...)
- this.addRow(table, key, L.Util.escapeHTML(this.feature.properties[key]).trim());
- }
- return table;
+ formatRow(key, value) {
+ if (value.indexOf('http') === 0) {
+ value = `${value}`
}
+ return value
+ },
-});
+ addRow(container, key, value) {
+ const tr = L.DomUtil.create('tr', '', container)
+ L.DomUtil.add('th', '', tr, key)
+ L.DomUtil.add('td', '', tr, this.formatRow(key, value))
+ },
+
+ renderBody() {
+ const table = L.DomUtil.create('table')
+
+ for (const key in this.feature.properties) {
+ if (typeof this.feature.properties[key] === 'object' || key === 'name') continue
+ // TODO, manage links (url, mailto, wikipedia...)
+ this.addRow(table, key, L.Util.escapeHTML(this.feature.properties[key]).trim())
+ }
+ return table
+ },
+})
L.U.PopupTemplate.GeoRSSImage = L.U.PopupTemplate.BaseWithTitle.extend({
+ options: {
+ minWidth: 300,
+ maxWidth: 500,
+ className: 'umap-popup-large umap-georss-image',
+ },
- options: {
- minWidth: 300,
- maxWidth: 500,
- className: 'umap-popup-large umap-georss-image'
- },
-
- renderBody: function () {
- var container = L.DomUtil.create('a');
- container.href = this.feature.properties.link;
- container.target = '_blank';
- if (this.feature.properties.img) {
- var img = L.DomUtil.create('img', '', container);
- img.src = this.feature.properties.img;
- // Sadly, we are unable to override this from JS the clean way
- // See https://github.com/Leaflet/Leaflet/commit/61d746818b99d362108545c151a27f09d60960ee#commitcomment-6061847
- img.style.maxWidth = this.options.maxWidth + 'px';
- img.style.maxHeight = this.options.maxWidth + 'px';
- this.onElementLoaded(img);
- }
- return container;
+ renderBody() {
+ const container = L.DomUtil.create('a')
+ container.href = this.feature.properties.link
+ container.target = '_blank'
+ if (this.feature.properties.img) {
+ const img = L.DomUtil.create('img', '', container)
+ img.src = this.feature.properties.img
+ // Sadly, we are unable to override this from JS the clean way
+ // See https://github.com/Leaflet/Leaflet/commit/61d746818b99d362108545c151a27f09d60960ee#commitcomment-6061847
+ img.style.maxWidth = `${this.options.maxWidth}px`
+ img.style.maxHeight = `${this.options.maxWidth}px`
+ this.onElementLoaded(img)
}
-
-});
+ return container
+ },
+})
L.U.PopupTemplate.GeoRSSLink = L.U.PopupTemplate.Default.extend({
+ options: {
+ className: 'umap-georss-link',
+ },
- options: {
- className: 'umap-georss-link'
- },
-
- renderBody: function () {
- var title = this.renderTitle(this),
- a = L.DomUtil.add('a');
- a.href = this.feature.properties.link;
- a.target = '_blank';
- a.appendChild(title);
- return a;
- }
-});
+ renderBody() {
+ const title = this.renderTitle(this)
+ const a = L.DomUtil.add('a')
+ a.href = this.feature.properties.link
+ a.target = '_blank'
+ a.appendChild(title)
+ return a
+ },
+})
diff --git a/umap/static/umap/js/umap.slideshow.js b/umap/static/umap/js/umap.slideshow.js
index e9d42cd4..f945f55b 100644
--- a/umap/static/umap/js/umap.slideshow.js
+++ b/umap/static/umap/js/umap.slideshow.js
@@ -1,164 +1,165 @@
L.U.Slideshow = L.Class.extend({
+ statics: {
+ CLASSNAME: 'umap-slideshow-active',
+ },
- statics: {
- CLASSNAME: 'umap-slideshow-active'
- },
+ options: {
+ delay: 5000,
+ autoplay: false,
+ },
- options: {
- delay: 5000,
- autoplay: false
- },
+ initialize(map, options) {
+ this.setOptions(options)
+ this.map = map
+ this._id = null
- initialize: function (map, options) {
- this.setOptions(options);
- this.map = map;
- this._id = null;
- var current = null, // current feature
- self = this;
- try {
- Object.defineProperty(this, 'current', {
- get: function () {
- if (!current) {
- var datalayer = this.defaultDatalayer();
- if (datalayer) current = datalayer.getFeatureByIndex(0);
- }
- return current;
- },
- set: function (feature) {
- current = feature;
- }
- });
- }
- catch (e) {
- // Certainly IE8, which has a limited version of defineProperty
- }
- try {
- Object.defineProperty(this, 'next', {
- get: function () {
- if (!current) {
- return self.current;
- }
- return current.getNext();
- }
- });
- }
- catch (e) {
- // Certainly IE8, which has a limited version of defineProperty
- }
- if (this.options.autoplay) {
- this.map.onceDataLoaded(function () {
- this.play();
- }, this);
- }
- this.map.on('edit:enabled', function () {
- this.stop();
- }, this);
- },
+ let // current feature
+ current = null
- setOptions: function (options) {
- L.setOptions(this, options);
- this.timeSpinner();
- },
-
- defaultDatalayer: function () {
- return this.map.findDataLayer(function (d) { return d.allowBrowse() && d.hasData(); });
- },
-
- timeSpinner: function () {
- var time = parseInt(this.options.delay, 10);
- if (!time) return;
- var css = 'rotation ' + time / 1000 + 's infinite linear',
- spinners = document.querySelectorAll('.umap-slideshow-toolbox .play .spinner');
- for (var i = 0; i < spinners.length; i++) {
- spinners[i].style.animation = css;
- spinners[i].style['-webkit-animation'] = css;
- spinners[i].style['-moz-animation'] = css;
- spinners[i].style['-o-animation'] = css;
- }
- },
-
- resetSpinners: function () {
- // Make that animnation is coordinated with user actions
- var spinners = document.querySelectorAll('.umap-slideshow-toolbox .play .spinner'),
- el, newOne;
- for (var i = 0; i < spinners.length; i++) {
- el = spinners[i];
- newOne = el.cloneNode(true);
- el.parentNode.replaceChild(newOne, el);
- }
- },
-
- play: function () {
- if (this._id) return;
- if (this.map.editEnabled || !this.map.options.slideshow.active) return;
- L.DomUtil.addClass(document.body, L.U.Slideshow.CLASSNAME);
- this._id = window.setInterval(L.bind(this.loop, this), this.options.delay);
- this.resetSpinners();
- this.loop();
- },
-
- loop: function () {
- this.current = this.next;
- this.step();
- },
-
- pause: function () {
- if (this._id) {
- L.DomUtil.removeClass(document.body, L.U.Slideshow.CLASSNAME);
- window.clearInterval(this._id);
- this._id = null;
- }
- },
-
- stop: function () {
- this.pause();
- this.current = null;
- },
-
- forward: function () {
- this.pause();
- this.current = this.next;
- this.step();
- },
-
- backward: function () {
- this.pause();
- if (this.current) this.current = this.current.getPrevious();
- this.step();
- },
-
- step: function () {
- if(!this.current) return this.stop();
- this.current.zoomTo({easing: this.options.easing});
- this.current.view();
- },
-
- renderToolbox: function (container) {
- var box = L.DomUtil.create('ul', 'umap-slideshow-toolbox'),
- play = L.DomUtil.create('li', 'play', box),
- stop = L.DomUtil.create('li', 'stop', box),
- prev = L.DomUtil.create('li', 'prev', box),
- next = L.DomUtil.create('li', 'next', box);
- L.DomUtil.create('div', 'spinner', play);
- play.title = L._('Start slideshow');
- stop.title = L._('Stop slideshow');
- next.title = L._('Zoom to the next');
- prev.title = L._('Zoom to the previous');
- var toggle = function () {
- if (this._id) this.pause();
- else this.play();
- };
- L.DomEvent.on(play, 'click', L.DomEvent.stop)
- .on(play, 'click', toggle, this);
- L.DomEvent.on(stop, 'click', L.DomEvent.stop)
- .on(stop, 'click', this.stop, this);
- L.DomEvent.on(prev, 'click', L.DomEvent.stop)
- .on(prev, 'click', this.backward, this);
- L.DomEvent.on(next, 'click', L.DomEvent.stop)
- .on(next, 'click', this.forward, this);
- container.appendChild(box);
- this.timeSpinner();
- return box;
+ const self = this
+ try {
+ Object.defineProperty(this, 'current', {
+ get() {
+ if (!current) {
+ const datalayer = this.defaultDatalayer()
+ if (datalayer) current = datalayer.getFeatureByIndex(0)
+ }
+ return current
+ },
+ set(feature) {
+ current = feature
+ },
+ })
+ } catch (e) {
+ // Certainly IE8, which has a limited version of defineProperty
}
+ try {
+ Object.defineProperty(this, 'next', {
+ get() {
+ if (!current) {
+ return self.current
+ }
+ return current.getNext()
+ },
+ })
+ } catch (e) {
+ // Certainly IE8, which has a limited version of defineProperty
+ }
+ if (this.options.autoplay) {
+ this.map.onceDataLoaded(function () {
+ this.play()
+ }, this)
+ }
+ this.map.on(
+ 'edit:enabled',
+ function () {
+ this.stop()
+ },
+ this
+ )
+ },
-});
+ setOptions(options) {
+ L.setOptions(this, options)
+ this.timeSpinner()
+ },
+
+ defaultDatalayer() {
+ return this.map.findDataLayer((d) => d.allowBrowse() && d.hasData())
+ },
+
+ timeSpinner() {
+ const time = parseInt(this.options.delay, 10)
+ if (!time) return
+ const css = `rotation ${time / 1000}s infinite linear`
+ const spinners = document.querySelectorAll('.umap-slideshow-toolbox .play .spinner')
+ for (let i = 0; i < spinners.length; i++) {
+ spinners[i].style.animation = css
+ spinners[i].style['-webkit-animation'] = css
+ spinners[i].style['-moz-animation'] = css
+ spinners[i].style['-o-animation'] = css
+ }
+ },
+
+ resetSpinners() {
+ // Make that animnation is coordinated with user actions
+ const spinners = document.querySelectorAll('.umap-slideshow-toolbox .play .spinner')
+
+ let el
+ let newOne
+ for (let i = 0; i < spinners.length; i++) {
+ el = spinners[i]
+ newOne = el.cloneNode(true)
+ el.parentNode.replaceChild(newOne, el)
+ }
+ },
+
+ play() {
+ if (this._id) return
+ if (this.map.editEnabled || !this.map.options.slideshow.active) return
+ L.DomUtil.addClass(document.body, L.U.Slideshow.CLASSNAME)
+ this._id = window.setInterval(L.bind(this.loop, this), this.options.delay)
+ this.resetSpinners()
+ this.loop()
+ },
+
+ loop() {
+ this.current = this.next
+ this.step()
+ },
+
+ pause() {
+ if (this._id) {
+ L.DomUtil.removeClass(document.body, L.U.Slideshow.CLASSNAME)
+ window.clearInterval(this._id)
+ this._id = null
+ }
+ },
+
+ stop() {
+ this.pause()
+ this.current = null
+ },
+
+ forward() {
+ this.pause()
+ this.current = this.next
+ this.step()
+ },
+
+ backward() {
+ this.pause()
+ if (this.current) this.current = this.current.getPrevious()
+ this.step()
+ },
+
+ step() {
+ if (!this.current) return this.stop()
+ this.current.zoomTo({ easing: this.options.easing })
+ this.current.view()
+ },
+
+ renderToolbox(container) {
+ const box = L.DomUtil.create('ul', 'umap-slideshow-toolbox')
+ const play = L.DomUtil.create('li', 'play', box)
+ const stop = L.DomUtil.create('li', 'stop', box)
+ const prev = L.DomUtil.create('li', 'prev', box)
+ const next = L.DomUtil.create('li', 'next', box)
+ L.DomUtil.create('div', 'spinner', play)
+ play.title = L._('Start slideshow')
+ stop.title = L._('Stop slideshow')
+ next.title = L._('Zoom to the next')
+ prev.title = L._('Zoom to the previous')
+ const toggle = function () {
+ if (this._id) this.pause()
+ else this.play()
+ }
+ L.DomEvent.on(play, 'click', L.DomEvent.stop).on(play, 'click', toggle, this)
+ L.DomEvent.on(stop, 'click', L.DomEvent.stop).on(stop, 'click', this.stop, this)
+ L.DomEvent.on(prev, 'click', L.DomEvent.stop).on(prev, 'click', this.backward, this)
+ L.DomEvent.on(next, 'click', L.DomEvent.stop).on(next, 'click', this.forward, this)
+ container.appendChild(box)
+ this.timeSpinner()
+ return box
+ },
+})
diff --git a/umap/static/umap/js/umap.tableeditor.js b/umap/static/umap/js/umap.tableeditor.js
index 66aca2a5..6f33002c 100644
--- a/umap/static/umap/js/umap.tableeditor.js
+++ b/umap/static/umap/js/umap.tableeditor.js
@@ -1,107 +1,124 @@
L.U.TableEditor = L.Class.extend({
+ initialize(datalayer) {
+ this.datalayer = datalayer
+ this.table = L.DomUtil.create('div', 'table')
+ this.header = L.DomUtil.create('div', 'thead', this.table)
+ this.body = L.DomUtil.create('div', 'tbody', this.table)
+ this.resetProperties()
+ },
- initialize: function (datalayer) {
- this.datalayer = datalayer;
- this.table = L.DomUtil.create('div', 'table');
- this.header = L.DomUtil.create('div', 'thead', this.table);
- this.body = L.DomUtil.create('div', 'tbody', this.table);
- this.resetProperties();
- },
-
- renderHeaders: function () {
- this.header.innerHTML = '';
- for (var i = 0; i < this.properties.length; i++) {
- this.renderHeader(this.properties[i]);
- }
- },
-
- renderHeader: function (property) {
- var container = L.DomUtil.create('div', 'tcell', this.header),
- title = L.DomUtil.add('span', '', container, property),
- del = L.DomUtil.create('i', 'umap-delete', container),
- rename = L.DomUtil.create('i', 'umap-edit', container);
- del.title = L._('Delete this property on all the features');
- rename.title = L._('Rename this property on all the features');
- var doDelete = function () {
- if (confirm(L._('Are you sure you want to delete this property on all the features?'))) {
- this.datalayer.eachLayer(function (feature) {
- feature.deleteProperty(property);
- });
- this.datalayer.deindexProperty(property);
- this.resetProperties();
- this.edit();
- }
- };
- var doRename = function () {
- var newName = prompt(L._('Please enter the new name of this property'), property);
- if (!newName || !this.validateName(newName)) return;
- this.datalayer.eachLayer(function (feature) {
- feature.renameProperty(property, newName);
- });
- this.datalayer.deindexProperty(property);
- this.datalayer.indexProperty(newName);
- this.resetProperties();
- this.edit();
- };
- L.DomEvent.on(del, 'click', doDelete, this);
- L.DomEvent.on(rename, 'click', doRename, this);
- },
-
- renderRow: function (feature) {
- var builder = new L.U.FormBuilder(feature, this.field_properties,
- {
- id: 'umap-feature-properties_' + L.stamp(feature),
- className: 'trow',
- callback: feature.resetTooltip
- }
- );
- this.body.appendChild(builder.build());
- },
-
- compileProperties: function () {
- if (this.properties.length === 0) this.properties = ['name'];
- // description is a forced textarea, don't edit it in a text input, or you lose cariage returns
- if (this.properties.indexOf('description') !== -1) this.properties.splice(this.properties.indexOf('description'), 1);
- this.properties.sort();
- this.field_properties = [];
- for (var i = 0; i < this.properties.length; i++) {
- this.field_properties.push(['properties.' + this.properties[i], {wrapper: 'div', wrapperClass: 'tcell'}]);
- }
- },
-
- resetProperties: function () {
- this.properties = this.datalayer._propertiesIndex;
- },
-
- validateName: function (name) {
- if (name.indexOf(".") !== -1) {
- this.datalayer.map.ui.alert({content: L._('Invalide property name: {name}', {name: name}), level: 'error'});
- return false;
- }
- return true;
- },
-
- edit: function () {
- var id = 'tableeditor:edit';
- this.datalayer.map.fire('dataloading', {id: id});
- this.compileProperties();
- this.renderHeaders();
- this.body.innerHTML = '';
- this.datalayer.eachLayer(this.renderRow, this);
- var addButton = L.DomUtil.create('li', 'add-property');
- L.DomUtil.create('i', 'umap-icon-16 umap-add', addButton);
- var label = L.DomUtil.create('span', '', addButton);
- label.textContent = label.title = L._('Add a new property');
- var addProperty = function () {
- var newName = prompt(L._('Please enter the name of the property'));
- if (!newName || !this.validateName(newName)) return;
- this.datalayer.indexProperty(newName);
- this.edit();
- };
- L.DomEvent.on(addButton, 'click', addProperty, this);
- var className = (this.properties.length > 2) ? 'umap-table-editor fullwidth dark' : 'umap-table-editor dark';
- this.datalayer.map.ui.openPanel({data: {html: this.table}, className: className, actions: [addButton]});
- this.datalayer.map.fire('dataload', {id: id});
+ renderHeaders() {
+ this.header.innerHTML = ''
+ for (let i = 0; i < this.properties.length; i++) {
+ this.renderHeader(this.properties[i])
}
+ },
-});
+ renderHeader(property) {
+ const container = L.DomUtil.create('div', 'tcell', this.header)
+ const title = L.DomUtil.add('span', '', container, property)
+ const del = L.DomUtil.create('i', 'umap-delete', container)
+ const rename = L.DomUtil.create('i', 'umap-edit', container)
+ del.title = L._('Delete this property on all the features')
+ rename.title = L._('Rename this property on all the features')
+ const doDelete = function () {
+ if (
+ confirm(
+ L._('Are you sure you want to delete this property on all the features?')
+ )
+ ) {
+ this.datalayer.eachLayer((feature) => {
+ feature.deleteProperty(property)
+ })
+ this.datalayer.deindexProperty(property)
+ this.resetProperties()
+ this.edit()
+ }
+ }
+ const doRename = function () {
+ const newName = prompt(
+ L._('Please enter the new name of this property'),
+ property
+ )
+ if (!newName || !this.validateName(newName)) return
+ this.datalayer.eachLayer((feature) => {
+ feature.renameProperty(property, newName)
+ })
+ this.datalayer.deindexProperty(property)
+ this.datalayer.indexProperty(newName)
+ this.resetProperties()
+ this.edit()
+ }
+ L.DomEvent.on(del, 'click', doDelete, this)
+ L.DomEvent.on(rename, 'click', doRename, this)
+ },
+
+ renderRow(feature) {
+ const builder = new L.U.FormBuilder(feature, this.field_properties, {
+ id: `umap-feature-properties_${L.stamp(feature)}`,
+ className: 'trow',
+ callback: feature.resetTooltip,
+ })
+ this.body.appendChild(builder.build())
+ },
+
+ compileProperties() {
+ if (this.properties.length === 0) this.properties = ['name']
+ // description is a forced textarea, don't edit it in a text input, or you lose cariage returns
+ if (this.properties.includes('description'))
+ this.properties.splice(this.properties.indexOf('description'), 1)
+ this.properties.sort()
+ this.field_properties = []
+ for (let i = 0; i < this.properties.length; i++) {
+ this.field_properties.push([
+ `properties.${this.properties[i]}`,
+ { wrapper: 'div', wrapperClass: 'tcell' },
+ ])
+ }
+ },
+
+ resetProperties() {
+ this.properties = this.datalayer._propertiesIndex
+ },
+
+ validateName(name) {
+ if (name.includes('.')) {
+ this.datalayer.map.ui.alert({
+ content: L._('Invalide property name: {name}', { name }),
+ level: 'error',
+ })
+ return false
+ }
+ return true
+ },
+
+ edit() {
+ const id = 'tableeditor:edit'
+ this.datalayer.map.fire('dataloading', { id })
+ this.compileProperties()
+ this.renderHeaders()
+ this.body.innerHTML = ''
+ this.datalayer.eachLayer(this.renderRow, this)
+ const addButton = L.DomUtil.create('li', 'add-property')
+ L.DomUtil.create('i', 'umap-icon-16 umap-add', addButton)
+ const label = L.DomUtil.create('span', '', addButton)
+ label.textContent = label.title = L._('Add a new property')
+ const addProperty = function () {
+ const newName = prompt(L._('Please enter the name of the property'))
+ if (!newName || !this.validateName(newName)) return
+ this.datalayer.indexProperty(newName)
+ this.edit()
+ }
+ L.DomEvent.on(addButton, 'click', addProperty, this)
+ const className =
+ this.properties.length > 2
+ ? 'umap-table-editor fullwidth dark'
+ : 'umap-table-editor dark'
+ this.datalayer.map.ui.openPanel({
+ data: { html: this.table },
+ className,
+ actions: [addButton],
+ })
+ this.datalayer.map.fire('dataload', { id })
+ },
+})
diff --git a/umap/static/umap/js/umap.ui.js b/umap/static/umap/js/umap.ui.js
index cb78ca36..a3552e52 100644
--- a/umap/static/umap/js/umap.ui.js
+++ b/umap/static/umap/js/umap.ui.js
@@ -1,176 +1,209 @@
/*
-* Modals
-*/
+ * Modals
+ */
L.U.UI = L.Evented.extend({
+ ALERTS: Array(),
+ ALERT_ID: null,
+ TOOLTIP_ID: null,
- ALERTS: Array(),
- ALERT_ID: null,
- TOOLTIP_ID: null,
+ initialize(parent) {
+ this.parent = parent
+ this.container = L.DomUtil.create('div', 'leaflet-ui-container', this.parent)
+ L.DomEvent.disableClickPropagation(this.container)
+ L.DomEvent.on(this.container, 'contextmenu', L.DomEvent.stopPropagation) // Do not activate our custom context menu.
+ L.DomEvent.on(this.container, 'mousewheel', L.DomEvent.stopPropagation)
+ L.DomEvent.on(this.container, 'MozMousePixelScroll', L.DomEvent.stopPropagation)
+ this._panel = L.DomUtil.create('div', '', this.container)
+ this._panel.id = 'umap-ui-container'
+ this._alert = L.DomUtil.create('div', 'with-transition', this.container)
+ this._alert.id = 'umap-alert-container'
+ this._tooltip = L.DomUtil.create('div', '', this.container)
+ this._tooltip.id = 'umap-tooltip-container'
+ },
- initialize: function (parent) {
- this.parent = parent;
- this.container = L.DomUtil.create('div', 'leaflet-ui-container', this.parent);
- L.DomEvent.disableClickPropagation(this.container);
- L.DomEvent.on(this.container, 'contextmenu', L.DomEvent.stopPropagation); // Do not activate our custom context menu.
- L.DomEvent.on(this.container, 'mousewheel', L.DomEvent.stopPropagation);
- L.DomEvent.on(this.container, 'MozMousePixelScroll', L.DomEvent.stopPropagation);
- this._panel = L.DomUtil.create('div', '', this.container);
- this._panel.id = 'umap-ui-container';
- this._alert = L.DomUtil.create('div', 'with-transition', this.container);
- this._alert.id = 'umap-alert-container';
- this._tooltip = L.DomUtil.create('div', '', this.container);
- this._tooltip.id = 'umap-tooltip-container';
- },
+ resetPanelClassName() {
+ this._panel.className = 'with-transition'
+ },
- resetPanelClassName: function () {
- this._panel.className = 'with-transition';
- },
+ openPanel({ data, actions, className }) {
+ this.fire('panel:open')
+ // We reset all because we can't know which class has been added
+ // by previous ui processes...
+ this.resetPanelClassName()
+ this._panel.innerHTML = ''
+ const actionsContainer = L.DomUtil.create('ul', 'toolbox', this._panel)
+ const body = L.DomUtil.create('div', 'body', this._panel)
+ if (data.html.nodeType && data.html.nodeType === 1) body.appendChild(data.html)
+ else body.innerHTML = data.html
+ const closeLink = L.DomUtil.create('li', 'umap-close-link', actionsContainer)
+ L.DomUtil.add('i', 'umap-close-icon', closeLink)
+ const label = L.DomUtil.create('span', '', closeLink)
+ label.title = label.textContent = L._('Close')
+ if (actions) {
+ for (let i = 0; i < actions.length; i++) {
+ actionsContainer.appendChild(actions[i])
+ }
+ }
+ if (className) L.DomUtil.addClass(this._panel, className)
+ if (L.DomUtil.hasClass(this.parent, 'umap-ui')) {
+ // Already open.
+ this.fire('panel:ready')
+ } else {
+ L.DomEvent.once(
+ this._panel,
+ 'transitionend',
+ function (e) {
+ this.fire('panel:ready')
+ },
+ this
+ )
+ L.DomUtil.addClass(this.parent, 'umap-ui')
+ }
+ L.DomEvent.on(closeLink, 'click', this.closePanel, this)
+ },
- openPanel: function (e) {
- this.fire('panel:open');
- // We reset all because we can't know which class has been added
- // by previous ui processes...
- this.resetPanelClassName();
- this._panel.innerHTML = '';
- var actionsContainer = L.DomUtil.create('ul', 'toolbox', this._panel);
- var body = L.DomUtil.create('div', 'body', this._panel);
- if (e.data.html.nodeType && e.data.html.nodeType === 1) body.appendChild(e.data.html);
- else body.innerHTML = e.data.html;
- var closeLink = L.DomUtil.create('li', 'umap-close-link', actionsContainer);
- L.DomUtil.add('i', 'umap-close-icon', closeLink);
- var label = L.DomUtil.create('span', '', closeLink);
- label.title = label.textContent = L._('Close');
- if (e.actions) {
- for (var i = 0; i < e.actions.length; i++) {
- actionsContainer.appendChild(e.actions[i]);
- }
- }
- if (e.className) L.DomUtil.addClass(this._panel, e.className);
- if (L.DomUtil.hasClass(this.parent, 'umap-ui')) {
- // Already open.
- this.fire('panel:ready');
- } else {
- L.DomEvent.once(this._panel, 'transitionend', function (e) {
- this.fire('panel:ready');
- }, this);
- L.DomUtil.addClass(this.parent, 'umap-ui');
- }
- L.DomEvent.on(closeLink, 'click', this.closePanel, this);
- },
+ closePanel() {
+ this.resetPanelClassName()
+ L.DomUtil.removeClass(this.parent, 'umap-ui')
+ this.fire('panel:closed')
+ },
- closePanel: function () {
- this.resetPanelClassName();
- L.DomUtil.removeClass(this.parent, 'umap-ui');
- this.fire('panel:closed');
- },
+ alert(e) {
+ if (L.DomUtil.hasClass(this.parent, 'umap-alert')) this.ALERTS.push(e)
+ else this.popAlert(e)
+ },
- alert: function (e) {
- if (L.DomUtil.hasClass(this.parent, 'umap-alert')) this.ALERTS.push(e);
- else this.popAlert(e);
- },
+ popAlert(e) {
+ const self = this
+ if (!e) {
+ if (this.ALERTS.length) e = this.ALERTS.pop()
+ else return
+ }
+ let timeoutID
+ const level_class = e.level && e.level == 'info' ? 'info' : 'error'
+ this._alert.innerHTML = ''
+ L.DomUtil.addClass(this.parent, 'umap-alert')
+ L.DomUtil.addClass(this._alert, level_class)
+ const close = function () {
+ if (timeoutID !== this.ALERT_ID) {
+ return
+ } // Another alert has been forced
+ this._alert.innerHTML = ''
+ L.DomUtil.removeClass(this.parent, 'umap-alert')
+ L.DomUtil.removeClass(this._alert, level_class)
+ if (timeoutID) window.clearTimeout(timeoutID)
+ this.popAlert()
+ }
+ const closeLink = L.DomUtil.create('a', 'umap-close-link', this._alert)
+ closeLink.href = '#'
+ L.DomUtil.add('i', 'umap-close-icon', closeLink)
+ const label = L.DomUtil.create('span', '', closeLink)
+ label.title = label.textContent = L._('Close')
+ L.DomEvent.on(closeLink, 'click', L.DomEvent.stop).on(
+ closeLink,
+ 'click',
+ close,
+ this
+ )
+ L.DomUtil.add('div', '', this._alert, e.content)
+ if (e.actions) {
+ let action
+ let el
+ for (let i = 0; i < e.actions.length; i++) {
+ action = e.actions[i]
+ el = L.DomUtil.element('a', { className: 'umap-action' }, this._alert)
+ el.href = '#'
+ el.textContent = action.label
+ L.DomEvent.on(el, 'click', L.DomEvent.stop).on(el, 'click', close, this)
+ if (action.callback)
+ L.DomEvent.on(
+ el,
+ 'click',
+ action.callback,
+ action.callbackContext || this.map
+ )
+ }
+ }
+ self.ALERT_ID = timeoutID = window.setTimeout(
+ L.bind(close, this),
+ e.duration || 3000
+ )
+ },
- popAlert: function (e) {
- var self = this;
- if(!e) {
- if (this.ALERTS.length) e = this.ALERTS.pop();
- else return;
- }
- var timeoutID,
- level_class = e.level && e.level == 'info'? 'info': 'error';
- this._alert.innerHTML = '';
- L.DomUtil.addClass(this.parent, 'umap-alert');
- L.DomUtil.addClass(this._alert, level_class);
- var close = function () {
- if (timeoutID !== this.ALERT_ID) { return;} // Another alert has been forced
- this._alert.innerHTML = '';
- L.DomUtil.removeClass(this.parent, 'umap-alert');
- L.DomUtil.removeClass(this._alert, level_class);
- if (timeoutID) window.clearTimeout(timeoutID);
- this.popAlert();
- };
- var closeLink = L.DomUtil.create('a', 'umap-close-link', this._alert);
- closeLink.href = '#';
- L.DomUtil.add('i', 'umap-close-icon', closeLink);
- var label = L.DomUtil.create('span', '', closeLink);
- label.title = label.textContent = L._('Close');
- L.DomEvent.on(closeLink, 'click', L.DomEvent.stop)
- .on(closeLink, 'click', close, this);
- L.DomUtil.add('div', '', this._alert, e.content);
- if (e.actions) {
- var action, el;
- for (var i = 0; i < e.actions.length; i++) {
- action = e.actions[i];
- el = L.DomUtil.element('a', {'className': 'umap-action'}, this._alert);
- el.href = '#';
- el.textContent = action.label;
- L.DomEvent.on(el, 'click', L.DomEvent.stop)
- .on(el, 'click', close, this);
- if (action.callback) L.DomEvent.on(el, 'click', action.callback, action.callbackContext || this.map);
- }
- }
- self.ALERT_ID = timeoutID = window.setTimeout(L.bind(close, this), e.duration || 3000);
- },
+ tooltip({ anchor, position, content, duration }) {
+ this.TOOLTIP_ID = Math.random()
+ const id = this.TOOLTIP_ID
+ L.DomUtil.addClass(this.parent, 'umap-tooltip')
+ if (anchor && position === 'top') this.anchorTooltipTop(anchor)
+ else if (anchor && position === 'left') this.anchorTooltipLeft(anchor)
+ else this.anchorTooltipAbsolute()
+ this._tooltip.innerHTML = content
+ function closeIt() {
+ this.closeTooltip(id)
+ }
+ if (anchor) L.DomEvent.once(anchor, 'mouseout', closeIt, this)
+ if (duration !== Infinity)
+ window.setTimeout(L.bind(closeIt, this), duration || 3000)
+ },
- tooltip: function (e) {
- this.TOOLTIP_ID = Math.random();
- var id = this.TOOLTIP_ID;
- L.DomUtil.addClass(this.parent, 'umap-tooltip');
- if (e.anchor && e.position === 'top') this.anchorTooltipTop(e.anchor);
- else if (e.anchor && e.position === 'left') this.anchorTooltipLeft(e.anchor);
- else this.anchorTooltipAbsolute();
- this._tooltip.innerHTML = e.content;
- function closeIt () { this.closeTooltip(id); }
- if (e.anchor) L.DomEvent.once(e.anchor, 'mouseout', closeIt, this);
- if (e.duration !== Infinity) window.setTimeout(L.bind(closeIt, this), e.duration || 3000);
- },
+ anchorTooltipAbsolute() {
+ this._tooltip.className = ''
+ const left =
+ this.parent.offsetLeft +
+ this.parent.clientWidth / 2 -
+ this._tooltip.clientWidth / 2
+ const top = this.parent.offsetTop + 75
+ this.setTooltipPosition({ top, left })
+ },
- anchorTooltipAbsolute: function () {
- this._tooltip.className = '';
- var left = this.parent.offsetLeft + (this.parent.clientWidth / 2) - (this._tooltip.clientWidth / 2),
- top = this.parent.offsetTop + 75;
- this.setTooltipPosition({top: top, left: left});
- },
+ anchorTooltipTop(el) {
+ this._tooltip.className = 'tooltip-top'
+ const coords = this.getPosition(el)
+ this.setTooltipPosition({
+ left: coords.left - 10,
+ bottom: this.getDocHeight() - coords.top + 11,
+ })
+ },
- anchorTooltipTop: function (el) {
- this._tooltip.className = 'tooltip-top';
- var coords = this.getPosition(el);
- this.setTooltipPosition({left: coords.left - 10, bottom: this.getDocHeight() - coords.top + 11});
- },
+ anchorTooltipLeft(el) {
+ this._tooltip.className = 'tooltip-left'
+ const coords = this.getPosition(el)
+ this.setTooltipPosition({
+ top: coords.top,
+ right: document.documentElement.offsetWidth - coords.left + 11,
+ })
+ },
- anchorTooltipLeft: function (el) {
- this._tooltip.className = 'tooltip-left';
- var coords = this.getPosition(el);
- this.setTooltipPosition({top: coords.top, right: document.documentElement.offsetWidth - coords.left + 11});
- },
+ closeTooltip(id) {
+ if (id && id !== this.TOOLTIP_ID) return
+ this._tooltip.innerHTML = ''
+ L.DomUtil.removeClass(this.parent, 'umap-tooltip')
+ },
- closeTooltip: function (id) {
- if (id && id !== this.TOOLTIP_ID) return;
- this._tooltip.innerHTML = '';
- L.DomUtil.removeClass(this.parent, 'umap-tooltip');
- },
+ getPosition(el) {
+ return el.getBoundingClientRect()
+ },
- getPosition: function (el) {
- return el.getBoundingClientRect();
- },
+ setTooltipPosition({ left, right, top, bottom }) {
+ if (left) this._tooltip.style.left = `${left}px`
+ else this._tooltip.style.left = 'initial'
+ if (right) this._tooltip.style.right = `${right}px`
+ else this._tooltip.style.right = 'initial'
+ if (top) this._tooltip.style.top = `${top}px`
+ else this._tooltip.style.top = 'initial'
+ if (bottom) this._tooltip.style.bottom = `${bottom}px`
+ else this._tooltip.style.bottom = 'initial'
+ },
- setTooltipPosition: function (coords) {
- if (coords.left) this._tooltip.style.left = coords.left + 'px';
- else this._tooltip.style.left = 'initial';
- if (coords.right) this._tooltip.style.right = coords.right + 'px';
- else this._tooltip.style.right = 'initial';
- if (coords.top) this._tooltip.style.top = coords.top + 'px';
- else this._tooltip.style.top = 'initial';
- if (coords.bottom) this._tooltip.style.bottom = coords.bottom + 'px';
- else this._tooltip.style.bottom = 'initial';
- },
-
- getDocHeight: function () {
- var D = document;
- return Math.max(
- D.body.scrollHeight, D.documentElement.scrollHeight,
- D.body.offsetHeight, D.documentElement.offsetHeight,
- D.body.clientHeight, D.documentElement.clientHeight
- );
- },
-
-});
+ getDocHeight() {
+ const D = document
+ return Math.max(
+ D.body.scrollHeight,
+ D.documentElement.scrollHeight,
+ D.body.offsetHeight,
+ D.documentElement.offsetHeight,
+ D.body.clientHeight,
+ D.documentElement.clientHeight
+ )
+ },
+})
diff --git a/umap/static/umap/js/umap.xhr.js b/umap/static/umap/js/umap.xhr.js
index 126f61a9..83d9ec83 100644
--- a/umap/static/umap/js/umap.xhr.js
+++ b/umap/static/umap/js/umap.xhr.js
@@ -1,285 +1,291 @@
L.U.Xhr = L.Evented.extend({
+ initialize(ui) {
+ this.ui = ui
+ },
- initialize: function (ui) {
- this.ui = ui;
- },
-
- _wrapper: function () {
- var wrapper;
- if (window.XMLHttpRequest === undefined) {
- wrapper = function() {
- try {
- return new window.ActiveXObject('Microsoft.XMLHTTP.6.0');
- }
- catch (e1) {
- try {
- return new window.ActiveXObject('Microsoft.XMLHTTP.3.0');
- }
- catch (e2) {
- throw new Error('XMLHttpRequest is not supported');
- }
- }
- };
- }
- else {
- wrapper = window.XMLHttpRequest;
- }
- return new wrapper();
- },
-
- _ajax: function (settings) {
- var xhr = this._wrapper(), id = Math.random(), self = this;
- this.fire('dataloading', {id: id});
- var loaded = function () {self.fire('dataload', {id: id});};
-
+ _wrapper() {
+ let wrapper
+ if (window.XMLHttpRequest === undefined) {
+ wrapper = () => {
try {
- xhr.open(settings.verb, settings.uri, true);
- } catch (err) {
- // Unknown protocol?
- this.ui.alert({content: L._('Error while fetching {url}', {url: settings.uri}), level: 'error'});
- loaded();
- return
+ return new window.ActiveXObject('Microsoft.XMLHTTP.6.0')
+ } catch (e1) {
+ try {
+ return new window.ActiveXObject('Microsoft.XMLHTTP.3.0')
+ } catch (e2) {
+ throw new Error('XMLHttpRequest is not supported')
+ }
}
+ }
+ } else {
+ wrapper = window.XMLHttpRequest
+ }
+ return new wrapper()
+ },
- if (settings.uri.indexOf('http') !== 0 || settings.uri.indexOf(window.location.origin) === 0) {
- // "X-" mode headers cause the request to be in preflight mode,
- // we don"t want that by default for CORS requests
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
- }
- if (settings.headers) {
- for (var name in settings.headers) {
- xhr.setRequestHeader(name, settings.headers[name]);
- }
- }
-
-
- xhr.onreadystatechange = function() {
- if (xhr.readyState === 4) {
- if (xhr.status == 200) {
- settings.callback.call(settings.context || xhr, xhr.responseText, xhr);
- }
- else if (xhr.status === 403) {
- self.ui.alert({content: xhr.responseText || L._('Action not allowed :('), level: 'error'});
- }
- else if (xhr.status === 412) {
- var msg = L._('Woops! Someone else seems to have edited the data. You can save anyway, but this will erase the changes made by others.');
- var actions = [
- {
- label: L._('Save anyway'),
- callback: function () {
- delete settings.headers['If-Match'];
- self._ajax(settings);
- },
- callbackContext: self
- },
- {
- label: L._('Cancel')
- }
- ];
- self.ui.alert({content: msg, level: 'error', duration: 100000, actions: actions});
- }
- else {
- if (xhr.status !== 0) { // 0 === request cut by user
- self.ui.alert({'content': L._('Problem in the response'), 'level': 'error'});
- }
- }
- loaded();
- }
- };
-
- try {
- xhr.send(settings.data);
- } catch (e) {
- // Pass
- loaded();
- console.error('Bad Request', e);
- }
- },
-
- // supports only JSON as response data type
- _json: function (verb, uri, options) {
- var args = arguments,
- self = this;
- var default_options = {
- 'async': true,
- 'callback': null,
- 'responseType': 'text',
- 'data': null,
- 'listen_form': null // optional form to listen in default callback
- };
- var settings = L.Util.extend({}, default_options, options);
-
- if (verb === 'POST') {
- // find a way not to make this django specific
- var token = document.cookie.replace(/(?:(?:^|.*;\s*)csrftoken\s*\=\s*([^;]*).*$)|^.*$/, '$1');
- if (token) {
- settings.headers = settings.headers || {};
- settings.headers['X-CSRFToken'] = token;
- }
- }
-
- var callback = function(responseText, response) {
- var data;
- try {
- data = JSON.parse(responseText);
- }
- catch (err) {
- console.log(err);
- self.ui.alert({content: L._('Problem in the response format'), level: 'error'});
- return;
- }
- if (data.errors) {
- console.log(data.errors);
- self.ui.alert({content: L._('An error occured'), level: 'error'});
- } else if (data.login_required) {
- // login_required should be an URL for the login form
- if (settings.login_callback) settings.login_callback(data);
- else self.login(data, args);
- }
- else {
- if (settings.callback) L.bind(settings.callback, settings.context || this)(data, response);
- else self.default_callback(data, settings, response);
- }
- };
-
- this._ajax({
- verb: verb,
- uri: uri,
- data: settings.data,
- callback: callback,
- headers: settings.headers,
- listener: settings.listener
- });
- },
-
- get: function(uri, options) {
- this._json('GET', uri, options);
- },
-
- post: function(uri, options) {
- this._json('POST', uri, options);
- },
-
- submit_form: function(form_id, options) {
- if(typeof options === 'undefined') options = {};
- var form = L.DomUtil.get(form_id);
- var formData = new FormData(form);
- if(options.extraFormData) formData.append(options.extraFormData);
- options.data = formData;
- this.post(form.action, options);
- return false;
- },
-
- listen_form: function (form_id, options) {
- var form = L.DomUtil.get(form_id), self = this;
- if (!form) return;
- L.DomEvent
- .on(form, 'submit', L.DomEvent.stopPropagation)
- .on(form, 'submit', L.DomEvent.preventDefault)
- .on(form, 'submit', function () {
- self.submit_form(form_id, options);
- });
- },
-
- listen_link: function (link_id, options) {
- var link = L.DomUtil.get(link_id), self = this;
- if (link) {
- L.DomEvent
- .on(link, 'click', L.DomEvent.stop)
- .on(link, 'click', function () {
- if (options.confirm && !confirm(options.confirm)) { return;}
- self.get(link.href, options);
- });
- }
- },
-
- default_callback: function (data, options) {
- // default callback, to avoid boilerplate
- if (data.redirect) {
- var newPath = data.redirect;
- if (window.location.pathname == newPath) window.location.reload(); // Keep the hash, so the current view
- else window.location = newPath;
- }
- else if (data.info) {
- this.ui.alert({content: data.info, level: 'info'});
- this.ui.closePanel();
- }
- else if (data.error) {
- this.ui.alert({content: data.error, level: 'error'});
- }
- else if (data.html) {
- var ui_options = {'data': data},
- listen_options;
- if (options.className) ui_options.className = options.className;
- this.ui.openPanel(ui_options);
- // To low boilerplate, if there is a form, listen it
- if (options.listen_form) {
- // Listen form again
- listen_options = L.Util.extend({}, options, options.listen_form.options);
- this.listen_form(options.listen_form.id, listen_options);
- }
- if (options.listen_link) {
- for (var i=0, l=options.listen_link.length; i {
+ self.fire('dataload', { id })
}
-});
+ try {
+ xhr.open(settings.verb, settings.uri, true)
+ } catch (err) {
+ // Unknown protocol?
+ this.ui.alert({
+ content: L._('Error while fetching {url}', { url: settings.uri }),
+ level: 'error',
+ })
+ loaded()
+ return
+ }
+
+ if (
+ settings.uri.indexOf('http') !== 0 ||
+ settings.uri.indexOf(window.location.origin) === 0
+ ) {
+ // "X-" mode headers cause the request to be in preflight mode,
+ // we don"t want that by default for CORS requests
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
+ }
+ if (settings.headers) {
+ for (const name in settings.headers) {
+ xhr.setRequestHeader(name, settings.headers[name])
+ }
+ }
+
+ xhr.onreadystatechange = () => {
+ if (xhr.readyState === 4) {
+ if (xhr.status == 200) {
+ settings.callback.call(settings.context || xhr, xhr.responseText, xhr)
+ } else if (xhr.status === 403) {
+ self.ui.alert({
+ content: xhr.responseText || L._('Action not allowed :('),
+ level: 'error',
+ })
+ } else if (xhr.status === 412) {
+ const msg = L._(
+ 'Woops! Someone else seems to have edited the data. You can save anyway, but this will erase the changes made by others.'
+ )
+ const actions = [
+ {
+ label: L._('Save anyway'),
+ callback() {
+ delete settings.headers['If-Match']
+ self._ajax(settings)
+ },
+ callbackContext: self,
+ },
+ {
+ label: L._('Cancel'),
+ },
+ ]
+ self.ui.alert({ content: msg, level: 'error', duration: 100000, actions })
+ } else {
+ if (xhr.status !== 0) {
+ // 0 === request cut by user
+ self.ui.alert({ content: L._('Problem in the response'), level: 'error' })
+ }
+ }
+ loaded()
+ }
+ }
+
+ try {
+ xhr.send(settings.data)
+ } catch (e) {
+ // Pass
+ loaded()
+ console.error('Bad Request', e)
+ }
+ },
+
+ // supports only JSON as response data type
+ _json(verb, uri, options) {
+ const args = arguments
+ const self = this
+ const default_options = {
+ async: true,
+ callback: null,
+ responseType: 'text',
+ data: null,
+ listen_form: null, // optional form to listen in default callback
+ }
+ const settings = L.Util.extend({}, default_options, options)
+
+ if (verb === 'POST') {
+ // find a way not to make this django specific
+ const token = document.cookie.replace(
+ /(?:(?:^|.*;\s*)csrftoken\s*\=\s*([^;]*).*$)|^.*$/,
+ '$1'
+ )
+ if (token) {
+ settings.headers = settings.headers || {}
+ settings.headers['X-CSRFToken'] = token
+ }
+ }
+
+ const callback = function (responseText, response) {
+ let data
+ try {
+ data = JSON.parse(responseText)
+ } catch (err) {
+ console.log(err)
+ self.ui.alert({
+ content: L._('Problem in the response format'),
+ level: 'error',
+ })
+ return
+ }
+ if (data.errors) {
+ console.log(data.errors)
+ self.ui.alert({ content: L._('An error occured'), level: 'error' })
+ } else if (data.login_required) {
+ // login_required should be an URL for the login form
+ if (settings.login_callback) settings.login_callback(data)
+ else self.login(data, args)
+ } else {
+ if (settings.callback)
+ L.bind(settings.callback, settings.context || this)(data, response)
+ else self.default_callback(data, settings, response)
+ }
+ }
+
+ this._ajax({
+ verb,
+ uri,
+ data: settings.data,
+ callback,
+ headers: settings.headers,
+ listener: settings.listener,
+ })
+ },
+
+ get(uri, options) {
+ this._json('GET', uri, options)
+ },
+
+ post(uri, options) {
+ this._json('POST', uri, options)
+ },
+
+ submit_form(form_id, options) {
+ if (typeof options === 'undefined') options = {}
+ const form = L.DomUtil.get(form_id)
+ const formData = new FormData(form)
+ if (options.extraFormData) formData.append(options.extraFormData)
+ options.data = formData
+ this.post(form.action, options)
+ return false
+ },
+
+ listen_form(form_id, options) {
+ const form = L.DomUtil.get(form_id)
+ const self = this
+ if (!form) return
+ L.DomEvent.on(form, 'submit', L.DomEvent.stopPropagation)
+ .on(form, 'submit', L.DomEvent.preventDefault)
+ .on(form, 'submit', () => {
+ self.submit_form(form_id, options)
+ })
+ },
+
+ listen_link(link_id, options) {
+ const link = L.DomUtil.get(link_id)
+ const self = this
+ if (link) {
+ L.DomEvent.on(link, 'click', L.DomEvent.stop).on(link, 'click', () => {
+ if (options.confirm && !confirm(options.confirm)) {
+ return
+ }
+ self.get(link.href, options)
+ })
+ }
+ },
+
+ default_callback(data, options) {
+ // default callback, to avoid boilerplate
+ if (data.redirect) {
+ const newPath = data.redirect
+ if (window.location.pathname == newPath)
+ window.location.reload() // Keep the hash, so the current view
+ else window.location = newPath
+ } else if (data.info) {
+ this.ui.alert({ content: data.info, level: 'info' })
+ this.ui.closePanel()
+ } else if (data.error) {
+ this.ui.alert({ content: data.error, level: 'error' })
+ } else if (data.html) {
+ const ui_options = { data: data }
+ let listen_options
+ if (options.className) ui_options.className = options.className
+ this.ui.openPanel(ui_options)
+ // To low boilerplate, if there is a form, listen it
+ if (options.listen_form) {
+ // Listen form again
+ listen_options = L.Util.extend({}, options, options.listen_form.options)
+ this.listen_form(options.listen_form.id, listen_options)
+ }
+ if (options.listen_link) {
+ for (let i = 0, l = options.listen_link.length; i < l; i++) {
+ // Listen link again
+ listen_options = L.Util.extend({}, options, options.listen_link[i].options)
+ this.listen_link(options.listen_link[i].id, listen_options)
+ }
+ }
+ } else if (options.success) {
+ // Success is called only if data contain no msg and no html
+ options.success(data)
+ }
+ },
+
+ login(data, args) {
+ // data.html: login form
+ // args: args of the first _json call, to call again at process end
+ const self = this
+ const proceed = () => {
+ self.ui.closePanel()
+ if (typeof args !== 'undefined') self._json(...args)
+ else self.default_callback(data, {})
+ }
+ const ask_for_login = (data) => {
+ self.ui.openPanel({ data: data, className: 'login-panel' })
+ self.listen_form('login_form', {
+ callback: function (data) {
+ if (data.html) ask_for_login(data) // Problem in the login - ask again
+ else proceed()
+ },
+ })
+ // Auth links
+ const links = document.getElementsByClassName('umap-login-popup')
+ Object.keys(links).forEach((el) => {
+ const link = links[el]
+ L.DomEvent.on(link, 'click', L.DomEvent.stop).on(link, 'click', () => {
+ self.ui.closePanel()
+ const win = window.open(link.href)
+ window.umap_proceed = () => {
+ proceed()
+ win.close()
+ }
+ })
+ })
+ }
+ if (data.login_required) {
+ this.get(data.login_required, {
+ callback: function (data) {
+ ask_for_login(data)
+ },
+ })
+ } else {
+ ask_for_login(data)
+ }
+ },
+
+ logout(url) {
+ this.get(url)
+ },
+})