diff --git a/umap/static/umap/css/tooltip.css b/umap/static/umap/css/tooltip.css
index 0a5e6d4d..ea289285 100644
--- a/umap/static/umap/css/tooltip.css
+++ b/umap/static/umap/css/tooltip.css
@@ -59,3 +59,16 @@
.tooltip-accent li:last-of-type {
margin-bottom: 0;
}
+
+.umap-tooltip-container.tooltip-right:before {
+ right: 100%;
+ top: calc(50% - var(--arrow-size));
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+ border-right-color: var(--tooltip-color);
+ border-width: var(--arrow-size);
+}
diff --git a/umap/static/umap/js/modules/rendering/controls.js b/umap/static/umap/js/modules/rendering/controls.js
new file mode 100644
index 00000000..6379b827
--- /dev/null
+++ b/umap/static/umap/js/modules/rendering/controls.js
@@ -0,0 +1,248 @@
+import { Control } from '../../../vendors/leaflet/leaflet-src.esm.js'
+import * as Utils from '../utils.js'
+import { translate } from '../i18n.js'
+
+export const HomeControl = Control.extend({
+ options: {
+ position: 'topleft',
+ },
+
+ onAdd: (map) => {
+ const path = map._umap.getStaticPathFor('home.svg')
+ const container = Utils.loadTemplate(
+ `
`
+ )
+ return container
+ },
+})
+
+export const EditControl = Control.extend({
+ options: {
+ position: 'topright',
+ },
+
+ onAdd: (map) => {
+ const template = `
+
+
+
+ `
+ const [container, { button }] = Utils.loadTemplateWithRefs(template)
+ button.addEventListener('click', () => map._umap.enableEdit())
+ button.addEventListener('mouseover', () => {
+ map._umap.tooltip.open({
+ content: map._umap.help.displayLabel('TOGGLE_EDIT'),
+ anchor: button,
+ position: 'bottom',
+ delay: 750,
+ duration: 5000,
+ })
+ })
+ return container
+ },
+})
+
+export const MoreControl = Control.extend({
+ options: {
+ position: 'topleft',
+ },
+
+ onAdd: function (map) {
+ const pos = this.getPosition()
+ const corner = map._controlCorners[pos]
+ const className = 'umap-more-controls'
+ const template = `
+
+
+
+ `
+ const [container, { button }] = Utils.loadTemplateWithRefs(template)
+ button.addEventListener('click', () => corner.classList.toggle(className))
+ button.addEventListener('mouseover', () => {
+ const extended = corner.classList.contains(className)
+ map._umap.tooltip.open({
+ content: extended ? translate('Hide controls') : translate('More controls'),
+ anchor: button,
+ position: 'right',
+ delay: 750,
+ })
+ })
+ return container
+ },
+})
+
+export const PermanentCreditsControl = Control.extend({
+ options: {
+ position: 'bottomleft',
+ },
+
+ onAdd: (map) => {
+ const container = Utils.loadTemplate(
+ `${Utils.toHTML(map.options.permanentCredit)}
`
+ )
+ const background = map.options.permanentCreditBackground ? '#FFFFFFB0' : ''
+ container.style.backgroundColor = background
+ return container
+ },
+})
+
+const BaseButton = Control.extend({
+ initialize: function (umap, options) {
+ this._umap = umap
+ Control.prototype.initialize.call(this, options)
+ },
+
+ onAdd: function (map) {
+ const template = `
+
+
+
+ `
+ const [container, { button }] = Utils.loadTemplateWithRefs(template)
+ button.addEventListener('click', () => this.onClick())
+ button.addEventListener('dblclick', (event) => {
+ event.stopPropagation()
+ })
+ this.afterAdd(container)
+ return container
+ },
+
+ afterAdd: (container) => {},
+})
+
+export const DataLayersControl = BaseButton.extend({
+ options: {
+ position: 'topleft',
+ className: 'umap-control-browse',
+ title: translate('Open browser'),
+ },
+
+ afterAdd: function (container) {
+ Utils.toggleBadge(container, this._umap.browser?.hasFilters())
+ },
+
+ onClick: function () {
+ this._umap.openBrowser()
+ },
+})
+
+export const CaptionControl = BaseButton.extend({
+ options: {
+ position: 'topleft',
+ className: 'umap-control-caption',
+ title: translate('About'),
+ },
+
+ onClick: function () {
+ this._umap.openCaption()
+ },
+})
+
+export const EmbedControl = BaseButton.extend({
+ options: {
+ position: 'topleft',
+ title: translate('Share and download'),
+ className: 'leaflet-control-embed',
+ },
+
+ onClick: function () {
+ this._umap.share.open()
+ },
+})
+
+export const AttributionControl = Control.Attribution.extend({
+ options: {
+ prefix: '',
+ },
+
+ _update: function () {
+ // Layer is no more on the map
+ if (!this._map) return
+ Control.Attribution.prototype._update.call(this)
+ const shortCredit = this._map._umap.getProperty('shortCredit')
+ const captionMenus = this._map._umap.getProperty('captionMenus')
+ // Use our own container, so we can hide/show on small screens
+ const originalCredits = this._container.innerHTML
+ this._container.innerHTML = ''
+ const template = `
+
+ `
+ const [container, { short, caption, home, site }] =
+ Utils.loadTemplateWithRefs(template)
+ caption.addEventListener('click', () => this._map._umap.openCaption())
+ this._container.appendChild(container)
+ short.hidden = !shortCredit
+ caption.hidden = !captionMenus
+ site.hidden = !captionMenus
+ home.hidden = this._map._umap.isEmbed || !captionMenus
+ },
+})
+
+/* Used in edit mode to define the default tilelayer */
+export const TileLayerChooser = BaseButton.extend({
+ options: {
+ position: 'topleft',
+ },
+
+ onClick: function () {
+ this.openSwitcher({ edit: true })
+ },
+
+ openSwitcher: function (options = {}) {
+ const template = `
+
+
${translate('Change tilelayers')}
+
+
+ `
+ const [container, { tileContainer }] = Utils.loadTemplateWithRefs(template)
+ this.buildList(tileContainer, options)
+ const panel = options.edit ? this._umap.editPanel : this._umap.panel
+ panel.open({ content: container, highlight: 'tilelayers' })
+ },
+
+ buildList: function (container, options) {
+ this._umap._leafletMap.eachTileLayer((tilelayer) => {
+ const browserIsHttps = window.location.protocol === 'https:'
+ const tileLayerIsHttp = tilelayer.options.url_template.indexOf('http:') === 0
+ if (browserIsHttps && tileLayerIsHttp) return
+ container.appendChild(this.addTileLayerElement(tilelayer, options))
+ })
+ },
+
+ addTileLayerElement: function (tilelayer, options) {
+ const selectedClass = this._umap._leafletMap.hasLayer(tilelayer) ? 'selected' : ''
+ const src = Utils.template(
+ tilelayer.options.url_template,
+ this._umap._leafletMap.options.demoTileInfos
+ )
+ const template = `
+
+
+ ${tilelayer.options.name}
+
+ `
+ const li = Utils.loadTemplate(template)
+ li.addEventListener('click', () => {
+ const oldTileLayer = this._umap.properties.tilelayer
+ this._umap._leafletMap.selectTileLayer(tilelayer)
+ this._umap._leafletMap._controls.tilelayers.setLayers()
+ if (options?.edit) {
+ this._umap.properties.tilelayer = tilelayer.toJSON()
+ this._umap.sync.update(
+ 'properties.tilelayer',
+ this._umap.properties.tilelayer,
+ oldTileLayer
+ )
+ }
+ })
+ return li
+ },
+})
diff --git a/umap/static/umap/js/modules/rendering/map.js b/umap/static/umap/js/modules/rendering/map.js
index b5f5d5ae..599068f7 100644
--- a/umap/static/umap/js/modules/rendering/map.js
+++ b/umap/static/umap/js/modules/rendering/map.js
@@ -11,6 +11,17 @@ import {
import { uMapAlert as Alert } from '../../components/alerts/alert.js'
import DropControl from '../drop.js'
import { translate } from '../i18n.js'
+import {
+ AttributionControl,
+ CaptionControl,
+ DataLayersControl,
+ EmbedControl,
+ EditControl,
+ HomeControl,
+ MoreControl,
+ PermanentCreditsControl,
+ TileLayerChooser,
+} from './controls.js'
import * as Utils from '../utils.js'
import * as Icon from './icon.js'
@@ -40,15 +51,15 @@ const ControlsMixin = {
this._controls = {}
if (this._umap.hasEditMode() && !this.options.noControl) {
- new U.EditControl(this).addTo(this)
+ new EditControl(this).addTo(this)
}
- this._controls.home = new U.HomeControl(this._umap)
+ this._controls.home = new HomeControl(this._umap)
this._controls.zoom = new Control.Zoom({
zoomInTitle: translate('Zoom in'),
zoomOutTitle: translate('Zoom out'),
})
- this._controls.datalayers = new U.DataLayersControl(this._umap)
- this._controls.caption = new U.CaptionControl(this._umap)
+ this._controls.datalayers = new DataLayersControl(this._umap)
+ this._controls.caption = new CaptionControl(this._umap)
this._controls.locate = new U.Locate(this, {
strings: {
title: translate('Center map on your location'),
@@ -69,8 +80,8 @@ const ControlsMixin = {
},
})
this._controls.search = new U.SearchControl()
- this._controls.embed = new Control.Embed(this._umap)
- this._controls.tilelayersChooser = new U.TileLayerChooser(this)
+ this._controls.embed = new EmbedControl(this._umap)
+ this._controls.tilelayersChooser = new TileLayerChooser(this._umap)
this._controls.editinosm = new Control.EditInOSM({
position: 'topleft',
widgetOptions: {
@@ -80,9 +91,9 @@ const ControlsMixin = {
},
})
this._controls.measure = new L.MeasureControl().initHandler(this)
- this._controls.more = new U.MoreControls()
+ this._controls.more = new MoreControl()
this._controls.scale = L.control.scale()
- this._controls.permanentCredit = new U.PermanentCreditsControl(this)
+ this._controls.permanentCredit = new PermanentCreditsControl(this)
this._umap.drop = new DropControl(this._umap, this, this._container)
this._controls.tilelayers = new U.TileLayerControl(this)
},
@@ -93,7 +104,7 @@ const ControlsMixin = {
}
if (this.options.noControl) return
- this._controls.attribution = new U.AttributionControl().addTo(this)
+ this._controls.attribution = new AttributionControl().addTo(this)
if (this.options.miniMap) {
this.whenReady(function () {
if (this.selectedTilelayer) {
diff --git a/umap/static/umap/js/modules/ui/base.js b/umap/static/umap/js/modules/ui/base.js
index b6479b37..9ff710a7 100644
--- a/umap/static/umap/js/modules/ui/base.js
+++ b/umap/static/umap/js/modules/ui/base.js
@@ -4,6 +4,8 @@ export class Positioned {
this.anchorTop(anchor)
} else if (anchor && position === 'bottom') {
this.anchorBottom(anchor)
+ } else if (anchor && position === 'right') {
+ this.anchorRight(anchor)
} else {
this.anchorAbsolute()
}
@@ -12,6 +14,7 @@ export class Positioned {
toggleClassPosition(position) {
this.container.classList.toggle('tooltip-bottom', position === 'bottom')
this.container.classList.toggle('tooltip-top', position === 'top')
+ this.container.classList.toggle('tooltip-right', position === 'right')
}
anchorTop(el) {
@@ -33,6 +36,16 @@ export class Positioned {
})
}
+ anchorRight(el) {
+ this.toggleClassPosition('right')
+ const coords = this.getPosition(el)
+ console.log(coords)
+ this.setPosition({
+ left: coords.right + 11,
+ top: coords.top,
+ })
+ }
+
anchorAbsolute() {
const left =
this.parent.offsetLeft +
diff --git a/umap/static/umap/js/modules/umap.js b/umap/static/umap/js/modules/umap.js
index 980ac1b8..e7a98085 100644
--- a/umap/static/umap/js/modules/umap.js
+++ b/umap/static/umap/js/modules/umap.js
@@ -107,7 +107,7 @@ export default class Umap {
if (geojson.properties.schema) this.overrideSchema(geojson.properties.schema)
// Do not display in an iframe.
- if (window.self !== window.top) {
+ if (this.isEmbed) {
this.properties.homeControl = false
}
@@ -258,6 +258,10 @@ export default class Umap {
}
}
+ get isEmbed() {
+ return window.self !== window.top
+ }
+
setPropertiesFromQueryString() {
const asBoolean = (key) => {
const value = this.searchParams.get(key)
diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js
index 8c302a5a..903502f8 100644
--- a/umap/static/umap/js/umap.controls.js
+++ b/umap/static/umap/js/umap.controls.js
@@ -1,183 +1,3 @@
-U.HomeControl = L.Control.extend({
- options: {
- position: 'topleft',
- },
-
- onAdd: (map) => {
- const path = map._umap.getStaticPathFor('home.svg')
- const container = U.Utils.loadTemplate(
- `
`
- )
- return container
- },
-})
-
-U.EditControl = L.Control.extend({
- options: {
- position: 'topright',
- },
-
- onAdd: function (map) {
- const container = L.DomUtil.create('div', 'edit-enable')
- const enableEditing = L.DomUtil.createButton(
- '',
- container,
- L._('Edit'),
- map._umap.enableEdit,
- map._umap
- )
- L.DomEvent.on(
- enableEditing,
- 'mouseover',
- () => {
- map._umap.tooltip.open({
- content: map._umap.help.displayLabel('TOGGLE_EDIT'),
- anchor: enableEditing,
- position: 'bottom',
- delay: 750,
- duration: 5000,
- })
- },
- this
- )
-
- return container
- },
-})
-
-U.MoreControls = L.Control.extend({
- options: {
- position: 'topleft',
- },
-
- onAdd: function () {
- const container = L.DomUtil.create('div', 'umap-control-text')
- const moreButton = L.DomUtil.createButton(
- 'umap-control-more',
- container,
- L._('More controls'),
- this.toggle,
- this
- )
- const lessButton = L.DomUtil.createButton(
- 'umap-control-less',
- container,
- L._('Hide controls'),
- this.toggle,
- this
- )
- return container
- },
-
- toggle: function () {
- const pos = this.getPosition()
- const corner = this._map._controlCorners[pos]
- const className = 'umap-more-controls'
- if (L.DomUtil.hasClass(corner, className)) L.DomUtil.removeClass(corner, className)
- else L.DomUtil.addClass(corner, className)
- },
-})
-
-U.PermanentCreditsControl = L.Control.extend({
- options: {
- position: 'bottomleft',
- },
-
- initialize: function (map, options) {
- this.map = map
- L.Control.prototype.initialize.call(this, options)
- },
-
- onAdd: function () {
- this.paragraphContainer = L.DomUtil.create(
- 'div',
- 'umap-permanent-credits-container text'
- )
- this.setCredits()
- this.setBackground()
- return this.paragraphContainer
- },
-
- setCredits: function () {
- this.paragraphContainer.innerHTML = U.Utils.toHTML(this.map.options.permanentCredit)
- },
-
- setBackground: function () {
- if (this.map.options.permanentCreditBackground) {
- this.paragraphContainer.style.backgroundColor = '#FFFFFFB0'
- } else {
- this.paragraphContainer.style.backgroundColor = ''
- }
- },
-})
-
-L.Control.Button = L.Control.extend({
- initialize: function (umap, options) {
- this._umap = umap
- L.Control.prototype.initialize.call(this, options)
- },
-
- getClassName: function () {
- return this.options.className
- },
-
- onAdd: function (map) {
- const container = L.DomUtil.create('div', `${this.getClassName()} umap-control`)
- const button = L.DomUtil.createButton(
- '',
- container,
- this.options.title,
- this.onClick,
- this
- )
- L.DomEvent.on(button, 'dblclick', L.DomEvent.stopPropagation)
- this.afterAdd(container)
- return container
- },
-
- afterAdd: (container) => {},
-})
-
-U.DataLayersControl = L.Control.Button.extend({
- options: {
- position: 'topleft',
- className: 'umap-control-browse',
- title: L._('Open browser'),
- },
-
- afterAdd: function (container) {
- U.Utils.toggleBadge(container, this._umap.browser?.hasFilters())
- },
-
- onClick: function () {
- this._umap.openBrowser()
- },
-})
-
-U.CaptionControl = L.Control.Button.extend({
- options: {
- position: 'topleft',
- className: 'umap-control-caption',
- title: L._('About'),
- },
-
- onClick: function () {
- this._umap.openCaption()
- },
-})
-
-L.Control.Embed = L.Control.Button.extend({
- options: {
- position: 'topleft',
- title: L._('Share and download'),
- className: 'leaflet-control-embed umap-control',
- },
-
- onClick: function () {
- this._umap.share.open()
- },
-})
-
/* Used in view mode to define the current tilelayer */
U.TileLayerControl = L.Control.IconLayers.extend({
initialize: function (map, options) {
@@ -238,128 +58,6 @@ U.TileLayerControl = L.Control.IconLayers.extend({
},
})
-/* Used in edit mode to define the default tilelayer */
-U.TileLayerChooser = L.Control.extend({
- options: {
- position: 'topleft',
- },
-
- initialize: function (map, options = {}) {
- this.map = map
- L.Control.prototype.initialize.call(this, options)
- },
-
- onAdd: function () {
- const container = L.DomUtil.create('div', 'leaflet-control-tilelayers umap-control')
- const changeMapBackgroundButton = L.DomUtil.createButton(
- '',
- container,
- L._('Change map background'),
- this.openSwitcher,
- this
- )
- L.DomEvent.on(changeMapBackgroundButton, 'dblclick', L.DomEvent.stopPropagation)
- return container
- },
-
- openSwitcher: function (options = {}) {
- const container = L.DomUtil.create('div', 'umap-edit-tilelayers')
- L.DomUtil.createTitle(container, L._('Change tilelayers'), 'icon-tilelayer')
- this._tilelayers_container = L.DomUtil.create('ul', '', container)
- this.buildList(options)
- const panel = options.edit ? this.map._umap.editPanel : this.map._umap.panel
- panel.open({ content: container, highlight: 'tilelayers' })
- },
-
- buildList: function (options) {
- this.map.eachTileLayer(function (tilelayer) {
- if (
- window.location.protocol === 'https:' &&
- tilelayer.options.url_template.indexOf('http:') === 0
- )
- return
- this.addTileLayerElement(tilelayer, options)
- }, this)
- },
-
- addTileLayerElement: function (tilelayer, options) {
- const selectedClass = this.map.hasLayer(tilelayer) ? 'selected' : ''
- const el = L.DomUtil.create('li', selectedClass, this._tilelayers_container)
- const img = L.DomUtil.create('img', '', el)
- const name = L.DomUtil.create('div', '', el)
- img.src = U.Utils.template(
- tilelayer.options.url_template,
- this.map.options.demoTileInfos
- )
- img.loading = 'lazy'
- name.textContent = tilelayer.options.name
- L.DomEvent.on(
- el,
- 'click',
- () => {
- const oldTileLayer = this.map._umap.properties.tilelayer
- this.map.selectTileLayer(tilelayer)
- this.map._controls.tilelayers.setLayers()
- if (options?.edit) {
- this.map._umap.properties.tilelayer = tilelayer.toJSON()
- this.map._umap.isDirty = true
- this.map._umap.sync.update(
- 'properties.tilelayer',
- this.map._umap.properties.tilelayer,
- oldTileLayer
- )
- }
- },
- this
- )
- },
-})
-
-U.AttributionControl = L.Control.Attribution.extend({
- options: {
- prefix: '',
- },
-
- _update: function () {
- // Layer is no more on the map
- if (!this._map) return
- L.Control.Attribution.prototype._update.call(this)
- // Use our own container, so we can hide/show on small screens
- const credits = this._container.innerHTML
- this._container.innerHTML = ''
- const container = L.DomUtil.create('div', 'attribution-container', this._container)
- container.innerHTML = credits
- const shortCredit = this._map._umap.getProperty('shortCredit')
- const captionMenus = this._map._umap.getProperty('captionMenus')
- if (shortCredit) {
- L.DomUtil.element({
- tagName: 'span',
- parent: container,
- safeHTML: ` — ${U.Utils.toHTML(shortCredit)}`,
- })
- }
- if (captionMenus) {
- const link = L.DomUtil.add('a', '', container, ` — ${L._('Open caption')}`)
- L.DomEvent.on(link, 'click', L.DomEvent.stop)
- .on(link, 'click', () => this._map._umap.openCaption())
- .on(link, 'dblclick', L.DomEvent.stop)
- }
- if (window.top === window.self && captionMenus) {
- // We are not in iframe mode
- L.DomUtil.createLink('', container, ` — ${L._('Home')}`, '/')
- }
- if (captionMenus) {
- L.DomUtil.createLink(
- '',
- container,
- ` — ${L._('Powered by uMap')}`,
- 'https://umap-project.org/'
- )
- }
- L.DomUtil.createLink('attribution-toggle', this._container, '')
- },
-})
-
/*
* Take control over L.Control.Locate to be able to
* call start() before adding the control (and thus the button) to the map.
diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css
index 0f6a3f4d..77b89a51 100644
--- a/umap/static/umap/map.css
+++ b/umap/static/umap/map.css
@@ -96,27 +96,20 @@ html[dir="rtl"] .leaflet-tooltip-pane > * {
background-color: white;
min-height: initial;
}
-.leaflet-control.display-on-more,
-.umap-control-less {
+.leaflet-control.display-on-more {
display: none;
}
-.umap-control-more,
-.umap-control-less {
+.umap-control-more {
background-image: url('./img/24-white.svg');
background-position: -72px -402px;
- text-indent: -9999px;
margin-bottom: 0;
}
-.umap-control-less {
+.umap-more-controls .umap-control-more {
background-position: -108px -402px;
}
-.umap-more-controls .display-on-more,
-.umap-more-controls .umap-control-less {
+.umap-more-controls .display-on-more {
display: block;
}
-.umap-more-controls .umap-control-more {
- display: none;
-}
.leaflet-control-embed [type="button"] {
background-position: 0 -180px;
}
diff --git a/umap/tests/integration/test_querystring.py b/umap/tests/integration/test_querystring.py
index 0694a335..75ed705d 100644
--- a/umap/tests/integration/test_querystring.py
+++ b/umap/tests/integration/test_querystring.py
@@ -63,5 +63,5 @@ def test_zoom_control(map, live_server, datalayer, page):
expect(control).to_be_visible()
page.goto(f"{live_server.url}{map.get_absolute_url()}?zoomControl=null")
expect(control).to_be_hidden()
- page.get_by_title("More controls").click()
+ page.locator(".umap-control-more").click()
expect(control).to_be_visible()