diff --git a/docs-users/fr/tutorials/5-multimedia-tooltips.md b/docs-users/fr/tutorials/5-multimedia-tooltips.md index 5b925791..2278d4da 100644 --- a/docs-users/fr/tutorials/5-multimedia-tooltips.md +++ b/docs-users/fr/tutorials/5-multimedia-tooltips.md @@ -12,7 +12,7 @@ data-url="https://umap.openstreetmap.fr/fr/map/new/" data-alt="Panneau d’aide au formatage." data-caption="Panneau d’aide au formatage." - data-selector=".umap-help-box" + data-selector=".umap-dialog" data-width="510" data-height="326" data-padding="5" diff --git a/umap/static/umap/base.css b/umap/static/umap/base.css index 3545188e..ded44fc6 100644 --- a/umap/static/umap/base.css +++ b/umap/static/umap/base.css @@ -756,146 +756,6 @@ input[type=hidden].blur + [type="button"] { } -/* *********** */ -/* Alerts */ -/* *********** */ -#umap-alert-container { - min-height: 46px; - line-height: 46px; - padding-left: 10px; - width: calc(100% - 500px); - position: absolute; - top: -46px; - left: 250px; /* Keep save/cancel button accessible. */ - right: 250px; - box-shadow: 0 1px 7px #999999; - visibility: hidden; - background: none repeat scroll 0 0 rgba(20, 22, 23, 0.8); - font-weight: bold; - color: #fff; - font-size: 0.8em; - z-index: 1012; - border-radius: 2px; -} -#umap-alert-container.error { - background-color: #c60f13; -} -.umap-alert #umap-alert-container { - visibility: visible; - top: 23px; -} -.umap-alert-container .umap-action { - margin-left: 10px; - background-color: #fff; - color: #000; - padding: 5px; - border-radius: 4px; -} -.umap-alert-container .umap-action:hover { - color: #000; -} -.umap-alert-container .error .umap-action { - background-color: #666; - color: #eee; -} -.umap-alert-container .error .umap-action:hover { - color: #fff; -} -.umap-alert-container input { - padding: 5px; - border-radius: 4px; - width: 100%; -} - -/* *********** */ -/* Tooltip */ -/* *********** */ -#umap-tooltip-container { - line-height: 20px; - padding: 5px 10px; - width: auto; - position: absolute; - box-shadow: 0 1px 7px #999999; - display: none; - background-color: rgba(40, 40, 40, 0.8); - color: #eeeeec; - font-size: 0.8em; - border-radius: 2px; - z-index: 1011; - font-weight: normal; - max-width: 300px; -} -.umap-tooltip #umap-tooltip-container { - display: block; -} -#umap-tooltip-container.tooltip-top:after { - top: 100%; - left: calc(50% - 11px); - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - border-top-color: rgba(30, 30, 30, 0.8); - border-width: 11px; - margin-left: calc(-50% + 21px); -} -#umap-tooltip-container.tooltip-bottom:before { - top: -22px; - left: calc(50% - 11px); - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - border-top-color: rgba(30, 30, 30, 0.7); - border-width: 11px; - transform: rotate(180deg); -} -#umap-tooltip-container.tooltip.tooltip-left:after { - left: 100%; - top: 50%; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - border-color: rgba(136, 183, 213, 0); - border-left-color: #333; - border-width: 11px; - margin-top: -10px; -} - - - -/* *********** */ -/* Close link */ -/* *********** */ -#umap-alert-container .umap-close-link { - color: #fff; - float: right; - padding-right: 10px; - width: 100px; - line-height: 1; - margin: .5rem; - background-color: #202425; - font-size: .7rem; -} -#umap-alert-container .umap-close-icon { - background-position: -74px -55px; -} -#umap-alert-container .umap-alert-actions { - display: flex; - margin: 1rem; -} -#umap-alert-container .umap-alert-actions .umap-action { - margin-bottom: 0; -} - - /* *********** */ /* Various */ /* *********** */ @@ -913,15 +773,3 @@ input[type=hidden].blur + [type="button"] { height: 100vh; opacity: 0.5; } - - -/* *********** */ -/* Mobile */ -/* *********** */ -@media all and (orientation:portrait) { - #umap-alert-container { - width: 100%; - left: 0; - right: 0; - } -} diff --git a/umap/static/umap/css/alert.css b/umap/static/umap/css/alert.css new file mode 100644 index 00000000..05833a55 --- /dev/null +++ b/umap/static/umap/css/alert.css @@ -0,0 +1,75 @@ +#umap-alert-container { + min-height: 46px; + line-height: 46px; + padding-left: 10px; + width: calc(100% - 500px); + position: absolute; + top: -46px; + left: 250px; /* Keep save/cancel button accessible. */ + right: 250px; + box-shadow: 0 1px 7px #999999; + visibility: hidden; + background: none repeat scroll 0 0 rgba(20, 22, 23, 0.8); + font-weight: bold; + color: #fff; + font-size: 0.8em; + z-index: 1012; + border-radius: 2px; +} +#umap-alert-container.error { + background-color: #c60f13; +} +.umap-alert #umap-alert-container { + visibility: visible; + top: 23px; +} +#umap-alert-container .umap-action { + margin-left: 10px; + background-color: #fff; + color: #000; + padding: 5px; + border-radius: 4px; +} +#umap-alert-container .umap-action:hover { + color: #000; +} +#umap-alert-container .error .umap-action { + background-color: #666; + color: #eee; +} +#umap-alert-container .error .umap-action:hover { + color: #fff; +} +#umap-alert-container input { + padding: 5px; + border-radius: 4px; + width: 100%; +} +#umap-alert-container .umap-close-link { + color: #fff; + float: right; + padding-right: 10px; + width: 100px; + line-height: 1; + margin: .5rem; + background-color: #202425; + font-size: .7rem; +} +#umap-alert-container .umap-close-icon { + background-position: -74px -55px; +} +#umap-alert-container .umap-alert-actions { + display: flex; + margin: 1rem; +} +#umap-alert-container .umap-alert-actions .umap-action { + margin-bottom: 0; +} + +@media all and (orientation:portrait) { + #umap-alert-container { + width: 100%; + left: 0; + right: 0; + } +} diff --git a/umap/static/umap/css/dialog.css b/umap/static/umap/css/dialog.css new file mode 100644 index 00000000..b83061f6 --- /dev/null +++ b/umap/static/umap/css/dialog.css @@ -0,0 +1,17 @@ +.umap-dialog { + z-index: 10001; + margin: auto; + margin-top: 100px; + width: 50vw; + max-width: 100vw; + max-height: 50vh; + padding: 20px; + border: 1px solid #222; + background-color: var(--background-color); + color: var(--text-color); + border-radius: 5px; +} +.umap-dialog .umap-close-link { + float: right; + width: 100px; +} diff --git a/umap/static/umap/css/panel.css b/umap/static/umap/css/panel.css index 80f168b7..8dca758a 100644 --- a/umap/static/umap/css/panel.css +++ b/umap/static/umap/css/panel.css @@ -7,6 +7,7 @@ overflow-x: auto; z-index: 1010; background-color: var(--background-color); + color: var(--text-color); opacity: 0.98; cursor: initial; border-radius: 5px; @@ -14,7 +15,6 @@ } .panel.dark { border: 1px solid #222; - color: #efefef; } .panel.full { width: initial; diff --git a/umap/static/umap/css/tooltip.css b/umap/static/umap/css/tooltip.css new file mode 100644 index 00000000..caa475eb --- /dev/null +++ b/umap/static/umap/css/tooltip.css @@ -0,0 +1,59 @@ +#umap-tooltip-container { + line-height: 20px; + padding: 5px 10px; + width: auto; + position: absolute; + box-shadow: 0 1px 7px #999999; + display: none; + background-color: rgba(40, 40, 40, 0.8); + color: #eeeeec; + font-size: 0.8em; + border-radius: 2px; + z-index: 1011; + font-weight: normal; + max-width: 300px; +} +.umap-tooltip #umap-tooltip-container { + display: block; +} +#umap-tooltip-container.tooltip-top:after { + top: 100%; + left: calc(50% - 11px); + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-top-color: rgba(30, 30, 30, 0.8); + border-width: 11px; + margin-left: calc(-50% + 21px); +} +#umap-tooltip-container.tooltip-bottom:before { + top: -22px; + left: calc(50% - 11px); + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-top-color: rgba(30, 30, 30, 0.7); + border-width: 11px; + transform: rotate(180deg); +} +#umap-tooltip-container.tooltip.tooltip-left:after { + left: 100%; + top: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(136, 183, 213, 0); + border-left-color: #333; + border-width: 11px; + margin-top: -10px; +} + diff --git a/umap/static/umap/js/modules/global.js b/umap/static/umap/js/modules/global.js index f665ef52..42aff04e 100644 --- a/umap/static/umap/js/modules/global.js +++ b/umap/static/umap/js/modules/global.js @@ -2,7 +2,10 @@ import URLs from './urls.js' import Browser from './browser.js' import Facets from './facets.js' import Caption from './caption.js' -import { Panel, EditPanel, FullPanel } from './panel.js' +import { Panel, EditPanel, FullPanel } from './ui/panel.js' +import Alert from './ui/alert.js' +import Dialog from './ui/dialog.js' +import Tooltip from './ui/tooltip.js' import * as Utils from './utils.js' import { SCHEMA } from './schema.js' import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js' @@ -21,6 +24,9 @@ window.U = { Browser, Facets, Panel, + Alert, + Dialog, + Tooltip, EditPanel, FullPanel, Utils, diff --git a/umap/static/umap/js/modules/request.js b/umap/static/umap/js/modules/request.js index a12d5b76..fe392871 100644 --- a/umap/static/umap/js/modules/request.js +++ b/umap/static/umap/js/modules/request.js @@ -47,14 +47,18 @@ class BaseRequest { // In case of error, an alert is sent, but non 20X status are not handled // The consumer must check the response status by hand export class Request extends BaseRequest { - constructor(ui) { + constructor(alert) { super() - this.ui = ui + this.alert = alert + } + + fire(name, params) { + document.body.dispatchEvent(new CustomEvent(name, params)) } async _fetch(method, uri, headers, data) { const id = Math.random() - this.ui.fire('dataloading', { id: id }) + this.fire('dataloading', { id: id }) try { const response = await BaseRequest.prototype._fetch.call( this, @@ -68,7 +72,7 @@ export class Request extends BaseRequest { if (error instanceof NOKError) return this._onNOK(error) return this._onError(error) } finally { - this.ui.fire('dataload', { id: id }) + this.fire('dataload', { id: id }) } } @@ -81,7 +85,7 @@ export class Request extends BaseRequest { } _onError(error) { - this.ui.alert({ content: L._('Problem in the response'), level: 'error' }) + this.alert.open({ content: L._('Problem in the response'), level: 'error' }) } _onNOK(error) { @@ -127,9 +131,9 @@ export class ServerRequest extends Request { try { const data = await response.json() if (data.info) { - this.ui.alert({ content: data.info, level: 'info' }) + this.alert.open({ content: data.info, level: 'info' }) } else if (data.error) { - this.ui.alert({ content: data.error, level: 'error' }) + this.alert.open({ content: data.error, level: 'error' }) return this._onError(new Error(data.error)) } return [data, response, null] @@ -144,7 +148,7 @@ export class ServerRequest extends Request { _onNOK(error) { if (error.status === 403) { - this.ui.alert({ + this.alert.open({ content: error.message || L._('Action not allowed :('), level: 'error', }) diff --git a/umap/static/umap/js/modules/ui/alert.js b/umap/static/umap/js/modules/ui/alert.js new file mode 100644 index 00000000..b02c2d82 --- /dev/null +++ b/umap/static/umap/js/modules/ui/alert.js @@ -0,0 +1,82 @@ +import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js' +import { translate } from '../i18n.js' + +const ALERTS = [] +let ALERT_ID = null + +export default class Alert { + constructor(parent) { + this.parent = parent + this.container = DomUtil.create('div', 'with-transition', this.parent) + this.container.id = 'umap-alert-container' + DomEvent.disableClickPropagation(this.container) + DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu. + DomEvent.on(this.container, 'wheel', DomEvent.stopPropagation) + DomEvent.on(this.container, 'MozMousePixelScroll', DomEvent.stopPropagation) + } + + open(params) { + if (DomUtil.hasClass(this.parent, 'umap-alert')) ALERTS.push(params) + else this._open(params) + } + + _open(params) { + if (!params) { + if (ALERTS.length) params = ALERTS.pop() + else return + } + let timeoutID + const level_class = params.level && params.level == 'info' ? 'info' : 'error' + this.container.innerHTML = '' + DomUtil.addClass(this.parent, 'umap-alert') + DomUtil.addClass(this.container, level_class) + const close = () => { + if (timeoutID && timeoutID !== ALERT_ID) { + return + } // Another alert has been forced + this.container.innerHTML = '' + DomUtil.removeClass(this.parent, 'umap-alert') + DomUtil.removeClass(this.container, level_class) + if (timeoutID) window.clearTimeout(timeoutID) + this._open() + } + const closeButton = DomUtil.createButton( + 'umap-close-link', + this.container, + '', + close, + this + ) + DomUtil.create('i', 'umap-close-icon', closeButton) + const label = DomUtil.create('span', '', closeButton) + label.title = label.textContent = translate('Close') + DomUtil.element({ + tagName: 'div', + innerHTML: params.content, + parent: this.container, + }) + let action, el, input + const form = DomUtil.create('div', 'umap-alert-actions', this.container) + for (let action of params.actions || []) { + if (action.input) { + input = DomUtil.element({ + tagName: 'input', + parent: form, + className: 'umap-alert-input', + placeholder: action.input, + }) + } + el = DomUtil.createButton( + 'umap-action', + form, + action.label, + action.callback, + action.callbackContext + ) + DomEvent.on(el, 'click', close, this) + } + if (params.duration !== Infinity) { + ALERT_ID = timeoutID = window.setTimeout(close, params.duration || 3000) + } + } +} diff --git a/umap/static/umap/js/modules/ui/dialog.js b/umap/static/umap/js/modules/ui/dialog.js new file mode 100644 index 00000000..a1542103 --- /dev/null +++ b/umap/static/umap/js/modules/ui/dialog.js @@ -0,0 +1,44 @@ +import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js' +import { translate } from '../i18n.js' + +export default class Dialog { + constructor(parent) { + this.parent = parent + this.container = DomUtil.create( + 'dialog', + 'umap-dialog', + this.parent + ) + DomEvent.disableClickPropagation(this.container) + DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu. + DomEvent.on(this.container, 'wheel', DomEvent.stopPropagation) + DomEvent.on(this.container, 'MozMousePixelScroll', DomEvent.stopPropagation) + } + + get visible() { + return this.container.open + } + + close() { + this.container.close() + } + + open({className, content, modal} = {}) { + this.container.innerHTML = '' + if (modal) this.container.showModal() + else this.container.show() + if (className) { + this.container.classList.add(className) + } + const closeButton = DomUtil.createButton( + 'umap-close-link', + this.container, + '', + () => this.container.close() + ) + DomUtil.createIcon(closeButton, 'icon-close') + const label = DomUtil.create('span', '', closeButton) + label.title = label.textContent = translate('Close') + this.container.appendChild(content) + } +} diff --git a/umap/static/umap/js/modules/panel.js b/umap/static/umap/js/modules/ui/panel.js similarity index 95% rename from umap/static/umap/js/modules/panel.js rename to umap/static/umap/js/modules/ui/panel.js index 48c17e41..5694a080 100644 --- a/umap/static/umap/js/modules/panel.js +++ b/umap/static/umap/js/modules/ui/panel.js @@ -1,5 +1,5 @@ -import { DomUtil, DomEvent } from '../../vendors/leaflet/leaflet-src.esm.js' -import { translate } from './i18n.js' +import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js' +import { translate } from '../i18n.js' export class Panel { constructor(map) { diff --git a/umap/static/umap/js/modules/ui/tooltip.js b/umap/static/umap/js/modules/ui/tooltip.js new file mode 100644 index 00000000..6573421d --- /dev/null +++ b/umap/static/umap/js/modules/ui/tooltip.js @@ -0,0 +1,116 @@ +import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js' +import { translate } from '../i18n.js' + +export default class Tooltip { + constructor(parent) { + this.parent = parent + this.container = DomUtil.create('div', 'with-transition', this.parent) + this.container.id = 'umap-tooltip-container' + DomEvent.disableClickPropagation(this.container) + DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu. + DomEvent.on(this.container, 'wheel', DomEvent.stopPropagation) + DomEvent.on(this.container, 'MozMousePixelScroll', DomEvent.stopPropagation) + } + + open(opts) { + function showIt() { + if (opts.anchor && opts.position === 'top') { + this.anchorTop(opts.anchor) + } else if (opts.anchor && opts.position === 'left') { + this.anchorLeft(opts.anchor) + } else if (opts.anchor && opts.position === 'bottom') { + this.anchorBottom(opts.anchor) + } else { + this.anchorAbsolute() + } + L.DomUtil.addClass(this.parent, 'umap-tooltip') + this.container.innerHTML = U.Utils.escapeHTML(opts.content) + } + this.TOOLTIP_ID = window.setTimeout(L.bind(showIt, this), opts.delay || 0) + const id = this.TOOLTIP_ID + const closeIt = () => { + this.close(id) + } + if (opts.anchor) { + L.DomEvent.once(opts.anchor, 'mouseout', closeIt) + } + if (opts.duration !== Infinity) { + window.setTimeout(closeIt, opts.duration || 3000) + } + } + + anchorAbsolute() { + this.container.className = '' + const left = + this.parent.offsetLeft + + this.parent.clientWidth / 2 - + this.container.clientWidth / 2, + top = this.parent.offsetTop + 75 + this.setPosition({ top: top, left: left }) + } + + anchorTop(el) { + this.container.className = 'tooltip-top' + const coords = this.getPosition(el) + this.setPosition({ + left: coords.left - 10, + bottom: this.getDocHeight() - coords.top + 11, + }) + } + + anchorBottom(el) { + this.container.className = 'tooltip-bottom' + const coords = this.getPosition(el) + this.setPosition({ + left: coords.left, + top: coords.bottom + 11, + }) + } + + anchorLeft(el) { + this.container.className = 'tooltip-left' + const coords = this.getPosition(el) + this.setPosition({ + top: coords.top, + right: document.documentElement.offsetWidth - coords.left + 11, + }) + } + + close(id) { + // Clear timetout even if a new tooltip has been added + // in the meantime. Eg. after a mouseout from the anchor. + window.clearTimeout(id) + if (id && id !== this.TOOLTIP_ID) return + this.container.className = '' + this.container.innerHTML = '' + this.setPosition({}) + L.DomUtil.removeClass(this.parent, 'umap-tooltip') + } + + getPosition(el) { + return el.getBoundingClientRect() + } + + setPosition(coords) { + if (coords.left) this.container.style.left = `${coords.left}px` + else this.container.style.left = 'initial' + if (coords.right) this.container.style.right = `${coords.right}px` + else this.container.style.right = 'initial' + if (coords.top) this.container.style.top = `${coords.top}px` + else this.container.style.top = 'initial' + if (coords.bottom) this.container.style.bottom = `${coords.bottom}px` + else this.container.style.bottom = 'initial' + } + + getDocHeight() { + const D = document + return Math.max( + D.body.scrollHeight, + D.documentElement.scrollHeight, + D.body.offsetHeight, + D.documentElement.offsetHeight, + D.body.clientHeight, + D.documentElement.clientHeight + ) + } +} diff --git a/umap/static/umap/js/umap.autocomplete.js b/umap/static/umap/js/umap.autocomplete.js index a1aa930a..87c9561b 100644 --- a/umap/static/umap/js/umap.autocomplete.js +++ b/umap/static/umap/js/umap.autocomplete.js @@ -12,8 +12,8 @@ U.AutoComplete = L.Class.extend({ initialize: function (el, options) { this.el = el - const ui = new U.UI(document.querySelector('header')) - this.server = new U.ServerRequest(ui) + const alert = new U.Alert(document.querySelector('header')) + this.server = new U.ServerRequest(alert) L.setOptions(this, options) let CURRENT = null try { diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 33569a92..4ae4ffbc 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -394,7 +394,7 @@ U.EditControl = L.Control.extend({ enableEditing, 'mouseover', function () { - map.ui.tooltip({ + map.tooltip.open({ content: map.help.displayLabel('TOGGLE_EDIT'), anchor: enableEditing, position: 'bottom', @@ -693,7 +693,7 @@ const ControlsMixin = { nameButton, 'mouseover', function () { - this.ui.tooltip({ + this.tooltip.open({ content: L._('Edit the title of the map'), anchor: nameButton, position: 'bottom', @@ -714,7 +714,7 @@ const ControlsMixin = { shareStatusButton, 'mouseover', function () { - this.ui.tooltip({ + this.tooltip.open({ content: L._('Update who can see and edit the map'), anchor: shareStatusButton, position: 'bottom', @@ -763,7 +763,7 @@ const ControlsMixin = { controlEditCancel, 'mouseover', function () { - this.ui.tooltip({ + this.tooltip.open({ content: this.help.displayLabel('CANCEL'), anchor: controlEditCancel, position: 'bottom', @@ -784,7 +784,7 @@ const ControlsMixin = { controlEditDisable, 'mouseover', function () { - this.ui.tooltip({ + this.tooltip.open({ content: this.help.displayLabel('PREVIEW'), anchor: controlEditDisable, position: 'bottom', @@ -805,7 +805,7 @@ const ControlsMixin = { controlEditSave, 'mouseover', function () { - this.ui.tooltip({ + this.tooltip.open({ content: this.help.displayLabel('SAVE'), anchor: controlEditSave, position: 'bottom', @@ -1048,7 +1048,6 @@ U.Locate = L.Control.Locate.extend({ if (!this._container || !this._container.parentNode) return return L.Control.Locate.prototype.remove.call(this) }, - }) U.Search = L.PhotonSearch.extend({ @@ -1087,7 +1086,7 @@ U.Search = L.PhotonSearch.extend({ if (latlng.isValid()) { this.reverse.doReverse(latlng) } else { - this.map.ui.alert({ content: 'Invalid latitude or longitude', mode: 'error' }) + this.map.alert.open({ content: 'Invalid latitude or longitude', mode: 'error' }) } return } @@ -1250,7 +1249,7 @@ U.Editable = L.Editable.extend({ L.Editable.prototype.initialize.call(this, map, options) this.on('editable:drawing:click editable:drawing:move', this.drawingTooltip) this.on('editable:drawing:end', (e) => { - this.closeTooltip() + this.map.tooltip.close() // Leaflet.Editable will delete the drawn shape if invalid // (eg. line has only one drawn point) // So let's check if the layer has no more shape @@ -1314,7 +1313,7 @@ U.Editable = L.Editable.extend({ drawingTooltip: function (e) { if (e.layer instanceof L.Marker && e.type == 'editable:drawing:start') { - this.map.ui.tooltip({ content: L._('Click to add a marker') }) + this.map.tooltip.open({ content: L._('Click to add a marker') }) } if (!(e.layer instanceof L.Polyline)) { // only continue with Polylines and Polygons @@ -1357,7 +1356,7 @@ U.Editable = L.Editable.extend({ } } if (content) { - this.map.ui.tooltip({ content: content }) + this.map.tooltip.open({ content: content }) } }, diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js index cf196478..149a6681 100644 --- a/umap/static/umap/js/umap.core.js +++ b/umap/static/umap/js/umap.core.js @@ -346,22 +346,6 @@ U.Help = L.Class.extend({ initialize: function (map) { this.map = map - this.box = L.DomUtil.create( - 'div', - 'umap-help-box with-transition dark', - document.body - ) - const closeButton = L.DomUtil.createButton( - 'umap-close-link', - this.box, - '', - this.hide, - this - ) - L.DomUtil.add('i', 'umap-close-icon', closeButton) - const label = L.DomUtil.create('span', '', closeButton) - label.title = label.textContent = L._('Close') - this.content = L.DomUtil.create('div', 'umap-help-content', this.box) this.isMacOS = /mac/i.test( // eslint-disable-next-line compat/compat -- Fallback available. navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform @@ -377,20 +361,12 @@ U.Help = L.Class.extend({ }, show: function () { - this.content.innerHTML = '' + const container = L.DomUtil.add('div') for (let i = 0, name; i < arguments.length; i++) { name = arguments[i] - L.DomUtil.add('div', 'umap-help-entry', this.content, this.resolve(name)) + L.DomUtil.add('div', 'umap-help-entry', container, this.resolve(name)) } - L.DomUtil.addClass(document.body, 'umap-help-on') - }, - - hide: function () { - L.DomUtil.removeClass(document.body, 'umap-help-on') - }, - - visible: function () { - return L.DomUtil.hasClass(document.body, 'umap-help-on') + this.map.dialog.open({ content: container, className: 'dark' }) }, resolve: function (name) { @@ -424,16 +400,15 @@ U.Help = L.Class.extend({ }, edit: function () { - const container = L.DomUtil.create('div', ''), - self = this, - title = L.DomUtil.create('h3', '', container), - actionsContainer = L.DomUtil.create('ul', 'umap-edit-actions', container) + const container = L.DomUtil.create('div', '') + const title = L.DomUtil.create('h3', '', container) + const actionsContainer = L.DomUtil.create('ul', 'umap-edit-actions', container) const addAction = (action) => { const actionContainer = L.DomUtil.add('li', '', actionsContainer) L.DomUtil.add('i', action.options.className, actionContainer), L.DomUtil.add('span', '', actionContainer, action.options.tooltip) L.DomEvent.on(actionContainer, 'click', action.addHooks, action) - L.DomEvent.on(actionContainer, 'click', self.hide, self) + L.DomEvent.on(actionContainer, 'click', this.map.dialog.close, this.map.dialog) } title.textContent = L._('Where do we go from here?') for (const id in this.map.helpMenuActions) { diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index 7c81921b..27c835e7 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -726,7 +726,7 @@ U.Marker = L.Marker.extend({ const builder = new U.FormBuilder(this, coordinatesOptions, { callback: function () { if (!this._latlng.isValid()) { - this.map.ui.alert({ + this.map.alert.open({ content: L._('Invalid latitude or longitude'), level: 'error', }) @@ -878,9 +878,9 @@ U.PathMixin = { _onMouseOver: function () { if (this.map.measureTools && this.map.measureTools.enabled()) { - this.map.ui.tooltip({ content: this.getMeasure(), anchor: this }) + this.map.tooltip.open({ content: this.getMeasure(), anchor: this }) } else if (this.map.editEnabled && !this.map.editedFeature) { - this.map.ui.tooltip({ content: L._('Click to edit'), anchor: this }) + this.map.tooltip.open({ content: L._('Click to edit'), anchor: this }) } }, @@ -928,7 +928,7 @@ U.PathMixin = { items.push({ text: L._('Display measure'), callback: function () { - this.map.ui.alert({ content: this.getMeasure(), level: 'info' }) + this.map.alert.open({ content: this.getMeasure(), level: 'info' }) }, context: this, }) diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index c52ad146..4915dfb3 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -78,7 +78,7 @@ L.FormBuilder.Element.include({ info, 'mouseover', function () { - this.builder.map.ui.tooltip({ + this.builder.map.tooltip.open({ anchor: info, content: this.options.helpTooltip, position: 'top', diff --git a/umap/static/umap/js/umap.importer.js b/umap/static/umap/js/umap.importer.js index 037c8452..9ee1bfaa 100644 --- a/umap/static/umap/js/umap.importer.js +++ b/umap/static/umap/js/umap.importer.js @@ -161,7 +161,7 @@ U.Importer = L.Class.extend({ } } else { if (!type) - return this.map.ui.alert({ + return this.map.alert.open({ content: L._('Please choose a format'), level: 'error', }) @@ -169,7 +169,7 @@ U.Importer = L.Class.extend({ try { this.map.importRaw(this.rawInput.value, type) } catch (e) { - this.ui.alert({ content: L._('Invalid umap data'), level: 'error' }) + this.alert.open({ content: L._('Invalid umap data'), level: 'error' }) console.error(e) } } else { diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index 6fbd9e65..054ff897 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -57,15 +57,17 @@ U.Map = L.Map.extend({ this.urls = new U.URLs(this.options.urls) this.panel = new U.Panel(this) + this.alert = new U.Alert(this._controlContainer) + this.tooltip = new U.Tooltip(this._controlContainer) + this.dialog = new U.Dialog(this._controlContainer) if (this.hasEditMode()) { this.editPanel = new U.EditPanel(this) this.fullPanel = new U.FullPanel(this) } - this.ui = new U.UI(this._container) - this.ui.on('dataloading', (e) => this.fire('dataloading', e)) - this.ui.on('dataload', (e) => this.fire('dataload', e)) - this.server = new U.ServerRequest(this.ui) - this.request = new U.Request(this.ui) + L.DomEvent.on(document.body, 'dataloading', (e) => this.fire('dataloading', e)) + L.DomEvent.on(document.body, 'dataload', (e) => this.fire('dataload', e)) + this.server = new U.ServerRequest(this.alert) + this.request = new U.Request(this.alert) this.initLoader() this.name = this.options.name @@ -359,7 +361,7 @@ U.Map = L.Map.extend({ icon: 'umap-fake-class', iconLoading: 'umap-fake-class', flyTo: this.options.easing, - onLocationError: (err) => this.ui.alert({ content: err.message }), + onLocationError: (err) => this.alert.open({ content: err.message }), }) this._controls.fullscreen = new L.Control.Fullscreen({ title: { false: L._('View Fullscreen'), true: L._('Exit Fullscreen') }, @@ -392,7 +394,9 @@ U.Map = L.Map.extend({ }, renderControls: function () { - const hasSlideshow = Boolean(this.options.slideshow && this.options.slideshow.active) + const hasSlideshow = Boolean( + this.options.slideshow && this.options.slideshow.active + ) const barEnabled = this.options.captionBar || hasSlideshow document.body.classList.toggle('umap-caption-bar-enabled', barEnabled) document.body.classList.toggle('umap-slideshow-enabled', hasSlideshow) @@ -522,8 +526,8 @@ U.Map = L.Map.extend({ L.DomEvent.stop(e) this.search() } else if (e.keyCode === U.Keys.ESC) { - if (this.help.visible()) { - this.help.hide() + if (this.dialog.visible) { + this.dialog.close() } else { this.panel.close() this.editPanel?.close() @@ -641,7 +645,7 @@ U.Map = L.Map.extend({ } catch (e) { console.error(e) this.removeLayer(tilelayer) - this.ui.alert({ + this.alert.open({ content: `${L._('Error in the tilelayer URL')}: ${tilelayer._url}`, level: 'error', }) @@ -676,7 +680,7 @@ U.Map = L.Map.extend({ } catch (e) { this.removeLayer(overlay) console.error(e) - this.ui.alert({ + this.alert.open({ content: `${L._('Error in the overlay URL')}: ${overlay._url}`, level: 'error', }) @@ -799,7 +803,7 @@ U.Map = L.Map.extend({ if (this.options.umap_id) { // We do not want an extra message during the map creation // to avoid the double notification/alert. - this.ui.alert({ + this.alert.open({ content: L._('The zoom and center have been modified.'), level: 'info', }) @@ -842,7 +846,7 @@ U.Map = L.Map.extend({ processFileToImport: function (file, layer, type) { type = type || U.Utils.detectFileType(file) if (!type) { - this.ui.alert({ + this.alert.open({ content: L._('Unable to detect format of file {filename}', { filename: file.name, }), @@ -899,7 +903,7 @@ U.Map = L.Map.extend({ self.importRaw(rawData) } catch (e) { console.error('Error importing data', e) - self.ui.alert({ + self.alert.open({ content: L._('Invalid umap data in {filename}', { filename: file.name }), level: 'error', }) @@ -1030,7 +1034,7 @@ U.Map = L.Map.extend({ label: L._('Copy link'), callback: () => { L.Util.copyToClipboard(data.permissions.anonymous_edit_url) - this.ui.alert({ + this.alert.open({ content: L._('Secret edit link copied to clipboard!'), level: 'info', }) @@ -1058,7 +1062,7 @@ U.Map = L.Map.extend({ history.pushState({}, this.options.name, data.url) else window.location = data.url alert.content = data.info || alert.content - this.once('saved', () => this.ui.alert(alert)) + this.once('saved', () => this.alert.open(alert)) this.permissions.save() } }, @@ -1079,7 +1083,7 @@ U.Map = L.Map.extend({ }, sendEditLink: async function () { - const input = this.ui._alert.querySelector('input') + const input = this.alert.container.querySelector('input') const email = input.value const formData = new FormData() @@ -1091,7 +1095,7 @@ U.Map = L.Map.extend({ star: async function () { if (!this.options.umap_id) - return this.ui.alert({ + return this.alert.open({ content: L._('Please save the map first'), level: 'error', }) @@ -1102,7 +1106,7 @@ U.Map = L.Map.extend({ let msg = data.starred ? L._('Map has been starred') : L._('Map has been unstarred') - this.ui.alert({ content: msg, level: 'info' }) + this.alert.open({ content: msg, level: 'info' }) this.renderControls() } }, diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 5c0606fc..02207e81 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -865,8 +865,8 @@ U.DataLayer = L.Evented.extend({ isRemoteLayer: function () { return Boolean( this.options.remoteData && - this.options.remoteData.url && - this.options.remoteData.format + this.options.remoteData.url && + this.options.remoteData.format ) }, @@ -965,7 +965,7 @@ U.DataLayer = L.Evented.extend({ message: err[0].message, }) } - this.map.ui.alert({ content: message, level: 'error', duration: 10000 }) + this.map.alert.open({ content: message, level: 'error', duration: 10000 }) console.error(err) } if (result && result.features.length) { @@ -992,7 +992,7 @@ U.DataLayer = L.Evented.extend({ const gj = JSON.parse(c) callback(gj) } catch (err) { - this.map.ui.alert({ content: `Invalid JSON file: ${err}` }) + this.map.alert.open({ content: `Invalid JSON file: ${err}` }) return } } @@ -1050,7 +1050,7 @@ U.DataLayer = L.Evented.extend({ return this.geojsonToFeatures(geometry.geometries) default: - this.map.ui.alert({ + this.map.alert.open({ content: L._('Skipping unknown geometry.type: {type}', { type: geometry.type || 'undefined', }), @@ -1641,7 +1641,7 @@ U.DataLayer = L.Evented.extend({ label: L._('Cancel'), }, ] - this.map.ui.alert({ + this.map.alert.open({ content: msg, level: 'error', duration: 100000, diff --git a/umap/static/umap/js/umap.permissions.js b/umap/static/umap/js/umap.permissions.js index 663d9528..e9eab799 100644 --- a/umap/static/umap/js/umap.permissions.js +++ b/umap/static/umap/js/umap.permissions.js @@ -53,7 +53,7 @@ U.MapPermissions = L.Class.extend({ edit: function () { if (this.map.options.editMode !== 'advanced') return if (!this.map.options.umap_id) - return this.map.ui.alert({ + return this.map.alert.open({ content: L._('Please save the map first'), level: 'info', }) @@ -139,7 +139,7 @@ U.MapPermissions = L.Class.extend({ const [data, response, error] = await this.map.server.post(this.getAttachUrl()) if (!error) { this.options.owner = this.map.options.user - this.map.ui.alert({ + this.map.alert.open({ content: L._('Map has been attached to your account'), level: 'info', }) diff --git a/umap/static/umap/js/umap.tableeditor.js b/umap/static/umap/js/umap.tableeditor.js index 4863a988..36515a60 100644 --- a/umap/static/umap/js/umap.tableeditor.js +++ b/umap/static/umap/js/umap.tableeditor.js @@ -83,7 +83,7 @@ U.TableEditor = L.Class.extend({ validateName: function (name) { if (name.indexOf('.') !== -1) { - this.datalayer.map.ui.alert({ + this.datalayer.map.alert.open({ content: L._('Invalide property name: {name}', { name: name }), level: 'error', }) diff --git a/umap/static/umap/js/umap.ui.js b/umap/static/umap/js/umap.ui.js deleted file mode 100644 index 8cadc752..00000000 --- a/umap/static/umap/js/umap.ui.js +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Modals - */ -U.UI = L.Evented.extend({ - ALERTS: Array(), - ALERT_ID: null, - TOOLTIP_ID: null, - - initialize: function (parent) { - this.parent = parent - this.container = L.DomUtil.create('div', 'leaflet-ui-container', this.parent) - L.DomEvent.disableClickPropagation(this.container) - L.DomEvent.on(this.container, 'contextmenu', L.DomEvent.stopPropagation) // Do not activate our custom context menu. - L.DomEvent.on(this.container, 'wheel', L.DomEvent.stopPropagation) - L.DomEvent.on(this.container, 'MozMousePixelScroll', L.DomEvent.stopPropagation) - this._alert = L.DomUtil.create('div', 'with-transition', this.container) - this._alert.id = 'umap-alert-container' - this._tooltip = L.DomUtil.create('div', '', this.container) - this._tooltip.id = 'umap-tooltip-container' - }, - - alert: function (e) { - if (L.DomUtil.hasClass(this.parent, 'umap-alert')) this.ALERTS.push(e) - else this.popAlert(e) - }, - - popAlert: function (e) { - if (!e) { - if (this.ALERTS.length) e = this.ALERTS.pop() - else return - } - let timeoutID - const level_class = e.level && e.level == 'info' ? 'info' : 'error' - this._alert.innerHTML = '' - L.DomUtil.addClass(this.parent, 'umap-alert') - L.DomUtil.addClass(this._alert, level_class) - const close = () => { - if (timeoutID && timeoutID !== this.ALERT_ID) { - return - } // Another alert has been forced - this._alert.innerHTML = '' - L.DomUtil.removeClass(this.parent, 'umap-alert') - L.DomUtil.removeClass(this._alert, level_class) - if (timeoutID) window.clearTimeout(timeoutID) - this.popAlert() - } - const closeButton = L.DomUtil.createButton( - 'umap-close-link', - this._alert, - '', - close, - this - ) - L.DomUtil.create('i', 'umap-close-icon', closeButton) - const label = L.DomUtil.create('span', '', closeButton) - label.title = label.textContent = L._('Close') - L.DomUtil.element({ tagName: 'div', innerHTML: e.content, parent: this._alert }) - if (e.actions) { - let action, el, input - const form = L.DomUtil.create('div', 'umap-alert-actions', this._alert) - for (let i = 0; i < e.actions.length; i++) { - action = e.actions[i] - if (action.input) { - input = L.DomUtil.element({ - tagName: 'input', - parent: form, - className: 'umap-alert-input', - placeholder: action.input, - }) - } - el = L.DomUtil.createButton( - 'umap-action', - form, - action.label, - action.callback, - action.callbackContext || this.map - ) - L.DomEvent.on(el, 'click', close, this) - } - } - if (e.duration !== Infinity) { - this.ALERT_ID = timeoutID = window.setTimeout( - L.bind(close, this), - e.duration || 3000 - ) - } - }, - - tooltip: function (opts) { - function showIt() { - if (opts.anchor && opts.position === 'top') { - this.anchorTooltipTop(opts.anchor) - } else if (opts.anchor && opts.position === 'left') { - this.anchorTooltipLeft(opts.anchor) - } else if (opts.anchor && opts.position === 'bottom') { - this.anchorTooltipBottom(opts.anchor) - } else { - this.anchorTooltipAbsolute() - } - L.DomUtil.addClass(this.parent, 'umap-tooltip') - this._tooltip.innerHTML = U.Utils.escapeHTML(opts.content) - } - this.TOOLTIP_ID = window.setTimeout(L.bind(showIt, this), opts.delay || 0) - const id = this.TOOLTIP_ID - function closeIt() { - this.closeTooltip(id) - } - if (opts.anchor) { - L.DomEvent.once(opts.anchor, 'mouseout', closeIt, this) - } - if (opts.duration !== Infinity) { - window.setTimeout(L.bind(closeIt, this), opts.duration || 3000) - } - }, - - anchorTooltipAbsolute: function () { - this._tooltip.className = '' - const left = - this.parent.offsetLeft + - this.parent.clientWidth / 2 - - this._tooltip.clientWidth / 2, - top = this.parent.offsetTop + 75 - this.setTooltipPosition({ top: top, left: left }) - }, - - anchorTooltipTop: function (el) { - this._tooltip.className = 'tooltip-top' - const coords = this.getPosition(el) - this.setTooltipPosition({ - left: coords.left - 10, - bottom: this.getDocHeight() - coords.top + 11, - }) - }, - - anchorTooltipBottom: function (el) { - this._tooltip.className = 'tooltip-bottom' - const coords = this.getPosition(el) - this.setTooltipPosition({ - left: coords.left, - top: coords.bottom + 11, - }) - }, - - anchorTooltipLeft: function (el) { - this._tooltip.className = 'tooltip-left' - const coords = this.getPosition(el) - this.setTooltipPosition({ - top: coords.top, - right: document.documentElement.offsetWidth - coords.left + 11, - }) - }, - - closeTooltip: function (id) { - // Clear timetout even if a new tooltip has been added - // in the meantime. Eg. after a mouseout from the anchor. - window.clearTimeout(id) - if (id && id !== this.TOOLTIP_ID) return - this._tooltip.className = '' - this._tooltip.innerHTML = '' - this.setTooltipPosition({}) - L.DomUtil.removeClass(this.parent, 'umap-tooltip') - }, - - getPosition: function (el) { - return el.getBoundingClientRect() - }, - - setTooltipPosition: function (coords) { - if (coords.left) this._tooltip.style.left = `${coords.left}px` - else this._tooltip.style.left = 'initial' - if (coords.right) this._tooltip.style.right = `${coords.right}px` - else this._tooltip.style.right = 'initial' - if (coords.top) this._tooltip.style.top = `${coords.top}px` - else this._tooltip.style.top = 'initial' - if (coords.bottom) this._tooltip.style.bottom = `${coords.bottom}px` - else this._tooltip.style.bottom = 'initial' - }, - - getDocHeight: function () { - const D = document - return Math.max( - D.body.scrollHeight, - D.documentElement.scrollHeight, - D.body.offsetHeight, - D.documentElement.offsetHeight, - D.body.clientHeight, - D.documentElement.clientHeight - ) - }, -}) diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index d2944a49..c584f077 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -390,24 +390,6 @@ ul.photon-autocomplete { /* ********************************* */ /* Help Lightbox */ /* ********************************* */ -.umap-help-box { - z-index: 10001; - position: absolute; - margin: 0 calc(50% - 500px/2); - width: 500px; - max-width: 100vw; - padding: 40px 20px; - border: 1px solid #222; - background-color: var(--color-darkGray); - color: #efefef; - font-size: 0.8em; - visibility: hidden; - top: -100%; -} -.umap-help-box .umap-close-link { - float: right; - width: 100px; -} .umap-help-button { display: inline-block; width: 16px; @@ -426,10 +408,6 @@ ul.photon-autocomplete { .dark .umap-help-button { background-image: url('./img/16-white.svg'); } -.umap-help-on .umap-help-box { - visibility: visible; - top: 100px; -} .umap-help-entry + .umap-help-entry { margin-top: 10px; border-top: 1px solid #aaa; diff --git a/umap/static/umap/vars.css b/umap/static/umap/vars.css index 16d5a7f7..c12c5114 100644 --- a/umap/static/umap/vars.css +++ b/umap/static/umap/vars.css @@ -11,6 +11,7 @@ --background-color: var(--color-light); --color-accent: var(--color-brightCyan); + --text-color: black; /* Buttons. */ --button-primary-background: var(--color-waterMint); @@ -29,4 +30,5 @@ } .dark { --background-color: var(--color-darkGray); + --text-color: #efefef; } diff --git a/umap/templates/umap/content.html b/umap/templates/umap/content.html index f061d8eb..01e3ed43 100644 --- a/umap/templates/umap/content.html +++ b/umap/templates/umap/content.html @@ -38,8 +38,8 @@ {{ block.super }} - diff --git a/umap/templates/umap/map_init.html b/umap/templates/umap/map_init.html index f3701593..3b3eb326 100644 --- a/umap/templates/umap/map_init.html +++ b/umap/templates/umap/map_init.html @@ -6,7 +6,7 @@ U.MAP = new U.Map("map", {{ map_settings|notag|safe }}) {% for m in messages %} {# We have just one, but we need to loop, as for messages API #} - U.MAP.ui.alert({ + U.MAP.alert.open({ content: "{{ m }}", level: "{{ m.tags }}", duration: 100000 diff --git a/umap/tests/integration/test_anonymous_owned_map.py b/umap/tests/integration/test_anonymous_owned_map.py index 5e266b8d..523589c2 100644 --- a/umap/tests/integration/test_anonymous_owned_map.py +++ b/umap/tests/integration/test_anonymous_owned_map.py @@ -164,7 +164,7 @@ def test_alert_message_after_create( page.goto(f"{live_server.url}/en/map/new") save = page.get_by_role("button", name="Save") expect(save).to_be_visible() - alert = page.locator(".umap-alert") + alert = page.locator("#umap-alert-container") expect(alert).to_be_hidden() with page.expect_response(re.compile(r".*/map/create/")): save.click() @@ -194,7 +194,7 @@ def test_alert_message_after_create( def test_email_sending_error_are_catched(tilelayer, page, live_server): page.goto(f"{live_server.url}/en/map/new") - alert = page.locator(".umap-alert") + alert = page.locator("#umap-alert-container") with page.expect_response(re.compile(r".*/map/create/")): page.get_by_role("button", name="Save").click() alert.get_by_placeholder("Email").fill("foo@bar.com") @@ -214,7 +214,7 @@ def test_alert_message_after_create_show_link_even_without_mail( page.goto(f"{live_server.url}/en/map/new") with page.expect_response(re.compile(r".*/map/create/")): page.get_by_role("button", name="Save").click() - alert = page.locator(".umap-alert") + alert = page.locator("#umap-alert-container") expect(alert).to_be_visible() expect( alert.get_by_text( diff --git a/umap/tests/integration/test_dashboard.py b/umap/tests/integration/test_dashboard.py index 86e8ff78..1d0d003f 100644 --- a/umap/tests/integration/test_dashboard.py +++ b/umap/tests/integration/test_dashboard.py @@ -28,7 +28,7 @@ def test_owner_can_delete_map_after_confirmation(map, live_server, login): def test_dashboard_map_preview(map, live_server, datalayer, login): page = login(map.owner) page.goto(f"{live_server.url}/en/me") - dialog = page.locator("dialog") + dialog = page.get_by_role("dialog") expect(dialog).to_be_hidden() button = page.get_by_role("button", name="Open preview") expect(button).to_be_visible() diff --git a/umap/tests/integration/test_import.py b/umap/tests/integration/test_import.py index c67d5f07..ac56d0ae 100644 --- a/umap/tests/integration/test_import.py +++ b/umap/tests/integration/test_import.py @@ -448,4 +448,4 @@ def test_import_csv_without_valid_latlon_headers(tilelayer, live_server, page): # FIXME do not create a layer expect(layers).to_have_count(1) expect(markers).to_have_count(0) - expect(page.locator(".umap-alert")).to_be_visible() + expect(page.locator("#umap-alert-container")).to_be_visible()