mirror of
https://github.com/umap-project/umap.git
synced 2025-04-29 03:42:37 +02:00
chore: Use web components to display alerts + a11y roles
This commit is contained in:
parent
c4306b4a7d
commit
5c2528900e
21 changed files with 400 additions and 343 deletions
|
@ -182,6 +182,7 @@ TEMPLATES = [
|
|||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"APP_DIRS": True,
|
||||
"DIRS": [os.path.join(PROJECT_DIR, STATIC_ROOT)],
|
||||
"OPTIONS": {
|
||||
"context_processors": (
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
#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;
|
||||
}
|
||||
}
|
45
umap/static/umap/js/components/alerts/alert.css
Normal file
45
umap/static/umap/js/components/alerts/alert.css
Normal file
|
@ -0,0 +1,45 @@
|
|||
[role="dialog"] {
|
||||
box-sizing: border-box;
|
||||
min-height: 46px;
|
||||
line-height: 46px;
|
||||
padding: var(--panel-gutter);
|
||||
position: absolute;
|
||||
box-shadow: 0 1px 7px #999999;
|
||||
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;
|
||||
margin-top: calc(var(--header-height) + var(--panel-gutter));
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
min-width: 80%;
|
||||
}
|
||||
[role="dialog"][data-level='error'] {
|
||||
background-color: #c60f13;
|
||||
}
|
||||
[role="dialog"] > div {
|
||||
margin: 0 auto;
|
||||
}
|
||||
[role="dialog"] [data-close] {
|
||||
color: #fff;
|
||||
max-width: 42px;
|
||||
line-height: initial;
|
||||
margin: 0;
|
||||
margin-left: var(--panel-gutter);
|
||||
background-color: #202425;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
[role="dialog"] [data-close] .icon + span {
|
||||
margin-left: 0;
|
||||
}
|
||||
#link-wrapper form {
|
||||
display: flex;
|
||||
}
|
||||
#link-wrapper form input[type='button'] {
|
||||
max-width: 100px;
|
||||
}
|
72
umap/static/umap/js/components/alerts/alert.html
Normal file
72
umap/static/umap/js/components/alerts/alert.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
{% load i18n static %}
|
||||
|
||||
<style type="text/css">
|
||||
@import "{% static 'umap/js/components/alerts/alert.css' %}";
|
||||
</style>
|
||||
|
||||
<umap-alert hidden>
|
||||
<div role="dialog">
|
||||
<div>
|
||||
<p role="alert"></p>
|
||||
</div>
|
||||
<button class="umap-close-link dark button" aria-label="{% translate "Close" %}" data-close>
|
||||
<i class="icon icon-16 icon-close"></i>
|
||||
</button>
|
||||
</div>
|
||||
</umap-alert>
|
||||
|
||||
<umap-alert-creation hidden>
|
||||
<div role="dialog">
|
||||
<div>
|
||||
<p role="alert"></p>
|
||||
<div id="link-wrapper">
|
||||
<form class="umap-alert-actions">
|
||||
<input type="url" name="url">
|
||||
<input type="button" value="{% translate "Copy link" %}">
|
||||
</form>
|
||||
</div>
|
||||
<div id="form-wrapper" hidden>
|
||||
<p>{% translate "You can also receive that URL by email:" %}</p>
|
||||
<form class="umap-alert-actions">
|
||||
<input type="email" name="email" class="umap-alert-input" required>
|
||||
<input type="submit" value="{% translate "Send me the link" %}" class="umap-action">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<button class="umap-close-link dark button" aria-label="{% translate "Close" %}" data-close>
|
||||
<i class="icon icon-16 icon-close"></i>
|
||||
</button>
|
||||
</div>
|
||||
</umap-alert-creation>
|
||||
|
||||
<umap-alert-choice hidden>
|
||||
<div role="dialog">
|
||||
<div>
|
||||
<p role="alert">
|
||||
{% blocktranslate %}
|
||||
Woops! Someone else seems to have edited the data. You can save anyway, but this will erase the changes made by others.
|
||||
{% endblocktranslate %}
|
||||
</p>
|
||||
<div id="choice-wrapper">
|
||||
<form class="umap-alert-actions">
|
||||
<button type="button" data-close>{% translate "Cancel" %}</button>
|
||||
<input type="submit" value="{% translate "Save anyway" %}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<button class="umap-close-link dark button" aria-label="{% translate "Close" %}" data-close>
|
||||
<i class="icon icon-16 icon-close"></i>
|
||||
</button>
|
||||
</div>
|
||||
</umap-alert-choice>
|
||||
|
||||
<script type="module">
|
||||
import {
|
||||
uMapAlert,
|
||||
uMapAlertCreation,
|
||||
uMapAlertChoice
|
||||
} from '{% static 'umap/js/components/alerts/alert.js' %}'
|
||||
customElements.define('umap-alert', uMapAlert)
|
||||
customElements.define('umap-alert-creation', uMapAlertCreation)
|
||||
customElements.define('umap-alert-choice', uMapAlertChoice)
|
||||
</script>
|
169
umap/static/umap/js/components/alerts/alert.js
Normal file
169
umap/static/umap/js/components/alerts/alert.js
Normal file
|
@ -0,0 +1,169 @@
|
|||
class uMapAlert extends HTMLElement {
|
||||
static info(message, duration = 5000) {
|
||||
const event = new CustomEvent('umap:alert', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: { message, duration },
|
||||
})
|
||||
document.dispatchEvent(event)
|
||||
}
|
||||
|
||||
// biome-ignore lint/style/useNumberNamespace: Number.Infinity returns undefined by default
|
||||
static error(message, duration = Infinity) {
|
||||
const event = new CustomEvent('umap:alert', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: { level: 'error', message, duration },
|
||||
})
|
||||
document.dispatchEvent(event)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.container = this.querySelector('[role="dialog"]')
|
||||
this.element = this.container.querySelector('[role="alert"]')
|
||||
}
|
||||
|
||||
_hide() {
|
||||
this.setAttribute('hidden', 'hidden')
|
||||
}
|
||||
|
||||
_show() {
|
||||
this.removeAttribute('hidden')
|
||||
}
|
||||
|
||||
_displayAlert(detail) {
|
||||
const { level = 'info', duration = 5000, message = '' } = detail
|
||||
this.container.dataset.level = level
|
||||
this.container.dataset.duration = duration
|
||||
this.element.textContent = message
|
||||
this._show()
|
||||
if (Number.isFinite(duration)) {
|
||||
setTimeout(() => {
|
||||
this._hide()
|
||||
}, duration)
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.addEventListener('click', (event) => {
|
||||
if (event.target.closest('[data-close]')) {
|
||||
this._hide()
|
||||
}
|
||||
})
|
||||
document.addEventListener('umap:alert', (event) => {
|
||||
this._displayAlert(event.detail)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class uMapAlertCreation extends uMapAlert {
|
||||
static info(
|
||||
message,
|
||||
// biome-ignore lint/style/useNumberNamespace: Number.Infinity returns undefined by default
|
||||
duration = Infinity,
|
||||
editLink = undefined,
|
||||
sendLink = undefined
|
||||
) {
|
||||
const event = new CustomEvent('umap:alert-creation', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: { message, duration, editLink, sendLink },
|
||||
})
|
||||
document.dispatchEvent(event)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.linkWrapper = this.container.querySelector('#link-wrapper')
|
||||
this.formWrapper = this.container.querySelector('#form-wrapper')
|
||||
}
|
||||
|
||||
_displayCreationAlert(detail) {
|
||||
const {
|
||||
level = 'info',
|
||||
duration = 5000,
|
||||
message = '',
|
||||
editLink = undefined,
|
||||
sendLink = undefined,
|
||||
} = detail
|
||||
uMapAlert.prototype._displayAlert.call(this, { level, duration, message })
|
||||
this.linkWrapper.querySelector('input[type="url"]').value = editLink
|
||||
const button = this.linkWrapper.querySelector('input[type="button"]')
|
||||
button.addEventListener('click', (event) => {
|
||||
event.preventDefault()
|
||||
L.Util.copyToClipboard(editLink)
|
||||
event.target.value = L._('✅ Copied!')
|
||||
})
|
||||
if (sendLink) {
|
||||
this.formWrapper.removeAttribute('hidden')
|
||||
const form = this.formWrapper.querySelector('form')
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault()
|
||||
const formData = new FormData(form)
|
||||
const server = new U.ServerRequest()
|
||||
this._hide()
|
||||
await server.post(sendLink, {}, formData)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.addEventListener('click', (event) => {
|
||||
if (event.target.closest('[data-close]')) {
|
||||
this._hide()
|
||||
}
|
||||
})
|
||||
document.addEventListener('umap:alert-creation', (event) => {
|
||||
this._displayCreationAlert(event.detail)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class uMapAlertChoice extends uMapAlert {
|
||||
static error(
|
||||
message,
|
||||
// biome-ignore lint/style/useNumberNamespace: Number.Infinity returns undefined by default
|
||||
duration = Infinity
|
||||
) {
|
||||
const event = new CustomEvent('umap:alert-choice', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: { message, duration },
|
||||
})
|
||||
document.dispatchEvent(event)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.choiceWrapper = this.container.querySelector('#choice-wrapper')
|
||||
}
|
||||
|
||||
_displayChoiceAlert(detail) {
|
||||
const { level = 'error', duration = 5000, message = '' } = detail
|
||||
uMapAlert.prototype._displayAlert.call(this, { level, duration, message })
|
||||
const button = this.choiceWrapper.querySelector('input[type="submit"]')
|
||||
button.addEventListener('click', (event) => {
|
||||
event.preventDefault()
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('umap:alert-choice-confirm', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.addEventListener('click', (event) => {
|
||||
if (event.target.closest('[data-close]')) {
|
||||
this._hide()
|
||||
}
|
||||
})
|
||||
document.addEventListener('umap:alert-choice', (event) => {
|
||||
this._displayChoiceAlert(event.detail)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export { uMapAlert, uMapAlertCreation, uMapAlertChoice }
|
|
@ -1,7 +1,6 @@
|
|||
import { DomUtil, DomEvent, setOptions } from '../../vendors/leaflet/leaflet-src.esm.js'
|
||||
import { translate } from './i18n.js'
|
||||
import { ServerRequest } from './request.js'
|
||||
import Alert from './ui/alert.js'
|
||||
|
||||
export class BaseAutocomplete {
|
||||
constructor(el, options) {
|
||||
|
@ -220,8 +219,7 @@ export class BaseAutocomplete {
|
|||
class BaseAjax extends BaseAutocomplete {
|
||||
constructor(el, options) {
|
||||
super(el, options)
|
||||
const alert = new Alert(document.querySelector('header'))
|
||||
this.server = new ServerRequest(alert)
|
||||
this.server = new ServerRequest()
|
||||
}
|
||||
optionToResult(option) {
|
||||
return {
|
||||
|
|
|
@ -3,7 +3,6 @@ import Browser from './browser.js'
|
|||
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'
|
||||
|
@ -14,6 +13,11 @@ import Orderable from './orderable.js'
|
|||
import Importer from './importer.js'
|
||||
import Help from './help.js'
|
||||
import { SyncEngine } from './sync/engine.js'
|
||||
import {
|
||||
uMapAlert as Alert,
|
||||
uMapAlertCreation as AlertCreation,
|
||||
uMapAlertChoice as AlertChoice,
|
||||
} from '../components/alerts/alert.js'
|
||||
|
||||
// Import modules and export them to the global scope.
|
||||
// For the not yet module-compatible JS out there.
|
||||
|
@ -21,6 +25,8 @@ import { SyncEngine } from './sync/engine.js'
|
|||
// By alphabetic order
|
||||
window.U = {
|
||||
Alert,
|
||||
AlertCreation,
|
||||
AlertChoice,
|
||||
AjaxAutocomplete,
|
||||
AjaxAutocompleteMultiple,
|
||||
Browser,
|
||||
|
|
|
@ -163,16 +163,12 @@ export default class Importer {
|
|||
this.map.processFileToImport(file, layer, type)
|
||||
}
|
||||
} else {
|
||||
if (!type)
|
||||
return this.map.alert.open({
|
||||
content: translate('Please choose a format'),
|
||||
level: 'error',
|
||||
})
|
||||
if (!type) return U.Alert.error(L._('Please choose a format'))
|
||||
if (this.rawInput.value && type === 'umap') {
|
||||
try {
|
||||
this.map.importRaw(this.rawInput.value, type)
|
||||
} catch (e) {
|
||||
this.alert.open({ content: translate('Invalid umap data'), level: 'error' })
|
||||
U.Alert.error(L._('Invalid umap data'))
|
||||
console.error(e)
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Uses `L._`` from Leaflet.i18n which we cannot import as a module yet
|
||||
import { DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
|
||||
import { translate } from './i18n.js'
|
||||
|
||||
export class RequestError extends Error {}
|
||||
|
||||
|
@ -47,11 +46,6 @@ 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(alert) {
|
||||
super()
|
||||
this.alert = alert
|
||||
}
|
||||
|
||||
fire(name, params) {
|
||||
document.body.dispatchEvent(new CustomEvent(name, params))
|
||||
}
|
||||
|
@ -85,7 +79,7 @@ export class Request extends BaseRequest {
|
|||
}
|
||||
|
||||
_onError(error) {
|
||||
this.alert.open({ content: L._('Problem in the response'), level: 'error' })
|
||||
U.Alert.error(translate('Problem in the response'))
|
||||
}
|
||||
|
||||
_onNOK(error) {
|
||||
|
@ -131,9 +125,9 @@ export class ServerRequest extends Request {
|
|||
try {
|
||||
const data = await response.json()
|
||||
if (data.info) {
|
||||
this.alert.open({ content: data.info, level: 'info' })
|
||||
U.Alert.info(data.info)
|
||||
} else if (data.error) {
|
||||
this.alert.open({ content: data.error, level: 'error' })
|
||||
U.Alert.error(data.error)
|
||||
return this._onError(new Error(data.error))
|
||||
}
|
||||
return [data, response, null]
|
||||
|
@ -148,10 +142,7 @@ export class ServerRequest extends Request {
|
|||
|
||||
_onNOK(error) {
|
||||
if (error.status === 403) {
|
||||
this.alert.open({
|
||||
content: error.message || L._('Action not allowed :('),
|
||||
level: 'error',
|
||||
})
|
||||
U.Alert.error(error.message || translate('Action not allowed :('))
|
||||
}
|
||||
return [{}, error.response, error]
|
||||
}
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1086,7 +1086,7 @@ U.Search = L.PhotonSearch.extend({
|
|||
if (latlng.isValid()) {
|
||||
this.reverse.doReverse(latlng)
|
||||
} else {
|
||||
this.map.alert.open({ content: 'Invalid latitude or longitude', mode: 'error' })
|
||||
U.Alert.error(L._('Invalid latitude or longitude'))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -764,10 +764,7 @@ U.Marker = L.Marker.extend({
|
|||
const builder = new U.FormBuilder(this, coordinatesOptions, {
|
||||
callback: function () {
|
||||
if (!this._latlng.isValid()) {
|
||||
this.map.alert.open({
|
||||
content: L._('Invalid latitude or longitude'),
|
||||
level: 'error',
|
||||
})
|
||||
U.Alert.error(L._('Invalid latitude or longitude'))
|
||||
builder.resetField('_latlng.lat')
|
||||
builder.resetField('_latlng.lng')
|
||||
}
|
||||
|
@ -966,7 +963,7 @@ U.PathMixin = {
|
|||
items.push({
|
||||
text: L._('Display measure'),
|
||||
callback: function () {
|
||||
this.map.alert.open({ content: this.getMeasure(), level: 'info' })
|
||||
U.Alert.info(this.getMeasure())
|
||||
},
|
||||
context: this,
|
||||
})
|
||||
|
|
|
@ -13,7 +13,7 @@ L.Map.mergeOptions({
|
|||
// we cannot rely on this because of the y is overriden by Leaflet
|
||||
// See https://github.com/Leaflet/Leaflet/pull/9201
|
||||
// And let's remove this -y when this PR is merged and released.
|
||||
demoTileInfos: { 's': 'a', 'z': 9, 'x': 265, 'y': 181, '-y': 181, 'r': '' },
|
||||
demoTileInfos: { s: 'a', z: 9, x: 265, y: 181, '-y': 181, r: '' },
|
||||
licences: [],
|
||||
licence: '',
|
||||
enableMarkerDraw: true,
|
||||
|
@ -59,7 +59,6 @@ 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()) {
|
||||
|
@ -68,8 +67,8 @@ U.Map = L.Map.extend({
|
|||
}
|
||||
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.server = new U.ServerRequest()
|
||||
this.request = new U.Request()
|
||||
|
||||
this.initLoader()
|
||||
this.name = this.options.name
|
||||
|
@ -391,7 +390,7 @@ U.Map = L.Map.extend({
|
|||
icon: 'umap-fake-class',
|
||||
iconLoading: 'umap-fake-class',
|
||||
flyTo: this.options.easing,
|
||||
onLocationError: (err) => this.alert.open({ content: err.message }),
|
||||
onLocationError: (err) => U.Alert.error(err.message),
|
||||
})
|
||||
this._controls.fullscreen = new L.Control.Fullscreen({
|
||||
title: { false: L._('View Fullscreen'), true: L._('Exit Fullscreen') },
|
||||
|
@ -680,10 +679,7 @@ U.Map = L.Map.extend({
|
|||
} catch (e) {
|
||||
console.error(e)
|
||||
this.removeLayer(tilelayer)
|
||||
this.alert.open({
|
||||
content: `${L._('Error in the tilelayer URL')}: ${tilelayer._url}`,
|
||||
level: 'error',
|
||||
})
|
||||
U.Alert.error(`${L._('Error in the tilelayer URL')}: ${tilelayer._url}`)
|
||||
// Users can put tilelayer URLs by hand, and if they add wrong {variable},
|
||||
// Leaflet throw an error, and then the map is no more editable
|
||||
}
|
||||
|
@ -715,10 +711,7 @@ U.Map = L.Map.extend({
|
|||
} catch (e) {
|
||||
this.removeLayer(overlay)
|
||||
console.error(e)
|
||||
this.alert.open({
|
||||
content: `${L._('Error in the overlay URL')}: ${overlay._url}`,
|
||||
level: 'error',
|
||||
})
|
||||
U.Alert.error(`${L._('Error in the overlay URL')}: ${overlay._url}`)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -843,10 +836,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.alert.open({
|
||||
content: L._('The zoom and center have been modified.'),
|
||||
level: 'info',
|
||||
})
|
||||
U.Alert.info(L._('The zoom and center have been modified.'))
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -886,12 +876,11 @@ U.Map = L.Map.extend({
|
|||
processFileToImport: function (file, layer, type) {
|
||||
type = type || U.Utils.detectFileType(file)
|
||||
if (!type) {
|
||||
this.alert.open({
|
||||
content: L._('Unable to detect format of file {filename}', {
|
||||
U.Alert.error(
|
||||
L._('Unable to detect format of file {filename}', {
|
||||
filename: file.name,
|
||||
}),
|
||||
level: 'error',
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
if (type === 'umap') {
|
||||
|
@ -947,10 +936,7 @@ U.Map = L.Map.extend({
|
|||
self.importRaw(rawData)
|
||||
} catch (e) {
|
||||
console.error('Error importing data', e)
|
||||
self.alert.open({
|
||||
content: L._('Invalid umap data in {filename}', { filename: file.name }),
|
||||
level: 'error',
|
||||
})
|
||||
U.Alert.error(L._('Invalid umap data in {filename}', { filename: file.name }))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1058,57 +1044,54 @@ U.Map = L.Map.extend({
|
|||
const [data, _, error] = await this.server.post(uri, {}, formData)
|
||||
// FIXME: login_required response will not be an error, so it will not
|
||||
// stop code while it should
|
||||
if (!error) {
|
||||
let duration = 3000,
|
||||
alert = { content: L._('Map has been saved!'), level: 'info' }
|
||||
if (error) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.options.umap_id) {
|
||||
alert.content = L._('Congratulations, your map has been created!')
|
||||
this.options.umap_id = data.id
|
||||
this.permissions.setOptions(data.permissions)
|
||||
this.permissions.commit()
|
||||
if (data.permissions && data.permissions.anonymous_edit_url) {
|
||||
alert.duration = Infinity
|
||||
alert.content =
|
||||
if (data?.permissions?.anonymous_edit_url) {
|
||||
const send_edit_link_url =
|
||||
this.options.urls.map_send_edit_link &&
|
||||
this.urls.get('map_send_edit_link', {
|
||||
map_id: this.options.umap_id,
|
||||
})
|
||||
this.once('saved', () => {
|
||||
U.AlertCreation.info(
|
||||
L._(
|
||||
'Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:'
|
||||
) + `<br>${data.permissions.anonymous_edit_url}`
|
||||
|
||||
alert.actions = [
|
||||
{
|
||||
label: L._('Copy link'),
|
||||
callback: () => {
|
||||
L.Util.copyToClipboard(data.permissions.anonymous_edit_url)
|
||||
this.alert.open({
|
||||
content: L._('Secret edit link copied to clipboard!'),
|
||||
level: 'info',
|
||||
'Your map has been created! As you are not logged in, ' +
|
||||
'here is your secret link to edit the map, please keep it safe:'
|
||||
),
|
||||
Number.Infinity,
|
||||
data.permissions.anonymous_edit_url,
|
||||
send_edit_link_url
|
||||
)
|
||||
})
|
||||
},
|
||||
callbackContext: this,
|
||||
},
|
||||
]
|
||||
if (this.options.urls.map_send_edit_link) {
|
||||
alert.actions.push({
|
||||
label: L._('Send me the link'),
|
||||
input: L._('Email'),
|
||||
callback: this.sendEditLink,
|
||||
callbackContext: this,
|
||||
} else {
|
||||
this.once('saved', () => {
|
||||
U.Alert.info(L._('Congratulations, your map has been created!'))
|
||||
})
|
||||
}
|
||||
}
|
||||
} else if (!this.permissions.isDirty) {
|
||||
} else {
|
||||
if (!this.permissions.isDirty) {
|
||||
// Do not override local changes to permissions,
|
||||
// but update in case some other editors changed them in the meantime.
|
||||
this.permissions.setOptions(data.permissions)
|
||||
this.permissions.commit()
|
||||
}
|
||||
// Update URL in case the name has changed.
|
||||
if (history && history.pushState)
|
||||
history.pushState({}, this.options.name, data.url)
|
||||
else window.location = data.url
|
||||
alert.content = data.info || alert.content
|
||||
this.once('saved', () => this.alert.open(alert))
|
||||
this.permissions.save()
|
||||
this.once('saved', () => {
|
||||
U.Alert.info(data.info || L._('Map has been saved!'))
|
||||
})
|
||||
}
|
||||
// Update URL in case the name has changed.
|
||||
if (history?.pushState) {
|
||||
history.pushState({}, this.options.name, data.url)
|
||||
} else {
|
||||
window.location = data.url
|
||||
}
|
||||
this.permissions.save()
|
||||
},
|
||||
|
||||
save: function () {
|
||||
|
@ -1126,33 +1109,20 @@ U.Map = L.Map.extend({
|
|||
}
|
||||
},
|
||||
|
||||
sendEditLink: async function () {
|
||||
const input = this.alert.container.querySelector('input')
|
||||
const email = input.value
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('email', email)
|
||||
|
||||
const url = this.urls.get('map_send_edit_link', { map_id: this.options.umap_id })
|
||||
await this.server.post(url, {}, formData)
|
||||
},
|
||||
|
||||
star: async function () {
|
||||
if (!this.options.umap_id)
|
||||
return this.alert.open({
|
||||
content: L._('Please save the map first'),
|
||||
level: 'error',
|
||||
})
|
||||
if (!this.options.umap_id) {
|
||||
return U.Alert.error(L._('Please save the map first'))
|
||||
}
|
||||
const url = this.urls.get('map_star', { map_id: this.options.umap_id })
|
||||
const [data, response, error] = await this.server.post(url)
|
||||
if (!error) {
|
||||
this.options.starred = data.starred
|
||||
let msg = data.starred
|
||||
? L._('Map has been starred')
|
||||
: L._('Map has been unstarred')
|
||||
this.alert.open({ content: msg, level: 'info' })
|
||||
this.renderControls()
|
||||
if (error) {
|
||||
return
|
||||
}
|
||||
this.options.starred = data.starred
|
||||
U.Alert.info(
|
||||
data.starred ? L._('Map has been starred') : L._('Map has been unstarred')
|
||||
)
|
||||
this.renderControls()
|
||||
},
|
||||
|
||||
geometry: function () {
|
||||
|
|
|
@ -958,7 +958,7 @@ U.DataLayer = L.Evented.extend({
|
|||
const doc = new DOMParser().parseFromString(x, 'text/xml')
|
||||
const errorNode = doc.querySelector('parsererror')
|
||||
if (errorNode) {
|
||||
this.map.alert.open({ content: L._('Cannot parse data'), level: 'error' })
|
||||
U.Alert.error(L._('Cannot parse data'))
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
@ -993,7 +993,7 @@ U.DataLayer = L.Evented.extend({
|
|||
message: err[0].message,
|
||||
})
|
||||
}
|
||||
this.map.alert.open({ content: message, level: 'error', duration: 10000 })
|
||||
U.Alert.error(message, 10000)
|
||||
console.error(err)
|
||||
}
|
||||
if (result && result.features.length) {
|
||||
|
@ -1020,7 +1020,7 @@ U.DataLayer = L.Evented.extend({
|
|||
const gj = JSON.parse(c)
|
||||
callback(gj)
|
||||
} catch (err) {
|
||||
this.map.alert.open({ content: `Invalid JSON file: ${err}` })
|
||||
U.Alert.error(`Invalid JSON file: ${err}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -1121,12 +1121,11 @@ U.DataLayer = L.Evented.extend({
|
|||
return this.geojsonToFeatures(geometry.geometries)
|
||||
|
||||
default:
|
||||
this.map.alert.open({
|
||||
content: L._('Skipping unknown geometry.type: {type}', {
|
||||
U.Alert.error(
|
||||
L._('Skipping unknown geometry.type: {type}', {
|
||||
type: geometry.type || 'undefined',
|
||||
}),
|
||||
level: 'error',
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1706,27 +1705,14 @@ U.DataLayer = L.Evented.extend({
|
|||
const [data, response, error] = await this.map.server.post(url, headers, formData)
|
||||
if (error) {
|
||||
if (response && response.status === 412) {
|
||||
const msg = L._(
|
||||
'Woops! Someone else seems to have edited the data. You can save anyway, but this will erase the changes made by others.'
|
||||
U.AlertChoice.error(
|
||||
L._(
|
||||
'Woops! Someone else seems to have edited the data. ' +
|
||||
'You can save anyway, but this will erase the changes made by others.'
|
||||
)
|
||||
const actions = [
|
||||
{
|
||||
label: L._('Save anyway'),
|
||||
callback: async () => {
|
||||
// Save again,
|
||||
// but do not pass the reference version this time
|
||||
)
|
||||
document.addEventListener('umap:alert-choice-confirm', async (event) => {
|
||||
await this._trySave(url, {}, formData)
|
||||
},
|
||||
},
|
||||
{
|
||||
label: L._('Cancel'),
|
||||
},
|
||||
]
|
||||
this.map.alert.open({
|
||||
content: msg,
|
||||
level: 'error',
|
||||
duration: 100000,
|
||||
actions: actions,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -52,11 +52,9 @@ U.MapPermissions = L.Class.extend({
|
|||
|
||||
edit: function () {
|
||||
if (this.map.options.editMode !== 'advanced') return
|
||||
if (!this.map.options.umap_id)
|
||||
return this.map.alert.open({
|
||||
content: L._('Please save the map first'),
|
||||
level: 'info',
|
||||
})
|
||||
if (!this.map.options.umap_id) {
|
||||
return U.Alert.info(L._('Please save the map first'))
|
||||
}
|
||||
const container = L.DomUtil.create('div', 'permissions-panel')
|
||||
const fields = []
|
||||
L.DomUtil.createTitle(container, L._('Update permissions'), 'icon-key')
|
||||
|
@ -140,10 +138,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.alert.open({
|
||||
content: L._('Map has been attached to your account'),
|
||||
level: 'info',
|
||||
})
|
||||
U.Alert.info(L._('Map has been attached to your account'))
|
||||
this.map.editPanel.close()
|
||||
}
|
||||
},
|
||||
|
|
|
@ -83,10 +83,7 @@ U.TableEditor = L.Class.extend({
|
|||
|
||||
validateName: function (name) {
|
||||
if (name.indexOf('.') !== -1) {
|
||||
this.datalayer.map.alert.open({
|
||||
content: L._('Invalide property name: {name}', { name: name }),
|
||||
level: 'error',
|
||||
})
|
||||
U.Alert.error(L._('Invalide property name: {name}', { name: name }))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -38,8 +38,7 @@
|
|||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', event => {
|
||||
const alert = new U.Alert(document.querySelector('header'))
|
||||
const server = new U.ServerRequest(alert)
|
||||
const server = new U.ServerRequest()
|
||||
const getMore = async function (e) {
|
||||
L.DomEvent.stop(e)
|
||||
const [{html}, response, error] = await server.get(this.href)
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
<link rel="stylesheet" href="{% static 'umap/nav.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'umap/map.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'umap/css/panel.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'umap/css/alert.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'umap/css/tooltip.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'umap/css/dialog.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'umap/theme.css' %}" />
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
{% load umap_tags %}
|
||||
|
||||
{% include "umap/messages.html" %}
|
||||
<div id="map"></div>
|
||||
<!-- djlint:off -->
|
||||
<script defer type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
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.alert.open({
|
||||
content: "{{ m }}",
|
||||
level: "{{ m.tags }}",
|
||||
duration: 100000
|
||||
})
|
||||
{% endfor %}
|
||||
})
|
||||
</script>
|
||||
<!-- djlint:on -->
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<div class="wrapper">
|
||||
<div class="row">
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
{% load i18n %}
|
||||
|
||||
{% include "umap/js/components/alerts/alert.html" %}
|
||||
|
||||
{% for message in messages %}
|
||||
<li {% if message.tags %}class="{{ message.tags }}"{% endif %}>{{ message }}</li>
|
||||
<script type="module" defer>
|
||||
U.Alert.info("{{ message }}")
|
||||
</script>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -906,6 +906,7 @@ class MapDelete(DeleteView):
|
|||
return HttpResponseForbidden(_("Only its owner can delete the map."))
|
||||
self.object.delete()
|
||||
home_url = reverse("home")
|
||||
messages.info(self.request, _("Map successfully deleted."))
|
||||
if is_ajax(self.request):
|
||||
return simple_json_response(redirect=home_url)
|
||||
else:
|
||||
|
|
Loading…
Reference in a new issue