From 2923e1ca51c424a1badc3e2b3a7bd8f61656c6ad Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 23 Sep 2024 11:08:21 +0200 Subject: [PATCH 1/3] feat: add a global toolbox in browser fix #2097 cf #1500 --- umap/static/umap/js/modules/browser.js | 32 +++++++++++++ umap/static/umap/map.css | 6 +++ umap/tests/integration/test_browser.py | 62 ++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/umap/static/umap/js/modules/browser.js b/umap/static/umap/js/modules/browser.js index 38c7b565..38946dff 100644 --- a/umap/static/umap/js/modules/browser.js +++ b/umap/static/umap/js/modules/browser.js @@ -1,6 +1,7 @@ import { DomEvent, DomUtil, stamp } from '../../vendors/leaflet/leaflet-src.esm.js' import { translate } from './i18n.js' import * as Icon from './rendering/icon.js' +import * as Utils from './utils.js' export default class Browser { constructor(map) { @@ -164,6 +165,7 @@ export default class Browser { }) this.filtersTitle = container.querySelector('summary') this.toggleBadge() + this.addMainToolbox(container) this.dataContainer = DomUtil.create('div', '', container) let fields = [ @@ -215,6 +217,36 @@ export default class Browser { } } + addMainToolbox(container) { + const [toolbox, { toggle, fitBounds, download }] = Utils.loadTemplateWithRefs(` +
+ + + +
+ `) + container.appendChild(toolbox) + toggle.addEventListener('click', () => this.toggleLayers()) + fitBounds.addEventListener('click', () => this.map.fitDataBounds()) + download.addEventListener('click', () => this.map.share.open()) + } + + toggleLayers() { + // If at least one layer is shown, hide it + // otherwise show all + let allHidden = true + this.map.eachBrowsableDataLayer((datalayer) => { + if (datalayer.isVisible()) allHidden = false + }) + this.map.eachBrowsableDataLayer((datalayer) => { + if (allHidden) { + datalayer.show() + } else { + if (datalayer.isVisible()) datalayer.hide() + } + }) + } + static backButton(map) { const button = DomUtil.createButtonIcon( DomUtil.create('li', '', undefined), diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index 4c57acf9..a7079a26 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -913,6 +913,12 @@ a.umap-control-caption, .umap-caption .umap-map-author { padding-inline-start: 31px; } +.umap-browser .main-toolbox { + padding-left: 4px; /* Align with toolbox below */ +} +.umap-browser .main-toolbox i { + cursor: pointer; +} /* ********************************* */ diff --git a/umap/tests/integration/test_browser.py b/umap/tests/integration/test_browser.py index 9adad27f..414445f8 100644 --- a/umap/tests/integration/test_browser.py +++ b/umap/tests/integration/test_browser.py @@ -414,3 +414,65 @@ def test_should_have_edit_buttons_in_edit_mode(live_server, openmap, page, boots expect(delete_layer).to_be_visible() expect(edit_feature).to_have_count(3) expect(delete_feature).to_have_count(3) + + +def test_main_toolbox_toggle_all_layers(live_server, map, page): + map.settings["properties"]["onLoadPanel"] = "databrowser" + map.save() + data = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {"name": "one point"}, + "geometry": {"type": "Point", "coordinates": [3.33, 46.92]}, + }, + ], + } + DataLayerFactory(map=map, data=data) + data = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {"name": "one other point"}, + "geometry": {"type": "Point", "coordinates": [3.34, 46.94]}, + }, + ], + } + DataLayerFactory(map=map, data=data) + data = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {"name": "another point"}, + "geometry": {"type": "Point", "coordinates": [3.35, 46.95]}, + }, + ], + "_umap_options": {"displayOnLoad": False}, + } + DataLayerFactory(map=map, data=data, settings={"displayOnLoad": False}) + page.goto(f"{live_server.url}{map.get_absolute_url()}#10/46.93/3.33") + markers = page.locator(".leaflet-marker-icon") + expect(markers).to_have_count(2) + # Only one is off + expect(page.locator(".datalayer.off")).to_have_count(1) + + # Click on button + page.locator(".umap-browser [data-ref=toggle]").click() + # Should have hidden the two other layers + expect(page.locator(".datalayer.off")).to_have_count(3) + expect(markers).to_have_count(0) + + # Click again + page.locator(".umap-browser [data-ref=toggle]").click() + # Should shown all layers + expect(page.locator(".datalayer.off")).to_have_count(0) + expect(markers).to_have_count(3) + + # Click again + page.locator(".umap-browser [data-ref=toggle]").click() + # Should hidden again all layers + expect(page.locator(".datalayer.off")).to_have_count(3) + expect(markers).to_have_count(0) From 36afe0ead538419665ce22e326ba7ba6675ce69a Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 7 Oct 2024 11:45:05 +0200 Subject: [PATCH 2/3] chore: open dropdown on download click in browser Instead of switching context and opening the share panel. --- umap/static/umap/js/modules/browser.js | 16 +++++++++++++++- umap/static/umap/js/modules/ui/contextmenu.js | 5 +++++ umap/static/umap/js/umap.controls.js | 4 +--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/umap/static/umap/js/modules/browser.js b/umap/static/umap/js/modules/browser.js index 38946dff..7699226b 100644 --- a/umap/static/umap/js/modules/browser.js +++ b/umap/static/umap/js/modules/browser.js @@ -2,6 +2,8 @@ import { DomEvent, DomUtil, stamp } from '../../vendors/leaflet/leaflet-src.esm. import { translate } from './i18n.js' import * as Icon from './rendering/icon.js' import * as Utils from './utils.js' +import { EXPORT_FORMATS } from './formatter.js' +import ContextMenu from './ui/contextmenu.js' export default class Browser { constructor(map) { @@ -228,7 +230,19 @@ export default class Browser { container.appendChild(toolbox) toggle.addEventListener('click', () => this.toggleLayers()) fitBounds.addEventListener('click', () => this.map.fitDataBounds()) - download.addEventListener('click', () => this.map.share.open()) + download.addEventListener('click', () => this.downloadVisible(download)) + } + + downloadVisible(element) { + const menu = new ContextMenu({ fixed: true }) + const items = [] + for (const format of Object.keys(EXPORT_FORMATS)) { + items.push({ + label: format, + action: () => this.map.share.download(format), + }) + } + menu.openBelow(element, items) } toggleLayers() { diff --git a/umap/static/umap/js/modules/ui/contextmenu.js b/umap/static/umap/js/modules/ui/contextmenu.js index 8d2603b5..dafb605b 100644 --- a/umap/static/umap/js/modules/ui/contextmenu.js +++ b/umap/static/umap/js/modules/ui/contextmenu.js @@ -21,6 +21,11 @@ export default class ContextMenu extends Positioned { this.openAt([left, top], items) } + openBelow(element, items) { + const coords = this.getPosition(element) + this.openAt([coords.left, coords.bottom], items) + } + openAt([left, top], items) { this.container.innerHTML = '' for (const item of items) { diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 815ee1dc..0246664b 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -661,9 +661,7 @@ const ControlsMixin = { }) } button.addEventListener('click', () => { - const x = button.offsetLeft - const y = button.offsetTop + button.offsetHeight - menu.openAt([x, y], actions) + menu.openBelow(button, actions) }) } this.help.getStartedLink(rightContainer) From 4b66e0fa83f56b9773db8945267bf9b29f5ed79a Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 7 Oct 2024 11:57:37 +0200 Subject: [PATCH 3/3] chore: add border top to browser toolbox --- umap/static/umap/map.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index a7079a26..fe83d407 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -915,6 +915,10 @@ a.umap-control-caption, } .umap-browser .main-toolbox { padding-left: 4px; /* Align with toolbox below */ + border-top: 1px solid var(--color-mediumGray); + margin-top: var(--box-margin); + padding-top: 3px; + padding-bottom: 3px; } .umap-browser .main-toolbox i { cursor: pointer;