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)