diff --git a/umap/static/umap/js/modules/browser.js b/umap/static/umap/js/modules/browser.js index 3ad28ea8..6af51fa6 100644 --- a/umap/static/umap/js/modules/browser.js +++ b/umap/static/umap/js/modules/browser.js @@ -1,6 +1,9 @@ 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' +import { EXPORT_FORMATS } from './formatter.js' +import ContextMenu from './ui/contextmenu.js' export default class Browser { constructor(map) { @@ -165,6 +168,7 @@ export default class Browser { }) this.filtersTitle = container.querySelector('summary') this.toggleBadge() + this.addMainToolbox(container) this.dataContainer = DomUtil.create('div', '', container) let fields = [ @@ -216,6 +220,48 @@ 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.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() { + // 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/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 0863e048..9a27a958 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -657,9 +657,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) diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index 4c57acf9..fe83d407 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -913,6 +913,16 @@ a.umap-control-caption, .umap-caption .umap-map-author { padding-inline-start: 31px; } +.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; +} /* ********************************* */ diff --git a/umap/tests/integration/test_browser.py b/umap/tests/integration/test_browser.py index 5319ea25..7391f173 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)