From 931ac7442d3fff6bd9876020fffe2fc5a9a6348b Mon Sep 17 00:00:00 2001 From: Joachim Schleicher Date: Sun, 10 Dec 2023 20:03:14 +0100 Subject: [PATCH 001/405] refactor share dialog for better usability * ordering is now short URL - download - backup - embed ordered by easier use cases for everyone to complex iframe code for web experts * show available formats as buttons instead of hiding them in a dropdown * add explaining labels * change the dark options fieldset to light gray --- umap/static/umap/base.css | 27 ++++-- umap/static/umap/img/16.svg | 4 +- umap/static/umap/img/source/16.svg | 27 +++++- umap/static/umap/js/umap.controls.js | 119 ++++++++++++++++----------- 4 files changed, 116 insertions(+), 61 deletions(-) diff --git a/umap/static/umap/base.css b/umap/static/umap/base.css index 78b7c342..b07dd8f1 100644 --- a/umap/static/umap/base.css +++ b/umap/static/umap/base.css @@ -162,14 +162,16 @@ textarea { padding: 7px; } select { + border: 1px solid #222; width: 100%; height: 28px; line-height: 28px; - color: #efefef; - border: 1px solid #222; - background-color: #393F3F; margin-top: 5px; } +.dark select { + color: #efefef; + background-color: #393F3F; +} select[multiple="multiple"] { height: auto; } @@ -225,7 +227,8 @@ button.flat, font-size: 10px; border-radius: 0 2px; } -.content .helptext { +.content .helptext, +#umap-ui-container .help-text { background-color: #eee; color: #000; } @@ -294,16 +297,19 @@ input[type="file"] + .error { text-align: left; display: block; cursor: pointer; - background-color: #232729; + background-color: #eee; height: 30px; line-height: 30px; - color: #fff; margin: 0; font-family: fira_sans; font-weight: normal; font-size: 1.2em; padding: 0 5px; } +.dark .fieldset.toggle .legend { + background-color: #232729; + color: #fff; +} .fieldset.toggle .legend:before { background-repeat: no-repeat; text-indent: 24px; @@ -311,11 +317,14 @@ input[type="file"] + .error { width: 24px; line-height: 24px; display: inline-block; - background-image: url('./img/16-white.svg'); + background-image: url('./img/16.svg'); vertical-align: bottom; content: " "; background-position: -144px -76px; } +.dark .fieldset.toggle .legend:before { + background-image: url('./img/16-white.svg'); +} .fieldset.toggle.on .legend:before { background-position: -144px -51px; } @@ -401,6 +410,7 @@ input.switch:checked ~ label:after { .umap-multiplechoice.by5 { grid-template-columns: 1fr 1fr 1fr; } +.button-bar.by4, .umap-multiplechoice.by4 { grid-template-columns: 1fr 1fr 1fr 1fr; } @@ -448,7 +458,8 @@ input.switch:checked ~ label:after { } .umap-field-iconUrl .action-button, .inheritable .define, -.inheritable .undefine { +.inheritable .undefine, +.copy-button { float: right; width: initial; min-height: 18px; diff --git a/umap/static/umap/img/16.svg b/umap/static/umap/img/16.svg index a7f38acd..3ed21c10 100644 --- a/umap/static/umap/img/16.svg +++ b/umap/static/umap/img/16.svg @@ -1,4 +1,4 @@ - + @@ -168,5 +168,7 @@ + + diff --git a/umap/static/umap/img/source/16.svg b/umap/static/umap/img/source/16.svg index f63086c6..ed2042bf 100644 --- a/umap/static/umap/img/source/16.svg +++ b/umap/static/umap/img/source/16.svg @@ -2,7 +2,7 @@ + @@ -852,5 +859,17 @@ id="path2378" style="stroke-width:0.264583" /> + + diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 157f32bf..bcdd0be4 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -1086,12 +1086,77 @@ L.U.Map.include({ const container = L.DomUtil.create('div', 'umap-share') const title = L.DomUtil.create('h3', '', container) title.textContent = L._('Share, download and embed this map') + if (this.options.shortUrl) { + L.DomUtil.createButton( + 'button copy-button', + container, + L._('copy'), + () => navigator.clipboard.writeText(this.options.shortUrl), + this + ) + L.DomUtil.add('h4', '', container, L._('Short URL')) + const shortUrlLabel = L.DomUtil.create('label', '', container) + shortUrlLabel.textContent = L._('Share this link to view the map') + 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 downloadLabel = L.DomUtil.create('label', '', container) + downloadLabel.textContent = L._('Choose the format of the data to export') + const exportCaveat = L.DomUtil.add( + 'small', + 'help-text', + container, + L._('Only visible features will be downloaded.') + ) + console.log(this.EXPORT_TYPES) + const typeInput = L.DomUtil.create( + 'div', + `button-bar by${Object.keys(this.EXPORT_TYPES).length}`, + container + ) + let option + for (const key in this.EXPORT_TYPES) { + if (this.EXPORT_TYPES.hasOwnProperty(key)) { + L.DomUtil.createButton( + 'button', + typeInput, + this.EXPORT_TYPES[key].name || key, + () => this.download(key), + this + ) + } + } + L.DomUtil.create('hr', '', container) + + L.DomUtil.add('h4', '', container, L._('Backup data')) + const backupLabel = L.DomUtil.create('label', '', container) + backupLabel.textContent = L._('Download all data and settings of the map') + const downloadUrl = L.Util.template(this.options.urls.map_download, { + map_id: this.options.umap_id, + }) + const link = L.DomUtil.createLink( + 'button', + container, + L._('Download full backup'), + downloadUrl + ) + let name = this.options.name || 'data' + name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase() + link.setAttribute('download', `${name}.umap`) + L.DomUtil.create('hr', '', container) const embedTitle = L.DomUtil.add('h4', '', container, L._('Embed the map')) const iframe = L.DomUtil.create('textarea', 'umap-share-iframe', container) const urlTitle = L.DomUtil.add('h4', '', container, L._('Direct link')) + const shortUrlLabel = L.DomUtil.create('label', '', container) + shortUrlLabel.textContent = L._( + 'Share this link to open a customized map view' + ) const exportUrl = L.DomUtil.create('input', 'umap-share-url', container) - let option exportUrl.type = 'text' const UIFields = [ ['dimensions.width', { handler: 'Input', label: L._('width') }], @@ -1132,54 +1197,12 @@ L.U.Map.include({ const builder = new L.U.FormBuilder(iframeExporter, UIFields, { callback: buildIframeCode, }) - const iframeOptions = L.DomUtil.createFieldset(container, L._('Export options')) + const iframeOptions = L.DomUtil.createFieldset( + container, + L._('Embed and link 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._('Backup data')) - const downloadUrl = L.Util.template(this.options.urls.map_download, { - map_id: this.options.umap_id, - }) - const link = L.DomUtil.createLink( - 'button', - container, - L._('Download full data'), - downloadUrl - ) - let name = this.options.name || 'data' - name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase() - link.setAttribute('download', `${name}.umap`) - 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.') - ) - for (const key in this.EXPORT_TYPES) { - if (this.EXPORT_TYPES.hasOwnProperty(key)) { - option = L.DomUtil.create('option', '', typeInput) - option.value = key - option.textContent = this.EXPORT_TYPES[key].name || key - if (this.EXPORT_TYPES[key].selected) option.selected = true - } - } - L.DomUtil.createButton( - 'button', - container, - L._('Download data'), - () => this.download(typeInput.value), - this - ) + this.ui.openPanel({ data: { html: container } }) }, }) From d19cc60a7a8e8d88152f7c98c1c206a9094f8972 Mon Sep 17 00:00:00 2001 From: Joachim Schleicher Date: Fri, 15 Dec 2023 10:16:55 +0100 Subject: [PATCH 002/405] refactor shareBox into separate class download() and format() functions as well as dialog itself and IFrameExporter helper moved into new file umap.share.js --- umap/static/umap/js/umap.controls.js | 239 +------------------- umap/static/umap/js/umap.js | 31 +-- umap/static/umap/js/umap.share.js | 263 ++++++++++++++++++++++ umap/static/umap/test/Map.Export.js | 6 +- umap/static/umap/test/index.html | 1 + umap/templates/umap/js.html | 1 + umap/tests/integration/test_export_map.py | 5 +- 7 files changed, 277 insertions(+), 269 deletions(-) create mode 100644 umap/static/umap/js/umap.share.js diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index bcdd0be4..2a6871c0 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -410,8 +410,8 @@ L.Control.Embed = L.Control.extend({ '', container, L._('Share, download and embed this map'), - map.renderShareBox, - map + map.share.open, + map.share ) L.DomEvent.on(shareButton, 'dblclick', L.DomEvent.stopPropagation) return container @@ -894,46 +894,6 @@ L.U.Map.include({ this.ui.openPanel({ data: { html: container }, actions: actions }) }, - EXPORT_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/gpx+xml', - }, - kml: { - formatter: function (map) { - return tokml(map.toGeoJSON()) - }, - ext: '.kml', - filetype: 'application/vnd.google-earth.kml+xml', - }, - csv: { - formatter: function (map) { - const table = [] - map.eachFeature((feature) => { - const row = feature.toGeoJSON()['properties'], - center = feature.getCenter() - delete row['_umap_options'] - row['Latitude'] = center.lat - row['Longitude'] = center.lng - table.push(row) - }) - return csv2geojson.dsv.csvFormat(table) - }, - ext: '.csv', - filetype: 'text/csv', - }, - }, - renderEditToolbar: function () { const container = L.DomUtil.create( 'div', @@ -1081,130 +1041,6 @@ L.U.Map.include({ this ) }, - - renderShareBox: function () { - const container = L.DomUtil.create('div', 'umap-share') - const title = L.DomUtil.create('h3', '', container) - title.textContent = L._('Share, download and embed this map') - if (this.options.shortUrl) { - L.DomUtil.createButton( - 'button copy-button', - container, - L._('copy'), - () => navigator.clipboard.writeText(this.options.shortUrl), - this - ) - L.DomUtil.add('h4', '', container, L._('Short URL')) - const shortUrlLabel = L.DomUtil.create('label', '', container) - shortUrlLabel.textContent = L._('Share this link to view the map') - 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 downloadLabel = L.DomUtil.create('label', '', container) - downloadLabel.textContent = L._('Choose the format of the data to export') - const exportCaveat = L.DomUtil.add( - 'small', - 'help-text', - container, - L._('Only visible features will be downloaded.') - ) - console.log(this.EXPORT_TYPES) - const typeInput = L.DomUtil.create( - 'div', - `button-bar by${Object.keys(this.EXPORT_TYPES).length}`, - container - ) - let option - for (const key in this.EXPORT_TYPES) { - if (this.EXPORT_TYPES.hasOwnProperty(key)) { - L.DomUtil.createButton( - 'button', - typeInput, - this.EXPORT_TYPES[key].name || key, - () => this.download(key), - this - ) - } - } - L.DomUtil.create('hr', '', container) - - L.DomUtil.add('h4', '', container, L._('Backup data')) - const backupLabel = L.DomUtil.create('label', '', container) - backupLabel.textContent = L._('Download all data and settings of the map') - const downloadUrl = L.Util.template(this.options.urls.map_download, { - map_id: this.options.umap_id, - }) - const link = L.DomUtil.createLink( - 'button', - container, - L._('Download full backup'), - downloadUrl - ) - let name = this.options.name || 'data' - name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase() - link.setAttribute('download', `${name}.umap`) - L.DomUtil.create('hr', '', container) - - const embedTitle = L.DomUtil.add('h4', '', container, L._('Embed the map')) - const iframe = L.DomUtil.create('textarea', 'umap-share-iframe', container) - const urlTitle = L.DomUtil.add('h4', '', container, L._('Direct link')) - const shortUrlLabel = L.DomUtil.create('label', '', container) - shortUrlLabel.textContent = L._( - 'Share this link to open a customized map view' - ) - const exportUrl = L.DomUtil.create('input', 'umap-share-url', container) - exportUrl.type = 'text' - 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() - exportUrl.value = window.location.protocol + iframeExporter.buildUrl() - } - buildIframeCode() - const builder = new L.U.FormBuilder(iframeExporter, UIFields, { - callback: buildIframeCode, - }) - const iframeOptions = L.DomUtil.createFieldset( - container, - L._('Embed and link options') - ) - iframeOptions.appendChild(builder.build()) - - this.ui.openPanel({ data: { html: container } }) - }, }) /* Used in view mode to define the current tilelayer */ @@ -1527,77 +1363,6 @@ L.U.ContextMenu = L.Map.ContextMenu.extend({ }, }) -L.U.IframeExporter = L.Evented.extend({ - options: { - includeFullScreenLink: true, - currentView: false, - keepCurrentDatalayers: false, - viewCurrentFeature: false, - }, - - queryString: { - scaleControl: false, - miniMap: false, - scrollWheelZoom: false, - zoomControl: true, - editMode: 'disabled', - moreControl: true, - searchControl: null, - tilelayersControl: null, - embedControl: null, - datalayersControl: true, - onLoadPanel: 'none', - captionBar: false, - captionMenus: true, - }, - - dimensions: { - width: '100%', - height: '300px', - }, - - 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: function () { - return this.map - }, - - buildUrl: function (options) { - 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 queryString = L.extend({}, this.queryString, options) - return `${this.baseUrl}?${L.Util.buildQueryString(queryString)}${currentView}` - }, - - build: function () { - const iframeUrl = this.buildUrl() - let code = `` - if (this.options.includeFullScreenLink) { - const fullUrl = this.buildUrl({ scrollWheelZoom: true }) - code += `

${L._('See full screen')}

` - } - return code - }, -}) - L.U.Editable = L.Editable.extend({ initialize: function (map, options) { L.Editable.prototype.initialize.call(this, map, options) diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index c814854e..5faf7c19 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -254,7 +254,7 @@ L.U.Map.include({ } this.initShortcuts() this.onceDataLoaded(function () { - if (L.Util.queryString('share')) this.renderShareBox() + if (L.Util.queryString('share')) this.share.open() else if (this.options.onLoadPanel === 'databrowser') this.openBrowser() else if (this.options.onLoadPanel === 'caption') this.displayCaption() else if ( @@ -347,6 +347,7 @@ L.U.Map.include({ this.browser = new L.U.Browser(this) this.importer = new L.U.Importer(this) this.drop = new L.U.DropControl(this) + this.share = new L.U.Share(this) this._controls.tilelayers = new L.U.TileLayerControl(this) this._controls.tilelayers.setLayers() @@ -846,28 +847,6 @@ L.U.Map.include({ }) }, - format: function (mode) { - const type = this.EXPORT_TYPES[mode] - const content = type.formatter(this) - let name = this.options.name || 'data' - name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase() - const filename = name + type.ext - return { content, filetype: type.filetype, filename } - }, - - download: function (mode) { - const { content, filetype, filename } = this.format(mode) - const blob = new Blob([content], { type: filetype }) - window.URL = window.URL || window.webkitURL - const el = document.createElement('a') - el.download = filename - el.href = window.URL.createObjectURL(blob) - el.style.display = 'none' - document.body.appendChild(el) - el.click() - document.body.removeChild(el) - }, - processFileToImport: function (file, layer, type) { type = type || L.Util.detectFileType(file) if (!type) { @@ -1699,9 +1678,9 @@ L.U.Map.include({ L.DomUtil.createButton( 'button umap-download', advancedButtons, - L._('Open download panel'), - this.renderShareBox, - this + L._('Open share & download panel'), + this.share.open, + this.share ) }, diff --git a/umap/static/umap/js/umap.share.js b/umap/static/umap/js/umap.share.js new file mode 100644 index 00000000..fff7bb73 --- /dev/null +++ b/umap/static/umap/js/umap.share.js @@ -0,0 +1,263 @@ +L.U.Share = L.Class.extend({ + EXPORT_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/gpx+xml', + }, + kml: { + formatter: function (map) { + return tokml(map.toGeoJSON()) + }, + ext: '.kml', + filetype: 'application/vnd.google-earth.kml+xml', + }, + csv: { + formatter: function (map) { + const table = [] + map.eachFeature((feature) => { + const row = feature.toGeoJSON()['properties'], + center = feature.getCenter() + delete row['_umap_options'] + row['Latitude'] = center.lat + row['Longitude'] = center.lng + table.push(row) + }) + return csv2geojson.dsv.csvFormat(table) + }, + ext: '.csv', + filetype: 'text/csv', + }, + }, + + initialize: function (map) { + this.map = map + }, + + build: function () { + this.container = L.DomUtil.create('div', 'umap-share') + this.title = L.DomUtil.create('h3', '', this.container) + this.title.textContent = L._('Share, download and embed this map') + if (this.map.options.shortUrl) { + L.DomUtil.createButton( + 'button copy-button', + this.container, + L._('copy'), + () => navigator.clipboard.writeText(this.map.options.shortUrl), + this + ) + L.DomUtil.add('h4', '', this.container, L._('Short URL')) + const shortUrlLabel = L.DomUtil.create('label', '', this.container) + shortUrlLabel.textContent = L._('Share this link to view the map') + const shortUrl = L.DomUtil.create('input', 'umap-short-url', this.container) + shortUrl.type = 'text' + shortUrl.value = this.map.options.shortUrl + L.DomUtil.create('hr', '', this.container) + } + + L.DomUtil.add('h4', '', this.container, L._('Download data')) + const downloadLabel = L.DomUtil.create('label', '', this.container) + downloadLabel.textContent = L._('Choose the format of the data to export') + const exportCaveat = L.DomUtil.add( + 'small', + 'help-text', + this.container, + L._('Only visible features will be downloaded.') + ) + console.log(this.EXPORT_TYPES) + const typeInput = L.DomUtil.create( + 'div', + `button-bar by${Object.keys(this.EXPORT_TYPES).length}`, + this.container + ) + let option + for (const key in this.EXPORT_TYPES) { + if (this.EXPORT_TYPES.hasOwnProperty(key)) { + L.DomUtil.createButton( + 'button', + typeInput, + this.EXPORT_TYPES[key].name || key, + () => this.download(key), + this + ) + } + } + L.DomUtil.create('hr', '', this.container) + + L.DomUtil.add('h4', '', this.container, L._('Backup data')) + const backupLabel = L.DomUtil.create('label', '', this.container) + backupLabel.textContent = L._('Download all data and properties of the map') + const downloadUrl = L.Util.template(this.map.options.urls.map_download, { + map_id: this.map.options.umap_id, + }) + const link = L.DomUtil.createLink( + 'button', + this.container, + L._('Download full backup'), + downloadUrl + ) + let name = this.map.options.name || 'data' + name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase() + link.setAttribute('download', `${name}.umap`) + L.DomUtil.create('hr', '', this.container) + + const embedTitle = L.DomUtil.add('h4', '', this.container, L._('Embed the map')) + const iframe = L.DomUtil.create('textarea', 'umap-share-iframe', this.container) + const urlTitle = L.DomUtil.add('h4', '', this.container, L._('Direct link')) + const shortUrlLabel = L.DomUtil.create('label', '', this.container) + shortUrlLabel.textContent = L._('Share this link to open a customized map view') + const exportUrl = L.DomUtil.create('input', 'umap-share-url', this.container) + exportUrl.type = 'text' + 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.map.HIDDABLE_CONTROLS.length; i++) { + UIFields.push(`queryString.${this.map.HIDDABLE_CONTROLS[i]}Control`) + } + const iframeExporter = new L.U.IframeExporter(this.map) + const buildIframeCode = () => { + iframe.innerHTML = iframeExporter.build() + exportUrl.value = window.location.protocol + iframeExporter.buildUrl() + } + buildIframeCode() + const builder = new L.U.FormBuilder(iframeExporter, UIFields, { + callback: buildIframeCode, + }) + const iframeOptions = L.DomUtil.createFieldset( + this.container, + L._('Embed and link options') + ) + iframeOptions.appendChild(builder.build()) + }, + + open: function () { + if (!this.container) this.build() + this.map.ui.openPanel({ data: { html: this.container } }) + }, + + format: function (mode) { + const type = this.EXPORT_TYPES[mode] + const content = type.formatter(this.map) + let name = this.map.options.name || 'data' + name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase() + const filename = name + type.ext + return { content, filetype: type.filetype, filename } + }, + + download: function (mode) { + const { content, filetype, filename } = this.format(mode) + const blob = new Blob([content], { type: filetype }) + window.URL = window.URL || window.webkitURL + const el = document.createElement('a') + el.download = filename + el.href = window.URL.createObjectURL(blob) + el.style.display = 'none' + document.body.appendChild(el) + el.click() + document.body.removeChild(el) + }, +}) + +L.U.IframeExporter = L.Evented.extend({ + options: { + includeFullScreenLink: true, + currentView: false, + keepCurrentDatalayers: false, + viewCurrentFeature: false, + }, + + queryString: { + scaleControl: false, + miniMap: false, + scrollWheelZoom: false, + zoomControl: true, + editMode: 'disabled', + moreControl: true, + searchControl: null, + tilelayersControl: null, + embedControl: null, + datalayersControl: true, + onLoadPanel: 'none', + captionBar: false, + captionMenus: true, + }, + + dimensions: { + width: '100%', + height: '300px', + }, + + 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: function () { + return this.map + }, + + buildUrl: function (options) { + 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 queryString = L.extend({}, this.queryString, options) + return `${this.baseUrl}?${L.Util.buildQueryString(queryString)}${currentView}` + }, + + build: function () { + const iframeUrl = this.buildUrl() + let code = `` + if (this.options.includeFullScreenLink) { + const fullUrl = this.buildUrl({ scrollWheelZoom: true }) + code += `

${L._('See full screen')}

` + } + return code + }, +}) diff --git a/umap/static/umap/test/Map.Export.js b/umap/static/umap/test/Map.Export.js index b0cda485..8983c364 100644 --- a/umap/static/umap/test/Map.Export.js +++ b/umap/static/umap/test/Map.Export.js @@ -20,7 +20,7 @@ describe('L.U.Map.Export', function () { describe('#formatters()', function () { it('should export to geojson', function () { - const { content, filetype, filename } = this.map.format('geojson') + const { content, filetype, filename } = this.map.share.format('geojson') assert.equal(filetype, 'application/json') assert.equal(filename, 'name_of_the_map.geojson') assert.deepEqual(JSON.parse(content), { @@ -86,7 +86,7 @@ describe('L.U.Map.Export', function () { }) it('should export to gpx', function () { - const { content, filetype, filename } = this.map.format('gpx') + const { content, filetype, filename } = this.map.share.format('gpx') assert.equal(filetype, 'application/gpx+xml') assert.equal(filename, 'name_of_the_map.gpx') const expected = @@ -95,7 +95,7 @@ describe('L.U.Map.Export', function () { }) it('should export to kml', function () { - const { content, filetype, filename } = this.map.format('kml') + const { content, filetype, filename } = this.map.share.format('kml') assert.equal(filetype, 'application/vnd.google-earth.kml+xml') assert.equal(filename, 'name_of_the_map.kml') const expected = diff --git a/umap/static/umap/test/index.html b/umap/static/umap/test/index.html index bc371d16..328c5262 100644 --- a/umap/static/umap/test/index.html +++ b/umap/static/umap/test/index.html @@ -43,6 +43,7 @@ + diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html index 131c1a02..066def43 100644 --- a/umap/templates/umap/js.html +++ b/umap/templates/umap/js.html @@ -45,6 +45,7 @@ + {% endcompress %} diff --git a/umap/tests/integration/test_export_map.py b/umap/tests/integration/test_export_map.py index 28b215a1..5007232c 100644 --- a/umap/tests/integration/test_export_map.py +++ b/umap/tests/integration/test_export_map.py @@ -9,7 +9,7 @@ pytestmark = pytest.mark.django_db def test_umap_export(map, live_server, datalayer, page): page.goto(f"{live_server.url}{map.get_absolute_url()}?share") - link = page.get_by_role("link", name="Download full data") + link = page.get_by_role("link", name="Download full backup") expect(link).to_be_visible() with page.expect_download() as download_info: link.click() @@ -73,9 +73,8 @@ def test_umap_export(map, live_server, datalayer, page): def test_csv_export(map, live_server, datalayer, page): page.goto(f"{live_server.url}{map.get_absolute_url()}?share") - button = page.get_by_role("button", name="Download data") + button = page.get_by_role("button", name="csv") expect(button).to_be_visible() - page.locator('select[name="format"]').select_option("csv") with page.expect_download() as download_info: button.click() download = download_info.value From aec4330df6ec77295ab1db45bd5f5d038a26b92e Mon Sep 17 00:00:00 2001 From: Joachim Schleicher Date: Tue, 19 Dec 2023 16:40:31 +0100 Subject: [PATCH 003/405] add download icons file icon CC0 from https://www.iconfinder.com/icons/9110902/file_download_icon arrow down drawn in inkscape --- umap/static/umap/img/16.svg | 8 ++++++ umap/static/umap/img/source/16.svg | 43 +++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/umap/static/umap/img/16.svg b/umap/static/umap/img/16.svg index 3ed21c10..1db810b6 100644 --- a/umap/static/umap/img/16.svg +++ b/umap/static/umap/img/16.svg @@ -1,5 +1,8 @@ + + + @@ -170,5 +173,10 @@ + + + + + diff --git a/umap/static/umap/img/source/16.svg b/umap/static/umap/img/source/16.svg index ed2042bf..99554023 100644 --- a/umap/static/umap/img/source/16.svg +++ b/umap/static/umap/img/source/16.svg @@ -20,6 +20,26 @@ xmlns:dc="http://purl.org/dc/elements/1.1/"> + + + + + + + + From 3f6c705d2e85c7271a8c592a833e1242d22e7cab Mon Sep 17 00:00:00 2001 From: Joachim Schleicher Date: Tue, 19 Dec 2023 17:27:27 +0100 Subject: [PATCH 004/405] re-style download panel according to suggestion by @Aurelie-Jallut in https://github.com/umap-project/umap/pull/1454#issuecomment-1858062371 --- umap/static/umap/js/umap.controls.js | 2 +- umap/static/umap/js/umap.share.js | 64 ++++++++++++++-------------- umap/static/umap/map.css | 33 ++++++++++++++ 3 files changed, 65 insertions(+), 34 deletions(-) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 2a6871c0..0af058ce 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -409,7 +409,7 @@ L.Control.Embed = L.Control.extend({ const shareButton = L.DomUtil.createButton( '', container, - L._('Share, download and embed this map'), + L._('Share and download'), map.share.open, map.share ) diff --git a/umap/static/umap/js/umap.share.js b/umap/static/umap/js/umap.share.js index fff7bb73..7a397a49 100644 --- a/umap/static/umap/js/umap.share.js +++ b/umap/static/umap/js/umap.share.js @@ -46,61 +46,59 @@ L.U.Share = L.Class.extend({ build: function () { this.container = L.DomUtil.create('div', 'umap-share') this.title = L.DomUtil.create('h3', '', this.container) - this.title.textContent = L._('Share, download and embed this map') + this.title.textContent = L._('Share and download') + + L.DomUtil.createButton( + 'button copy-button', + this.container, + L._('copy'), + () => navigator.clipboard.writeText(this.mapUrl.value), + this + ) + const mapUrlLabel = L.DomUtil.add('label', '', this.container, L._('Link to view the map')) + this.mapUrl = L.DomUtil.create('input', 'umap-share-url', mapUrlLabel) + this.mapUrl.type = 'text' + this.mapUrl.readOnly = true + this.mapUrl.value = window.location.protocol + L.Util.getBaseUrl() + if (this.map.options.shortUrl) { L.DomUtil.createButton( 'button copy-button', this.container, L._('copy'), - () => navigator.clipboard.writeText(this.map.options.shortUrl), + () => navigator.clipboard.writeText(this.shortUrl.value), this ) - L.DomUtil.add('h4', '', this.container, L._('Short URL')) const shortUrlLabel = L.DomUtil.create('label', '', this.container) - shortUrlLabel.textContent = L._('Share this link to view the map') - const shortUrl = L.DomUtil.create('input', 'umap-short-url', this.container) - shortUrl.type = 'text' - shortUrl.value = this.map.options.shortUrl - L.DomUtil.create('hr', '', this.container) + shortUrlLabel.textContent = L._('Short link') + this.shortUrl = L.DomUtil.create('input', 'umap-share-url', shortUrlLabel) + this.shortUrl.type = 'text' + this.shortUrl.readOnly = true + this.shortUrl.value = this.map.options.shortUrl } - L.DomUtil.add('h4', '', this.container, L._('Download data')) - const downloadLabel = L.DomUtil.create('label', '', this.container) - downloadLabel.textContent = L._('Choose the format of the data to export') - const exportCaveat = L.DomUtil.add( - 'small', - 'help-text', - this.container, - L._('Only visible features will be downloaded.') - ) - console.log(this.EXPORT_TYPES) - const typeInput = L.DomUtil.create( - 'div', - `button-bar by${Object.keys(this.EXPORT_TYPES).length}`, - this.container - ) - let option + L.DomUtil.create('hr', '', this.container) + + L.DomUtil.add('h4', '', this.container, L._('Download')) + L.DomUtil.add('small', 'label', this.container, L._('Only visible layers')) for (const key in this.EXPORT_TYPES) { if (this.EXPORT_TYPES.hasOwnProperty(key)) { L.DomUtil.createButton( - 'button', - typeInput, - this.EXPORT_TYPES[key].name || key, + 'download-file', + this.container, + this.EXPORT_TYPES[key].name || key + ' data', () => this.download(key), this ) } } - L.DomUtil.create('hr', '', this.container) - - L.DomUtil.add('h4', '', this.container, L._('Backup data')) - const backupLabel = L.DomUtil.create('label', '', this.container) - backupLabel.textContent = L._('Download all data and properties of the map') + L.DomUtil.create('div', 'vspace', this.container) + L.DomUtil.add('small', 'label', this.container, L._('All data and settings of the map')) const downloadUrl = L.Util.template(this.map.options.urls.map_download, { map_id: this.map.options.umap_id, }) const link = L.DomUtil.createLink( - 'button', + 'download-backup', this.container, L._('Download full backup'), downloadUrl diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index ccd5ed48..5095a73b 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -239,6 +239,39 @@ ul.photon-autocomplete { +/* ***************************** */ +/* Share and download panel */ +/* ***************************** */ +.download-file { + height: 1.2em; + min-height: 1.2em; + padding: 0; + text-align: left; + vertical-align: bottom; +} +.download-file:before, +.download-backup:before { + height: 24px; + width: 24px; + background-repeat: no-repeat; + background-image: url('./img/16.svg'); + background-size: auto auto; + background-position: -4px -145px; + content: " "; + vertical-align: bottom; + display: inline-block; +} +.download-backup:before { + background-position: -27px -144px; +} +.leaflet-container .download-backup { + color: black; + display: block; +} +.vspace { + height: 32px; +} + /* *********** */ /* Draw */ /* *********** */ From cab87cd59f0282e90b88661e2e63e930d6cb599d Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 20 Dec 2023 11:45:00 +0100 Subject: [PATCH 005/405] Deal with data:image in icon image form --- umap/static/umap/js/umap.core.js | 12 ++++++++++++ umap/static/umap/js/umap.forms.js | 24 ++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js index e3ce8f02..2cf791ba 100644 --- a/umap/static/umap/js/umap.core.js +++ b/umap/static/umap/js/umap.core.js @@ -260,6 +260,18 @@ L.Util.hasVar = (value) => { return typeof value === 'string' && value.indexOf('{') != -1 } +L.Util.isPath = function (value) { + return value && value.length && value.startsWith('/') +} + +L.Util.isRemoteUrl = function (value) { + return value && value.length && value.startsWith('http') +} + +L.Util.isDataImage = function (value) { + return value && value.length && value.startsWith('data:image') +} + L.Util.copyToClipboard = function (textToCopy) { // https://stackoverflow.com/a/65996386 // Navigator clipboard api needs a secure context (https) diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index fb7a41c1..b3970973 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -538,8 +538,8 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({ this.footer.innerHTML = '' this.buildTabs() const value = this.value() - if (!value || value.startsWith('/')) this.showSymbolsTab() - else if (value.startsWith('http')) this.showURLTab() + if (!value || L.Util.isPath(value)) this.showSymbolsTab() + else if (L.Util.isRemoteUrl(value) || L.Util.isDataImage(value)) this.showURLTab() else this.showCharsTab() const closeButton = L.DomUtil.createButton( 'button action-button', @@ -596,18 +596,11 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({ this.body.innerHTML = '' }, - isPath: function () { - const value = this.value() - return value && value.length && value.startsWith('/') - }, - - isRemoteUrl: function () { - const value = this.value() - return value && value.length && value.startsWith('http') - }, - isImg: function () { - return this.isPath() || this.isRemoteUrl() + const value = this.value() + return ( + L.Util.isPath(value) || L.Util.isRemoteUrl(value) || L.Util.isDataImage(value) + ) }, updatePreview: function () { @@ -731,7 +724,10 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({ showURLTab: function () { this.openTab('url') - const value = this.isRemoteUrl() ? this.value() : null + const value = + L.Util.isRemoteUrl(this.value()) || L.Util.isDataImage(this.value()) + ? this.value() + : null const input = this.buildInput(this.body, value) input.placeholder = L._('Add image URL') input.type = 'url' From 2208a6c67dc1c32b05e180f3dfbf67f8dc396610 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 30 Jun 2023 16:26:50 +0200 Subject: [PATCH 006/405] WIP: OpenStreetMap dedicated popup template --- umap/static/umap/js/umap.forms.js | 1 + umap/static/umap/js/umap.popup.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index b3970973..1aabfec5 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -372,6 +372,7 @@ L.FormBuilder.PopupContent = L.FormBuilder.Select.extend({ ['Table', L._('Table')], ['GeoRSSImage', L._('GeoRSS (title + image)')], ['GeoRSSLink', L._('GeoRSS (only link)')], + ['OSM', L._('OpenStreetMap')], ], }) diff --git a/umap/static/umap/js/umap.popup.js b/umap/static/umap/js/umap.popup.js index 6bccceed..e6b1d50f 100644 --- a/umap/static/umap/js/umap.popup.js +++ b/umap/static/umap/js/umap.popup.js @@ -251,3 +251,24 @@ L.U.PopupTemplate.GeoRSSLink = L.U.PopupTemplate.Default.extend({ return a }, }) + +L.U.PopupTemplate.OSM = L.U.PopupTemplate.Default.extend({ + options: { + className: 'umap-openstreetmap', + }, + + getName: function () { + const props = this.feature.properties + if (L.locale && props[`name:${L.locale}`]) return props[`name:${L.locale}`]; + return props.name + }, + + renderBody: function () { + const props = this.feature.properties + let kind = props.shop || props.amenity || props.craft + const container = L.DomUtil.add('div') + const kindEl = L.DomUtil.add('h4', '', container, kind) + L.DomUtil.add('h3', 'popup-title', container, this.getName()) + return container + }, +}) From 48ab865b9a6a31b479435692eaec91c00fa093d3 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 20 Dec 2023 12:14:02 +0100 Subject: [PATCH 007/405] More work on OpenStreetMap popup template --- umap/static/umap/js/umap.core.js | 4 +-- umap/static/umap/js/umap.popup.js | 54 ++++++++++++++++++++++++++++--- umap/static/umap/map.css | 25 ++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js index 2cf791ba..cff2c619 100644 --- a/umap/static/umap/js/umap.core.js +++ b/umap/static/umap/js/umap.core.js @@ -383,8 +383,8 @@ L.DomUtil.after = (target, el) => { } L.DomUtil.RGBRegex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/ -L.DomUtil.TextColorFromBackgroundColor = (el) => { - return L.DomUtil.contrastedColor(el) ? '#ffffff' : '#000000' +L.DomUtil.TextColorFromBackgroundColor = (el, bgcolor) => { + return L.DomUtil.contrastedColor(el, bgcolor) ? '#ffffff' : '#000000' } const _CACHE_CONSTRAST = {} L.DomUtil.contrastedColor = (el, bgcolor) => { diff --git a/umap/static/umap/js/umap.popup.js b/umap/static/umap/js/umap.popup.js index e6b1d50f..f4289fde 100644 --- a/umap/static/umap/js/umap.popup.js +++ b/umap/static/umap/js/umap.popup.js @@ -259,16 +259,62 @@ L.U.PopupTemplate.OSM = L.U.PopupTemplate.Default.extend({ getName: function () { const props = this.feature.properties - if (L.locale && props[`name:${L.locale}`]) return props[`name:${L.locale}`]; + if (L.locale && props[`name:${L.locale}`]) return props[`name:${L.locale}`] return props.name }, renderBody: function () { const props = this.feature.properties - let kind = props.shop || props.amenity || props.craft const container = L.DomUtil.add('div') - const kindEl = L.DomUtil.add('h4', '', container, kind) - L.DomUtil.add('h3', 'popup-title', container, this.getName()) + const title = L.DomUtil.add('h3', 'popup-title', container) + const color = this.feature.getDynamicOption('color') + title.style.backgroundColor = color + const iconUrl = this.feature.getDynamicOption('iconUrl') + let img + if (L.Util.isPath(iconUrl) || L.Util.isRemoteUrl(iconUrl) || L.Util.isDataImage(iconUrl)) { + img = L.DomUtil.add('img', 'popup-icon', title) + img.src = iconUrl + } else { + img = L.DomUtil.add('span', 'popup-icon', title) + img.textContent = iconUrl + } + if (L.DomUtil.contrastedColor(title, color)) { + if (L.Util.isPath(iconUrl) && iconUrl.endsWith('.svg')) { + img.style.filter = 'invert(1)' + } + title.style.color = 'white' + } + L.DomUtil.add('span', '', title, this.getName()) + const street = props['addr:street'] + if (street) { + const row = L.DomUtil.add('address', 'address', container) + const number = props['addr:housenumber'] + if (number) { + // Poor way to deal with international forms of writting addresses + L.DomUtil.add('span', '', row, `${L._("No.")}: ${number}`) + L.DomUtil.add('span', '', row, `${L._("Street")}: ${street}`) + } else { + L.DomUtil.add('span', '', row, street) + } + } + if (props.website) { + L.DomUtil.element('a', {href: props.website, textContent: props.website}, container) + } + const phone = props.phone || props['contact:phone'] + if (phone) { + L.DomUtil.add('div', '', container, L.DomUtil.element('a', {href: `tel:${phone}`, textContent: phone})) + } + if (props.mobile) { + L.DomUtil.add('div', '', container, L.DomUtil.element('a', {href: `tel:${props.mobile}`, textContent: props.mobile})) + } + const email = props.email || props['contact:email'] + if (email) { + L.DomUtil.add('div', '', container, L.DomUtil.element('a', {href: `mailto:${email}`, textContent: email})) + } + const id = props['@id'] + if (id) { + L.DomUtil.add('div', 'osm-link', container, L.DomUtil.element('a', {href: `https://www.openstreetmap.org/${id}`, textContent: L._('See on OpenStreetMap')})) + } return container }, }) diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index ccd5ed48..17f7a815 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -1266,6 +1266,31 @@ a.add-datalayer:hover, .umap-popup a:hover { text-decoration: underline; } +.popup-title { + display: flex; + align-items: center; +} +.popup-title img { + margin: 0 5px; +} +a[href^='mailto']::before { + content: '🖃 '; +} +a[href^='tel']::before { + content: '🕿 '; +} +address span { + padding-right: 5px; +} +.osm-link { + margin-top: 10px; + text-align: right; + font-style: italic; +} +span.popup-icon { + padding: 5px; +} + /* ************* */ /* Marker's Icon */ From 554352e003fe84d76362f42685c04da9a870e770 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Thu, 21 Dec 2023 12:39:51 +0100 Subject: [PATCH 008/405] Allow to type a latlng in the search box fix #1000 cf #1001 --- umap/static/umap/js/umap.controls.js | 38 +++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 157f32bf..ee76e440 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -1210,7 +1210,6 @@ L.U.TileLayerControl = L.Control.IconLayers.extend({ L.Control.IconLayers.prototype.setLayers.call(this, layers.slice(0, maxShown)) if (this.map.selected_tilelayer) this.setActiveLayer(this.map.selected_tilelayer) }, - }) /* Used in edit mode to define the default tilelayer */ @@ -1357,6 +1356,43 @@ L.U.Search = L.PhotonSearch.extend({ L.PhotonSearch.prototype.initialize.call(this, map, input, options) this.options.url = map.options.urls.search if (map.options.maxBounds) this.options.bbox = map.options.maxBounds.toBBoxString() + this.reverse = new L.PhotonReverse({ + handleResults: (geojson) => { + this.handleResultsWithReverse(geojson) + }, + }) + }, + + handleResultsWithReverse: function (geojson) { + const latlng = this.reverse.latlng + geojson.features.unshift({ + type: 'Feature', + geometry: { type: 'Point', coordinates: [latlng.lng, latlng.lat] }, + properties: { + name: L._('Go to "{coords}"', { coords: `${latlng.lat} ${latlng.lng}` }), + }, + }) + + this.handleResults(geojson) + }, + + search: function () { + const pattern = /^(?[-+]?\d{1,2}[.,]\d+)\s*[ ,]\s*(?[-+]?\d{1,3}[.,]\d+)$/ + if (pattern.test(this.input.value)) { + this.hide() + const { lat, lng } = pattern.exec(this.input.value).groups + const latlng = L.latLng(lat, lng) + if (latlng.isValid()) { + this.reverse.doReverse(latlng) + } else { + this.map.ui.alert({ content: 'Invalid latitude or longitude', mode: 'error' }) + } + return + } + // Only numbers, abort. + if (/^[\d .,]*$/.test(this.input.value)) return + // Do normal search + L.PhotonSearch.prototype.search.call(this) }, onBlur: function (e) { From 70ae3e4bd8e1f7b886fd8e3e4eb7530ac524c8b7 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 22 Dec 2023 11:13:03 +0100 Subject: [PATCH 009/405] Upgrade leaflet.photon to 0.9.0 This release fixes the API domain --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 47608090..aca9d953 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "leaflet.locatecontrol": "^0.79.0", "leaflet.markercluster": "^1.5.3", "leaflet.path.drag": "0.0.6", - "leaflet.photon": "0.8.0", + "leaflet.photon": "0.9.0", "osmtogeojson": "^3.0.0-beta.3", "simple-statistics": "^7.8.3", "togpx": "^0.5.4", @@ -1465,9 +1465,9 @@ "integrity": "sha1-bZw68LnXsDJUSuFr/eaI8BYFFKA=" }, "node_modules/leaflet.photon": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/leaflet.photon/-/leaflet.photon-0.8.0.tgz", - "integrity": "sha512-uvCPocNvRJaArW8yPm6K4bkgvoMCbCOA9tgFlQfOVw+mRmN4jbkuEFoSP0nJhj8UKIOWsRtGfOjxtzaJyZuciw==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/leaflet.photon/-/leaflet.photon-0.9.0.tgz", + "integrity": "sha512-qLZzYqBAHvOGgf/ymj3zdknlR1zfVZTPSF02wfES3tWR0iZA48HaxpFTZ0ZLflHxsDuQf6Hl+03xwBAHHQTslA==" }, "node_modules/lebab": { "version": "3.2.1", @@ -3674,9 +3674,9 @@ "integrity": "sha1-bZw68LnXsDJUSuFr/eaI8BYFFKA=" }, "leaflet.photon": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/leaflet.photon/-/leaflet.photon-0.8.0.tgz", - "integrity": "sha512-uvCPocNvRJaArW8yPm6K4bkgvoMCbCOA9tgFlQfOVw+mRmN4jbkuEFoSP0nJhj8UKIOWsRtGfOjxtzaJyZuciw==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/leaflet.photon/-/leaflet.photon-0.9.0.tgz", + "integrity": "sha512-qLZzYqBAHvOGgf/ymj3zdknlR1zfVZTPSF02wfES3tWR0iZA48HaxpFTZ0ZLflHxsDuQf6Hl+03xwBAHHQTslA==" }, "lebab": { "version": "3.2.1", diff --git a/package.json b/package.json index bad0c918..7d9b551e 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "leaflet.locatecontrol": "^0.79.0", "leaflet.markercluster": "^1.5.3", "leaflet.path.drag": "0.0.6", - "leaflet.photon": "0.8.0", + "leaflet.photon": "0.9.0", "osmtogeojson": "^3.0.0-beta.3", "simple-statistics": "^7.8.3", "togpx": "^0.5.4", From 50e8161b8959c8eff09d283f0a89b8a38274103b Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 26 Dec 2023 12:08:20 +0100 Subject: [PATCH 010/405] Rename var in popup.js --- umap/static/umap/js/umap.popup.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/umap/static/umap/js/umap.popup.js b/umap/static/umap/js/umap.popup.js index f4289fde..c30c03a9 100644 --- a/umap/static/umap/js/umap.popup.js +++ b/umap/static/umap/js/umap.popup.js @@ -270,17 +270,17 @@ L.U.PopupTemplate.OSM = L.U.PopupTemplate.Default.extend({ const color = this.feature.getDynamicOption('color') title.style.backgroundColor = color const iconUrl = this.feature.getDynamicOption('iconUrl') - let img + let icon if (L.Util.isPath(iconUrl) || L.Util.isRemoteUrl(iconUrl) || L.Util.isDataImage(iconUrl)) { - img = L.DomUtil.add('img', 'popup-icon', title) - img.src = iconUrl + icon = L.DomUtil.add('img', 'popup-icon', title) + icon.src = iconUrl } else { - img = L.DomUtil.add('span', 'popup-icon', title) - img.textContent = iconUrl + icon = L.DomUtil.add('span', 'popup-icon', title) + icon.textContent = iconUrl } if (L.DomUtil.contrastedColor(title, color)) { if (L.Util.isPath(iconUrl) && iconUrl.endsWith('.svg')) { - img.style.filter = 'invert(1)' + icon.style.filter = 'invert(1)' } title.style.color = 'white' } From a4dbb695451b1e40dfdcd59e2ac343bc9366cc40 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 26 Dec 2023 12:09:27 +0100 Subject: [PATCH 011/405] Prettier on popup.js --- umap/static/umap/js/umap.popup.js | 50 ++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/umap/static/umap/js/umap.popup.js b/umap/static/umap/js/umap.popup.js index c30c03a9..0233b77a 100644 --- a/umap/static/umap/js/umap.popup.js +++ b/umap/static/umap/js/umap.popup.js @@ -271,7 +271,11 @@ L.U.PopupTemplate.OSM = L.U.PopupTemplate.Default.extend({ title.style.backgroundColor = color const iconUrl = this.feature.getDynamicOption('iconUrl') let icon - if (L.Util.isPath(iconUrl) || L.Util.isRemoteUrl(iconUrl) || L.Util.isDataImage(iconUrl)) { + if ( + L.Util.isPath(iconUrl) || + L.Util.isRemoteUrl(iconUrl) || + L.Util.isDataImage(iconUrl) + ) { icon = L.DomUtil.add('img', 'popup-icon', title) icon.src = iconUrl } else { @@ -291,29 +295,59 @@ L.U.PopupTemplate.OSM = L.U.PopupTemplate.Default.extend({ const number = props['addr:housenumber'] if (number) { // Poor way to deal with international forms of writting addresses - L.DomUtil.add('span', '', row, `${L._("No.")}: ${number}`) - L.DomUtil.add('span', '', row, `${L._("Street")}: ${street}`) + L.DomUtil.add('span', '', row, `${L._('No.')}: ${number}`) + L.DomUtil.add('span', '', row, `${L._('Street')}: ${street}`) } else { L.DomUtil.add('span', '', row, street) } } if (props.website) { - L.DomUtil.element('a', {href: props.website, textContent: props.website}, container) + L.DomUtil.element( + 'a', + { href: props.website, textContent: props.website }, + container + ) } const phone = props.phone || props['contact:phone'] if (phone) { - L.DomUtil.add('div', '', container, L.DomUtil.element('a', {href: `tel:${phone}`, textContent: phone})) + L.DomUtil.add( + 'div', + '', + container, + L.DomUtil.element('a', { href: `tel:${phone}`, textContent: phone }) + ) } if (props.mobile) { - L.DomUtil.add('div', '', container, L.DomUtil.element('a', {href: `tel:${props.mobile}`, textContent: props.mobile})) + L.DomUtil.add( + 'div', + '', + container, + L.DomUtil.element('a', { + href: `tel:${props.mobile}`, + textContent: props.mobile, + }) + ) } const email = props.email || props['contact:email'] if (email) { - L.DomUtil.add('div', '', container, L.DomUtil.element('a', {href: `mailto:${email}`, textContent: email})) + L.DomUtil.add( + 'div', + '', + container, + L.DomUtil.element('a', { href: `mailto:${email}`, textContent: email }) + ) } const id = props['@id'] if (id) { - L.DomUtil.add('div', 'osm-link', container, L.DomUtil.element('a', {href: `https://www.openstreetmap.org/${id}`, textContent: L._('See on OpenStreetMap')})) + L.DomUtil.add( + 'div', + 'osm-link', + container, + L.DomUtil.element('a', { + href: `https://www.openstreetmap.org/${id}`, + textContent: L._('See on OpenStreetMap'), + }) + ) } return container }, From 9e346d2d4c4ecf870a8bbcc1187fae9e11c499da Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 26 Dec 2023 12:16:50 +0100 Subject: [PATCH 012/405] search input: add a placeholder to mention typing coordinates --- umap/static/umap/js/umap.controls.js | 1 + 1 file changed, 1 insertion(+) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index ee76e440..217b842b 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -1353,6 +1353,7 @@ L.U.StarControl = L.Control.extend({ L.U.Search = L.PhotonSearch.extend({ initialize: function (map, input, options) { + this.options.placeholder = L._('Type a place name or coordinates') L.PhotonSearch.prototype.initialize.call(this, map, input, options) this.options.url = map.options.urls.search if (map.options.maxBounds) this.options.bbox = map.options.maxBounds.toBBoxString() From 936bbb946471bbab0f0aa2734efa7efce703e440 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 27 Dec 2023 09:17:29 +0100 Subject: [PATCH 013/405] Be more explicit on changed fields when updating choropleth form The postUpdate method of the Choropleth layer is called after any form field change, even if this field is not in the dedicated choropleth helper. So the previous check was too broad, and it would try to fetch the breaks input value on any form helper, which would fail if someone change any "non choropleth" property (like the colour) --- umap/static/umap/js/umap.layer.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index e316233d..4602fed0 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -235,13 +235,18 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ }, postUpdate: function (e) { - if (e.helper.field === 'options.choropleth.breaks') { + const field = e.helper.field, + builder = e.helper.builder + // If user touches the breaks, then force manual mode + if (field === 'options.choropleth.breaks') { this.datalayer.options.choropleth.mode = 'manual' - e.helper.builder.helpers['options.choropleth.mode'].fetch() + builder.helpers['options.choropleth.mode'].fetch() } this.computeBreaks() - if (e.helper.field !== 'options.choropleth.breaks') { - e.helper.builder.helpers['options.choropleth.breaks'].fetch() + // If user changes the mode or the number of classes, + // then update the breaks input value + if (field === 'options.choropleth.mode' || field === 'options.choropleth.classes') { + builder.helpers['options.choropleth.breaks'].fetch() } }, From c9b4b96c0107f5bdb5d12ca7a6a45b8b5064aef4 Mon Sep 17 00:00:00 2001 From: David Larlet Date: Tue, 19 Dec 2023 14:22:06 -0500 Subject: [PATCH 014/405] =?UTF-8?q?Preview=20map=20only=20on=20click=20in?= =?UTF-8?q?=20user=E2=80=99s=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #1459 --- umap/models.py | 35 ++++++++++++++++++++++++ umap/templates/umap/map_table.html | 44 ++++++++++++++++++++++++++++-- umap/templatetags/umap_tags.py | 28 +------------------ 3 files changed, 77 insertions(+), 30 deletions(-) diff --git a/umap/models.py b/umap/models.py index 71f68144..7bf8494f 100644 --- a/umap/models.py +++ b/umap/models.py @@ -1,3 +1,4 @@ +import json import os import time @@ -193,6 +194,40 @@ class Map(NamedModel): objects = models.Manager() public = PublicManager() + @property + def unique_id(self): + return f"map_{self.pk}" + + @property + def preview_settings(self): + from .views import _urls_for_js + + layers = self.datalayer_set.all() + datalayer_data = [c.metadata() for c in layers] + map_settings = self.settings + if "properties" not in map_settings: + map_settings["properties"] = {} + map_settings["properties"].update( + { + "tilelayers": [TileLayer.get_default().json], + "datalayers": datalayer_data, + "urls": _urls_for_js(), + "STATIC_URL": settings.STATIC_URL, + "editMode": "disabled", + "hash": False, + "attributionControl": False, + "scrollWheelZoom": False, + "umapAttributionControl": False, + "noControl": True, + "umap_id": self.pk, + "onLoadPanel": "none", + "captionBar": False, + "default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL, + "slideshow": {}, + } + ) + return map_settings + def get_absolute_url(self): return reverse("map", kwargs={"slug": self.slug or "map", "map_id": self.pk}) diff --git a/umap/templates/umap/map_table.html b/umap/templates/umap/map_table.html index e4df76b7..d5721d15 100644 --- a/umap/templates/umap/map_table.html +++ b/umap/templates/umap/map_table.html @@ -1,9 +1,8 @@ -{% load umap_tags umap_tags i18n %} +{% load umap_tags i18n %} {% if not is_ajax %} - @@ -14,10 +13,19 @@ {% endif %} {% for map_inst in maps %} + {{ map_inst.preview_settings|json_script:map_inst.unique_id }} - @@ -39,3 +47,33 @@ class="button more_button neutral">{% trans "More" %} {% endif %} + + + + diff --git a/umap/templatetags/umap_tags.py b/umap/templatetags/umap_tags.py index 349e426e..70728ffa 100644 --- a/umap/templatetags/umap_tags.py +++ b/umap/templatetags/umap_tags.py @@ -4,9 +4,6 @@ from copy import copy from django import template from django.conf import settings -from ..models import DataLayer, TileLayer -from ..views import _urls_for_js - register = template.Library() @@ -22,30 +19,7 @@ def umap_js(locale=None): @register.inclusion_tag("umap/map_fragment.html") def map_fragment(map_instance, **kwargs): - layers = DataLayer.objects.filter(map=map_instance) - datalayer_data = [c.metadata() for c in layers] - map_settings = map_instance.settings - if "properties" not in map_settings: - map_settings["properties"] = {} - map_settings["properties"].update( - { - "tilelayers": [TileLayer.get_default().json], - "datalayers": datalayer_data, - "urls": _urls_for_js(), - "STATIC_URL": settings.STATIC_URL, - "editMode": "disabled", - "hash": False, - "attributionControl": False, - "scrollWheelZoom": False, - "umapAttributionControl": False, - "noControl": True, - "umap_id": map_instance.pk, - "onLoadPanel": "none", - "captionBar": False, - "default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL, - "slideshow": {}, - } - ) + map_settings = map_instance.preview_settings map_settings["properties"].update(kwargs) prefix = kwargs.pop("prefix", None) or "map" page = kwargs.pop("page", None) or "" From 7ff543e1d581009869ac642e49487abbd9e64f96 Mon Sep 17 00:00:00 2001 From: David Larlet Date: Tue, 19 Dec 2023 20:50:30 -0500 Subject: [PATCH 015/405] Create the unique_id in the template (vs. model) --- umap/models.py | 4 --- umap/templates/umap/map_table.html | 52 ++++++++++++++++-------------- umap/templatetags/umap_tags.py | 6 ++++ 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/umap/models.py b/umap/models.py index 7bf8494f..e3e11e1c 100644 --- a/umap/models.py +++ b/umap/models.py @@ -194,10 +194,6 @@ class Map(NamedModel): objects = models.Manager() public = PublicManager() - @property - def unique_id(self): - return f"map_{self.pk}" - @property def preview_settings(self): from .views import _urls_for_js diff --git a/umap/templates/umap/map_table.html b/umap/templates/umap/map_table.html index d5721d15..6da94810 100644 --- a/umap/templates/umap/map_table.html +++ b/umap/templates/umap/map_table.html @@ -13,31 +13,33 @@ {% endif %} {% for map_inst in maps %} - {{ map_inst.preview_settings|json_script:map_inst.unique_id }} - - - - - - - + {% with unique_id="map_"|addstr:map_inst.pk %} + {{ map_inst.preview_settings|json_script:unique_id }} + + + + + + + + {% endwith %} {% endfor %}
{% blocktrans %}Map{% endblocktrans %} {% blocktrans %}Name{% endblocktrans %} {% blocktrans %}Who can see / edit{% endblocktrans %} {% blocktrans %}Last save{% endblocktrans %}
{% map_fragment map_inst prefix=prefix page=request.GET.p %} {{ map_inst.name }} + + +
+
+

+ +

+
+
{{ map_inst.get_share_status_display }} / {{ map_inst.get_edit_status_display }} {{ map_inst.modified_at }}
- {{ map_inst.name }} - - -
-
-

- -

-
-
-
{{ map_inst.get_share_status_display }} / {{ map_inst.get_edit_status_display }}{{ map_inst.modified_at }} - {{ map_inst.owner }} - - {% translate "Share" %} | - {% translate "Edit" %} | - {% translate "Download" %} -
+ {{ map_inst.name }} + + +
+
+

+ +

+
+
+
{{ map_inst.get_share_status_display }} / {{ map_inst.get_edit_status_display }}{{ map_inst.modified_at }} + {{ map_inst.owner }} + + {% translate "Share" %} | + {% translate "Edit" %} | + {% translate "Download" %} +
diff --git a/umap/templatetags/umap_tags.py b/umap/templatetags/umap_tags.py index 70728ffa..2349c579 100644 --- a/umap/templatetags/umap_tags.py +++ b/umap/templatetags/umap_tags.py @@ -60,3 +60,9 @@ def ipdb(what): ipdb.set_trace() return "" + + +@register.filter +def addstr(arg1, arg2): + # Necessity: https://stackoverflow.com/a/23783666 + return str(arg1) + str(arg2) From 8d1181562502acc4c79d893bbc521a917d669afa Mon Sep 17 00:00:00 2001 From: David Larlet Date: Tue, 19 Dec 2023 20:55:19 -0500 Subject: [PATCH 016/405] Move _urls_for_js to utils.py (circular imports) --- umap/models.py | 4 +--- umap/utils.py | 17 +++++++++++++++++ umap/views.py | 16 ---------------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/umap/models.py b/umap/models.py index e3e11e1c..c74e7c0e 100644 --- a/umap/models.py +++ b/umap/models.py @@ -1,4 +1,3 @@ -import json import os import time @@ -12,6 +11,7 @@ from django.urls import reverse from django.utils.translation import gettext_lazy as _ from .managers import PublicManager +from .utils import _urls_for_js # Did not find a clean way to do this in Django @@ -196,8 +196,6 @@ class Map(NamedModel): @property def preview_settings(self): - from .views import _urls_for_js - layers = self.datalayer_set.all() datalayer_data = [c.metadata() for c in layers] map_settings = self.settings diff --git a/umap/utils.py b/umap/utils.py index d7dfa6fb..2a51fe4e 100644 --- a/umap/utils.py +++ b/umap/utils.py @@ -1,9 +1,26 @@ import gzip import os +from django.conf import settings from django.urls import URLPattern, URLResolver, get_resolver +def _urls_for_js(urls=None): + """ + Return templated URLs prepared for javascript. + """ + if urls is None: + # prevent circular import + from .urls import i18n_urls, urlpatterns + + urls = [ + url.name for url in urlpatterns + i18n_urls if getattr(url, "name", None) + ] + urls = dict(zip(urls, [get_uri_template(url) for url in urls])) + urls.update(getattr(settings, "UMAP_EXTRA_URLS", {})) + return urls + + def get_uri_template(urlname, args=None, prefix=""): """ Utility function to return an URI Template from a named URL in django diff --git a/umap/views.py b/umap/views.py index d20b924a..08631cb0 100644 --- a/umap/views.py +++ b/umap/views.py @@ -390,22 +390,6 @@ ajax_proxy = AjaxProxy.as_view() # ############## # -def _urls_for_js(urls=None): - """ - Return templated URLs prepared for javascript. - """ - if urls is None: - # prevent circular import - from .urls import i18n_urls, urlpatterns - - urls = [ - url.name for url in urlpatterns + i18n_urls if getattr(url, "name", None) - ] - urls = dict(zip(urls, [get_uri_template(url) for url in urls])) - urls.update(getattr(settings, "UMAP_EXTRA_URLS", {})) - return urls - - def simple_json_response(**kwargs): return HttpResponse(json.dumps(kwargs), content_type="application/json") From ded244c9ff024c8c65620c549781b855ce6d34d7 Mon Sep 17 00:00:00 2001 From: David Larlet Date: Wed, 20 Dec 2023 22:02:00 -0500 Subject: [PATCH 017/405] Put CSS and JS blocks to their dedicated places --- umap/static/umap/content.css | 19 ++++++++++++++-- umap/templates/umap/map_table.html | 30 ------------------------- umap/templates/umap/user_dashboard.html | 16 +++++++++++++ 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/umap/static/umap/content.css b/umap/static/umap/content.css index 909501c1..dfd3848c 100644 --- a/umap/static/umap/content.css +++ b/umap/static/umap/content.css @@ -296,9 +296,9 @@ table.maps { width: 100%; } table.maps .map_fragment { - height: 100px; + display: block; + height: 80vh; width: 100%; - min-width: 200px; } table.maps tbody tr:nth-child(odd) { background-color: #f4f4f4; @@ -310,6 +310,21 @@ table.maps thead tr { line-height: 2em; } +/* **************************** */ +/* Dialog */ +/* **************************** */ +dialog { + width: 90vw; + height: 90vh; +} +dialog::backdrop { + background: #fff5; + backdrop-filter: blur(4px); +} +.close-dialog { + text-align: center; +} + /* ************************************************* */ /* ********************* MOBILE ******************** */ diff --git a/umap/templates/umap/map_table.html b/umap/templates/umap/map_table.html index 6da94810..5ad884a0 100644 --- a/umap/templates/umap/map_table.html +++ b/umap/templates/umap/map_table.html @@ -49,33 +49,3 @@ class="button more_button neutral">{% trans "More" %} {% endif %} - - - - diff --git a/umap/templates/umap/user_dashboard.html b/umap/templates/umap/user_dashboard.html index 8cbc2710..3c4f897b 100644 --- a/umap/templates/umap/user_dashboard.html +++ b/umap/templates/umap/user_dashboard.html @@ -23,3 +23,19 @@ {% endblock maincontent %} + +{% block bottom_js %} + {{ block.super }} + +{% endblock bottom_js %} From c3cb813c230443ab940952048793810389be02bd Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 22 Dec 2023 17:16:06 +0100 Subject: [PATCH 018/405] Allow to reopen a dialog in dashboard previews --- umap/templates/umap/content.html | 2 +- umap/templates/umap/user_dashboard.html | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/umap/templates/umap/content.html b/umap/templates/umap/content.html index 7ae6c00c..f3315db2 100644 --- a/umap/templates/umap/content.html +++ b/umap/templates/umap/content.html @@ -67,7 +67,7 @@ const container = this.parentNode container.innerHTML = data Array.prototype.forEach.call( - container.querySelectorAll('script'), + container.querySelectorAll('script:not([type="application/json"])'), function (item) { eval(item.firstChild.textContent) } diff --git a/umap/templates/umap/user_dashboard.html b/umap/templates/umap/user_dashboard.html index 3c4f897b..cb3d540a 100644 --- a/umap/templates/umap/user_dashboard.html +++ b/umap/templates/umap/user_dashboard.html @@ -28,12 +28,18 @@ {{ block.super }} + @@ -50,8 +52,10 @@ - - + + @@ -61,7 +65,6 @@ - @@ -89,22 +92,24 @@ + diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html index 066def43..414f145b 100644 --- a/umap/templates/umap/js.html +++ b/umap/templates/umap/js.html @@ -30,6 +30,7 @@ {% endcompress %} {% if locale %}{% endif %} {% compress js %} + diff --git a/umap/templates/umap/map_fragment.html b/umap/templates/umap/map_fragment.html index a05afda3..5ff40350 100644 --- a/umap/templates/umap/map_fragment.html +++ b/umap/templates/umap/map_fragment.html @@ -2,6 +2,9 @@
diff --git a/umap/templates/umap/map_init.html b/umap/templates/umap/map_init.html index 66d036b4..c7e36560 100644 --- a/umap/templates/umap/map_init.html +++ b/umap/templates/umap/map_init.html @@ -1,7 +1,9 @@ {% load umap_tags %}
- From ce22a2ee74f22f36c44b0be0355ed2fadcb53b27 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 29 Dec 2023 15:07:27 +0100 Subject: [PATCH 055/405] Only load Leaflet once, as module --- umap/static/umap/js/modules/vendors.js | 143 ++++++++++++++++++++++++- umap/static/umap/test/index.html | 89 ++++++++------- umap/templates/umap/js.html | 94 ++++++++-------- 3 files changed, 231 insertions(+), 95 deletions(-) diff --git a/umap/static/umap/js/modules/vendors.js b/umap/static/umap/js/modules/vendors.js index 1bf5fa64..85ab07e1 100644 --- a/umap/static/umap/js/modules/vendors.js +++ b/umap/static/umap/js/modules/vendors.js @@ -1,7 +1,144 @@ -import { Util } from '../../vendors/leaflet/leaflet-src.esm.js' +import { + bind, + Bounds, + Browser, + Canvas, + Circle, + Class, + Control, + control, + DivIcon, + DomEvent, + DomUtil, + Draggable, + Evented, + extend, + FeatureGroup, + featureGroup, + GeoJSON, + Handler, + Icon, + LatLng, + latLng, + LatLngBounds, + latLngBounds, + Layer, + LayerGroup, + LineUtil, + Map, + map, + Marker, + Mixin, + Path, + Point, + point, + Polygon, + Polyline, + polyline, + Popup, + Projection, + Rectangle, + rectangle, + setOptions, + stamp, + svg, + TileLayer, + Util, +} from '../../vendors/leaflet/leaflet-src.esm.js' // expose the modules to the window.vendors global scope -window.vendors = { +window.L = { + bind, + Bounds, + Browser, + Canvas, + Circle, + Class, + Control, + control, + DivIcon, + DomEvent, + DomUtil, + Draggable, + Evented, + extend, + FeatureGroup, + featureGroup, + GeoJSON, + Handler, + Icon, + LatLng, + latLng, + LatLngBounds, + latLngBounds, + Layer, + LayerGroup, + LineUtil, + Map, + map, + Marker, + Mixin, + Path, + Point, + point, + Polygon, + Polyline, + polyline, + Popup, + Projection, + Rectangle, + rectangle, + setOptions, + stamp, + svg, + TileLayer, + Util, +} + +export { + bind, + Bounds, + Browser, + Canvas, + Circle, + Class, + Control, + control, + DivIcon, + DomEvent, + DomUtil, + Draggable, + Evented, + extend, + FeatureGroup, + featureGroup, + GeoJSON, + Handler, + Icon, + LatLng, + latLng, + LatLngBounds, + latLngBounds, + Layer, + LayerGroup, + LineUtil, + Map, + map, + Marker, + Mixin, + Path, + Point, + point, + Polygon, + Polyline, + polyline, + Popup, + Projection, + Rectangle, + rectangle, + setOptions, + stamp, + svg, + TileLayer, Util, } -export { Util } diff --git a/umap/static/umap/test/index.html b/umap/static/umap/test/index.html index f0b0e487..f77b0e74 100644 --- a/umap/static/umap/test/index.html +++ b/umap/static/umap/test/index.html @@ -3,51 +3,50 @@ Umap front Tests - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html index 414f145b..45e3198a 100644 --- a/umap/templates/umap/js.html +++ b/umap/templates/umap/js.html @@ -1,52 +1,52 @@ {% load compress %} {% compress js %} - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + {% endcompress %} -{% if locale %}{% endif %} +{% if locale %}{% endif %} {% compress js %} - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + {% endcompress %} From c6ce22f3193abc929321d0e83e0b443bbdb51f7e Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 29 Dec 2023 16:11:08 +0100 Subject: [PATCH 056/405] Also run login javascript when page is loaded --- umap/templates/umap/content.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/umap/templates/umap/content.html b/umap/templates/umap/content.html index f3315db2..da8c2b05 100644 --- a/umap/templates/umap/content.html +++ b/umap/templates/umap/content.html @@ -37,7 +37,10 @@ {% endblock content %} {% block bottom_js %} {{ block.super }} + -{% endblock bottom_js %} + {% endblock bottom_js %} {% block footer %} {{ block.super }} {% include "umap/content_footer.html" %} From 7c697f75295caff8848873e847a20d234e40603b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Mon, 8 Jan 2024 17:24:36 +0100 Subject: [PATCH 057/405] Merge @ybon changes --- umap/static/umap/js/modules/global.js | 5 + umap/static/umap/js/modules/urls.js | 2 +- umap/static/umap/js/modules/vendors.js | 144 ------------------------- umap/static/umap/test/index.html | 33 +++--- umap/templates/umap/content.html | 7 +- umap/templates/umap/js.html | 32 ++++-- 6 files changed, 47 insertions(+), 176 deletions(-) create mode 100644 umap/static/umap/js/modules/global.js delete mode 100644 umap/static/umap/js/modules/vendors.js diff --git a/umap/static/umap/js/modules/global.js b/umap/static/umap/js/modules/global.js new file mode 100644 index 00000000..a9485375 --- /dev/null +++ b/umap/static/umap/js/modules/global.js @@ -0,0 +1,5 @@ +import * as L from '../../vendors/leaflet/leaflet-src.esm.js' + +// Exposes the modules to the window global scope, it's expected by leaflet plugins +// in a writeable form. +window.L = { ...L } diff --git a/umap/static/umap/js/modules/urls.js b/umap/static/umap/js/modules/urls.js index 66db6b1a..fd3d4b5f 100644 --- a/umap/static/umap/js/modules/urls.js +++ b/umap/static/umap/js/modules/urls.js @@ -1,4 +1,4 @@ -import { Util } from './vendors.js' +import { Util } from '../../vendors/leaflet/leaflet-src.esm.js' export default class URLs { constructor(serverUrls) { diff --git a/umap/static/umap/js/modules/vendors.js b/umap/static/umap/js/modules/vendors.js deleted file mode 100644 index 85ab07e1..00000000 --- a/umap/static/umap/js/modules/vendors.js +++ /dev/null @@ -1,144 +0,0 @@ -import { - bind, - Bounds, - Browser, - Canvas, - Circle, - Class, - Control, - control, - DivIcon, - DomEvent, - DomUtil, - Draggable, - Evented, - extend, - FeatureGroup, - featureGroup, - GeoJSON, - Handler, - Icon, - LatLng, - latLng, - LatLngBounds, - latLngBounds, - Layer, - LayerGroup, - LineUtil, - Map, - map, - Marker, - Mixin, - Path, - Point, - point, - Polygon, - Polyline, - polyline, - Popup, - Projection, - Rectangle, - rectangle, - setOptions, - stamp, - svg, - TileLayer, - Util, -} from '../../vendors/leaflet/leaflet-src.esm.js' - -// expose the modules to the window.vendors global scope -window.L = { - bind, - Bounds, - Browser, - Canvas, - Circle, - Class, - Control, - control, - DivIcon, - DomEvent, - DomUtil, - Draggable, - Evented, - extend, - FeatureGroup, - featureGroup, - GeoJSON, - Handler, - Icon, - LatLng, - latLng, - LatLngBounds, - latLngBounds, - Layer, - LayerGroup, - LineUtil, - Map, - map, - Marker, - Mixin, - Path, - Point, - point, - Polygon, - Polyline, - polyline, - Popup, - Projection, - Rectangle, - rectangle, - setOptions, - stamp, - svg, - TileLayer, - Util, -} - -export { - bind, - Bounds, - Browser, - Canvas, - Circle, - Class, - Control, - control, - DivIcon, - DomEvent, - DomUtil, - Draggable, - Evented, - extend, - FeatureGroup, - featureGroup, - GeoJSON, - Handler, - Icon, - LatLng, - latLng, - LatLngBounds, - latLngBounds, - Layer, - LayerGroup, - LineUtil, - Map, - map, - Marker, - Mixin, - Path, - Point, - point, - Polygon, - Polyline, - polyline, - Popup, - Projection, - Rectangle, - rectangle, - setOptions, - stamp, - svg, - TileLayer, - Util, -} diff --git a/umap/static/umap/test/index.html b/umap/static/umap/test/index.html index f77b0e74..0d643766 100644 --- a/umap/static/umap/test/index.html +++ b/umap/static/umap/test/index.html @@ -3,7 +3,7 @@ Umap front Tests - + @@ -26,6 +26,7 @@ + @@ -77,21 +78,21 @@ }) chai.config.includeStack = true - - - - - - - - - - - - - - - + + + + + + + + + + + + + + +