From 8ddc570e23bc403317b0509f50580af9d077ca86 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 22 May 2024 10:30:37 +0200 Subject: [PATCH 1/4] chore: move alert to dedicated module --- umap/static/umap/base.css | 89 ------------------- umap/static/umap/css/alert.css | 75 ++++++++++++++++ umap/static/umap/js/modules/global.js | 2 + umap/static/umap/js/modules/request.js | 20 +++-- umap/static/umap/js/modules/ui/alert.js | 82 +++++++++++++++++ umap/static/umap/js/umap.autocomplete.js | 4 +- umap/static/umap/js/umap.controls.js | 3 +- umap/static/umap/js/umap.features.js | 4 +- umap/static/umap/js/umap.importer.js | 4 +- umap/static/umap/js/umap.js | 35 ++++---- umap/static/umap/js/umap.layer.js | 12 +-- umap/static/umap/js/umap.permissions.js | 4 +- umap/static/umap/js/umap.tableeditor.js | 2 +- umap/static/umap/js/umap.ui.js | 71 --------------- umap/templates/umap/content.html | 4 +- umap/templates/umap/css.html | 1 + umap/templates/umap/map_init.html | 2 +- .../integration/test_anonymous_owned_map.py | 6 +- umap/tests/integration/test_import.py | 2 +- 19 files changed, 214 insertions(+), 208 deletions(-) create mode 100644 umap/static/umap/css/alert.css create mode 100644 umap/static/umap/js/modules/ui/alert.js diff --git a/umap/static/umap/base.css b/umap/static/umap/base.css index 3545188e..6079a78b 100644 --- a/umap/static/umap/base.css +++ b/umap/static/umap/base.css @@ -756,57 +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 */ /* *********** */ @@ -870,32 +819,6 @@ input[type=hidden].blur + [type="button"] { } - -/* *********** */ -/* 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 +836,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/js/modules/global.js b/umap/static/umap/js/modules/global.js index f665ef52..83bc9b1f 100644 --- a/umap/static/umap/js/modules/global.js +++ b/umap/static/umap/js/modules/global.js @@ -3,6 +3,7 @@ import Browser from './browser.js' import Facets from './facets.js' import Caption from './caption.js' import { Panel, EditPanel, FullPanel } from './panel.js' +import Alert from './ui/alert.js' import * as Utils from './utils.js' import { SCHEMA } from './schema.js' import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js' @@ -21,6 +22,7 @@ window.U = { Browser, Facets, Panel, + Alert, 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/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..f7f09711 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -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 } diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index 7c81921b..3ab590b8 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', }) @@ -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.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..c8f3f3bb 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -57,15 +57,16 @@ 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) 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 +360,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 +393,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) @@ -641,7 +644,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 +679,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 +802,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 +845,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 +902,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 +1033,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 +1061,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 +1082,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 +1094,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 +1105,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 index 8cadc752..9e0a2f4e 100644 --- a/umap/static/umap/js/umap.ui.js +++ b/umap/static/umap/js/umap.ui.js @@ -2,8 +2,6 @@ * Modals */ U.UI = L.Evented.extend({ - ALERTS: Array(), - ALERT_ID: null, TOOLTIP_ID: null, initialize: function (parent) { @@ -13,79 +11,10 @@ U.UI = L.Evented.extend({ 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') { 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 }} - From 8e446dbe708c0012830710deb35a5841b35d3788 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 22 May 2024 11:50:59 +0200 Subject: [PATCH 3/4] chore: move panel.js to ui/ subfolder --- umap/static/umap/js/modules/global.js | 2 +- umap/static/umap/js/modules/{ => ui}/panel.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename umap/static/umap/js/modules/{ => ui}/panel.js (95%) diff --git a/umap/static/umap/js/modules/global.js b/umap/static/umap/js/modules/global.js index 924854f9..226f67a5 100644 --- a/umap/static/umap/js/modules/global.js +++ b/umap/static/umap/js/modules/global.js @@ -2,7 +2,7 @@ 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 Tooltip from './ui/tooltip.js' import * as Utils from './utils.js' 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) { From 776d92e7cc6fd4c08d79da9908cd61b90def2d2a Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 22 May 2024 14:00:53 +0200 Subject: [PATCH 4/4] chore: add minimal dialog class to replace custom made help box --- .../fr/tutorials/5-multimedia-tooltips.md | 2 +- umap/static/umap/css/dialog.css | 17 +++++++ umap/static/umap/css/panel.css | 2 +- umap/static/umap/js/modules/global.js | 2 + umap/static/umap/js/modules/ui/dialog.js | 44 +++++++++++++++++++ umap/static/umap/js/umap.core.js | 39 +++------------- umap/static/umap/js/umap.js | 5 ++- umap/static/umap/map.css | 22 ---------- umap/static/umap/vars.css | 2 + umap/templates/umap/css.html | 1 + umap/tests/integration/test_dashboard.py | 2 +- 11 files changed, 79 insertions(+), 59 deletions(-) create mode 100644 umap/static/umap/css/dialog.css create mode 100644 umap/static/umap/js/modules/ui/dialog.js 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/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/js/modules/global.js b/umap/static/umap/js/modules/global.js index 226f67a5..42aff04e 100644 --- a/umap/static/umap/js/modules/global.js +++ b/umap/static/umap/js/modules/global.js @@ -4,6 +4,7 @@ import Facets from './facets.js' import Caption from './caption.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' @@ -24,6 +25,7 @@ window.U = { Facets, Panel, Alert, + Dialog, Tooltip, EditPanel, FullPanel, 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/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.js b/umap/static/umap/js/umap.js index 5dda78d1..054ff897 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -59,6 +59,7 @@ U.Map = L.Map.extend({ 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) @@ -525,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() diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index 8d872b92..f2e1971c 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/css.html b/umap/templates/umap/css.html index 4e12a22d..1233cbb6 100644 --- a/umap/templates/umap/css.html +++ b/umap/templates/umap/css.html @@ -31,4 +31,5 @@ + 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()