wip: rework dialog class

This commit is contained in:
Yohan Boniface 2024-07-02 18:11:22 +02:00
parent 186025e0f0
commit 91badcdb5e
20 changed files with 296 additions and 180 deletions

View file

@ -14,7 +14,28 @@
height: fit-content; height: fit-content;
max-height: 90vh; max-height: 90vh;
} }
.umap-dialog .umap-close-link { :where([data-component="no-dialog"]:not([hidden])) {
float: right; display: block;
width: 100px; inset-inline-start: 50%;
position: fixed;
transform: translateX(-50%);
}
:where([data-component*="dialog"] menu) {
display: flex;
gap: calc(var(--gutter) / 2);
justify-content: flex-end;
margin: 0;
padding: 0;
}
:where([data-component*="dialog"] [data-ref="fieldset"]) {
border: 0;
margin: unset;
padding: unset;
}
/* hack for Firefox */
@-moz-document url-prefix() {
[data-component="no-dialog"]:not([hidden]) {
inset-inline-start: 0;
transform: none;
}
} }

View file

@ -10,6 +10,7 @@
position: sticky; position: sticky;
top: 0; top: 0;
height: var(--panel-header-height); height: var(--panel-header-height);
float: right;
} }
.window .buttons li { .window .buttons li {
cursor: pointer; cursor: pointer;

View file

@ -1,4 +1,4 @@
[role="dialog"] { .umap-alert[role="dialog"] {
box-sizing: border-box; box-sizing: border-box;
min-height: 46px; min-height: 46px;
line-height: 46px; line-height: 46px;
@ -20,36 +20,36 @@
width: max-content; width: max-content;
z-index: var(--zindex-alert); z-index: var(--zindex-alert);
} }
[role="dialog"] > div { .umap-alert[role="dialog"] > div {
margin: 0 auto; margin: 0 auto;
min-width: 60%; min-width: 60%;
background-size: 20px; background-size: 20px;
background-position: 0 15px; background-position: 0 15px;
padding-left: 28px; padding-left: 28px;
} }
[role="dialog"][data-level="info"] > div { .umap-alert[role="dialog"][data-level="info"] > div {
background-image: url('../../../img/alert-icon-info.svg'); background-image: url('../../../img/alert-icon-info.svg');
background-repeat: no-repeat; background-repeat: no-repeat;
} }
[role="dialog"][data-level="success"] > div { .umap-alert[role="dialog"][data-level="success"] > div {
background-image: url('../../../img/alert-icon-success.svg'); background-image: url('../../../img/alert-icon-success.svg');
background-repeat: no-repeat; background-repeat: no-repeat;
} }
[role="dialog"][data-level="error"] > div { .umap-alert[role="dialog"][data-level="error"] > div {
background-image: url('../../../img/alert-icon-error.svg'); background-image: url('../../../img/alert-icon-error.svg');
background-repeat: no-repeat; background-repeat: no-repeat;
} }
[role="dialog"][data-level="error"] { .umap-alert[role="dialog"][data-level="error"] {
background-color: var(--color-darkRed); background-color: var(--color-darkRed);
} }
[role="dialog"] a { .umap-alert[role="dialog"] a {
text-decoration: underline; text-decoration: underline;
} }
[role="dialog"] label { .umap-alert[role="dialog"] label {
font-size: .8rem; font-size: .8rem;
font-weight: normal; font-weight: normal;
} }
[role="dialog"] a[target="_blank"] { .umap-alert[role="dialog"] a[target="_blank"] {
background: url('../../../img/icon-external-link.svg') no-repeat right center; background: url('../../../img/icon-external-link.svg') no-repeat right center;
padding-right: 14px; padding-right: 14px;
background-size: 12px; background-size: 12px;
@ -127,7 +127,7 @@ h3[role="alert"] + p {
#link-wrapper { #link-wrapper {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
[role="dialog"] #conflict-wrapper a[target="_blank"] { .umap-alert[role="dialog"] #conflict-wrapper a[target="_blank"] {
background-position-y: 16px; background-position-y: 16px;
} }

View file

@ -45,6 +45,7 @@ export class BaseAutocomplete {
placeholder: this.options.placeholder, placeholder: this.options.placeholder,
autocomplete: 'off', autocomplete: 'off',
className: this.options.className, className: this.options.className,
name: this.options.name || 'autocomplete'
}) })
DomEvent.on(this.input, 'keydown', this.onKeyDown, this) DomEvent.on(this.input, 'keydown', this.onKeyDown, this)
DomEvent.on(this.input, 'keyup', this.onKeyUp, this) DomEvent.on(this.input, 'keyup', this.onKeyUp, this)

View file

@ -14,7 +14,7 @@ import { HTTPError, NOKError, Request, RequestError, ServerRequest } from './req
import Rules from './rules.js' import Rules from './rules.js'
import { SCHEMA } from './schema.js' import { SCHEMA } from './schema.js'
import { SyncEngine } from './sync/engine.js' import { SyncEngine } from './sync/engine.js'
import { Dialog, Prompt } from './ui/dialog.js' import Dialog from './ui/dialog.js'
import { EditPanel, FullPanel, Panel } from './ui/panel.js' import { EditPanel, FullPanel, Panel } from './ui/panel.js'
import Tooltip from './ui/tooltip.js' import Tooltip from './ui/tooltip.js'
import URLs from './urls.js' import URLs from './urls.js'
@ -42,7 +42,6 @@ window.U = {
NOKError, NOKError,
Orderable, Orderable,
Panel, Panel,
Prompt,
Request, Request,
RequestError, RequestError,
Rules, Rules,

View file

@ -208,7 +208,7 @@ export default class Help {
}) })
} }
} }
this.dialog.open({ content: container, className: 'dark' }) this.dialog.open({ template: container, className: 'dark', cancel: false, accept: false })
} }
button(container, entries, classname) { button(container, entries, classname) {
@ -245,7 +245,7 @@ export default class Help {
DomUtil.add('i', action.options.className, actionContainer) DomUtil.add('i', action.options.className, actionContainer)
DomUtil.add('span', '', actionContainer, action.options.tooltip) DomUtil.add('span', '', actionContainer, action.options.tooltip)
DomEvent.on(actionContainer, 'click', action.addHooks, action) DomEvent.on(actionContainer, 'click', action.addHooks, action)
DomEvent.on(actionContainer, 'click', this.dialog.close, this.map.dialog) DomEvent.on(actionContainer, 'click', this.dialog.close, this.dialog)
} }
title.textContent = translate('Where do we go from here?') title.textContent = translate('Where do we go from here?')
for (const id in this.map.helpMenuActions) { for (const id in this.map.helpMenuActions) {

View file

@ -2,7 +2,7 @@ import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
import { uMapAlert as Alert } from '../components/alerts/alert.js' import { uMapAlert as Alert } from '../components/alerts/alert.js'
import { translate } from './i18n.js' import { translate } from './i18n.js'
import { SCHEMA } from './schema.js' import { SCHEMA } from './schema.js'
import { Dialog } from './ui/dialog.js' import Dialog from './ui/dialog.js'
import * as Utils from './utils.js' import * as Utils from './utils.js'
const TEMPLATE = ` const TEMPLATE = `
@ -114,7 +114,7 @@ export default class Importer {
} }
get action() { get action() {
return this.qs('[name=action]:checked').value return this.qs('[name=action]:checked')?.value
} }
get layerId() { get layerId() {
@ -234,7 +234,7 @@ export default class Importer {
} }
submit() { submit() {
let hasErrors = false let hasErrors
if (this.format === 'umap') { if (this.format === 'umap') {
hasErrors = !this.full() hasErrors = !this.full()
} else if (!this.url) { } else if (!this.url) {
@ -242,7 +242,7 @@ export default class Importer {
} else if (this.action) { } else if (this.action) {
hasErrors = !this[this.action]() hasErrors = !this[this.action]()
} }
if (!hasErrors) { if (hasErrors === false) {
Alert.info(translate('Data successfully imported!')) Alert.info(translate('Data successfully imported!'))
} }
} }

View file

@ -37,8 +37,10 @@ export class Importer {
this.autocomplete = new Autocomplete(container, options) this.autocomplete = new Autocomplete(container, options)
importer.dialog.open({ importer.dialog.open({
content: container, template: container,
className: `${this.id} importer dark`, className: `${this.id} importer dark`,
cancel: false,
accept: false,
}) })
} }
} }

View file

@ -30,13 +30,15 @@ export class Importer {
importer.format = select.options[select.selectedIndex].dataset.format importer.format = select.options[select.selectedIndex].dataset.format
importer.layerName = select.options[select.selectedIndex].textContent importer.layerName = select.options[select.selectedIndex].textContent
} }
importer.dialog.close()
} }
L.DomUtil.createButton('', container, translate('Choose this dataset'), confirm)
importer.dialog.open({ importer.dialog
content: container, .open({
className: `${this.id} importer dark`, template: container,
}) className: `${this.id} importer dark`,
accept: translate('Choose this dataset'),
cancel: false,
})
.then(confirm)
} }
} }

View file

@ -25,7 +25,6 @@ const TEMPLATE = `
</label> </label>
<label id="boundary"> <label id="boundary">
</label> </label>
<button class="button">${translate('Choose this data')}</button>
` `
class Autocomplete extends SingleMixin(BaseAjax) { class Autocomplete extends SingleMixin(BaseAjax) {
@ -66,7 +65,6 @@ export class Importer {
} else { } else {
console.error(response) console.error(response)
} }
const asPoint = container.querySelector('[name=aspoint]')
this.autocomplete = new Autocomplete(container.querySelector('#boundary'), { this.autocomplete = new Autocomplete(container.querySelector('#boundary'), {
placeholder: translate('Search admin boundary'), placeholder: translate('Search admin boundary'),
url: `${this.baseUrl}/boundaries/search?text={q}`, url: `${this.baseUrl}/boundaries/search?text={q}`,
@ -75,21 +73,23 @@ export class Importer {
boundaryName = choice.item.label boundaryName = choice.item.label
}, },
}) })
const confirm = () => { const confirm = (form) => {
if (!boundary || !select.value) { if (!boundary || !select.value) {
Alert.error(translate('Please choose a theme and a boundary first.')) Alert.error(translate('Please choose a theme and a boundary first.'))
return return
} }
importer.url = `${this.baseUrl}/data/${select.value}/${boundary}?format=geojson&aspoint=${asPoint.checked}` importer.url = `${this.baseUrl}/data/${form.theme}/${boundary}?format=geojson&aspoint=${Boolean(form.aspoint)}`
importer.format = 'geojson' importer.format = 'geojson'
importer.layerName = `${boundaryName}${select.options[select.selectedIndex].textContent}` importer.layerName = `${boundaryName}${select.options[select.selectedIndex].textContent}`
importer.dialog.close()
} }
DomEvent.on(container.querySelector('button'), 'click', confirm)
importer.dialog.open({ importer.dialog
content: container, .open({
className: `${this.id} importer dark`, template: container,
}) className: `${this.id} importer dark`,
accept: translate('Choose this data'),
cancel: false,
})
.then(confirm)
} }
} }

View file

@ -11,7 +11,7 @@ const TEMPLATE = `
</label> </label>
<label> <label>
${translate('Geometry mode')} ${translate('Geometry mode')}
<select name="out-mode"> <select name="out">
<option value="geom" selected>${translate('Default')}</option> <option value="geom" selected>${translate('Default')}</option>
<option value="center">${translate('Only geometry centers')}</option> <option value="center">${translate('Only geometry centers')}</option>
</select> </select>
@ -58,27 +58,28 @@ export class Importer {
}) })
this.map.help.parse(container) this.map.help.parse(container)
const confirm = () => { const confirm = (form) => {
let tags = container.querySelector('[name=tags]').value if (!form.tags) {
if (!tags) { Alert.error(translate('Expression is empty'))
Alert.error(translate('Please define an expression for the query first'))
return return
} }
const outMode = container.querySelector('[name=out-mode]').value let tags = form.tags
if (!tags.startsWith('[')) tags = `[${tags}]` if (!tags.startsWith('[')) tags = `[${tags}]`
let area = '{south},{west},{north},{east}' let area = '{south},{west},{north},{east}'
if (boundary) area = `area:${boundary}` if (boundary) area = `area:${boundary}`
const query = `[out:json];nwr${tags}(${area});out ${outMode};` const query = `[out:json];nwr${tags}(${area});out ${form.out};`
importer.url = `${this.baseUrl}?data=${query}` importer.url = `${this.baseUrl}?data=${query}`
if (boundary) importer.layerName = boundaryName if (boundary) importer.layerName = boundaryName
importer.format = 'osm' importer.format = 'osm'
importer.dialog.close()
} }
L.DomUtil.createButton('', container, translate('Choose this data'), confirm)
importer.dialog.open({ importer.dialog
content: container, .open({
className: `${this.id} importer dark`, template: container,
}) className: `${this.id} importer dark`,
accept: translate('Choose this data'),
cancel: false
})
.then(confirm)
} }
} }

View file

@ -1,22 +1,89 @@
import { DomEvent, DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js' import { DomEvent, DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from '../i18n.js' import { translate } from '../i18n.js'
export class Dialog { // From https://css-tricks.com/replace-javascript-dialogs-html-dialog-element/
constructor() { export default class Dialog {
this.className = 'umap-dialog window' constructor(settings = {}) {
this.container = DomUtil.create('dialog', this.className, document.body) this.settings = Object.assign(
DomEvent.disableClickPropagation(this.container) {
DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu. accept: translate('OK'),
DomEvent.on(this.container, 'wheel', DomEvent.stopPropagation) cancel: translate('Cancel'),
DomEvent.on(this.container, 'MozMousePixelScroll', DomEvent.stopPropagation) className: '',
message: '',
template: '',
},
settings
)
this.init()
} }
get visible() { collectFormData(formData) {
return this.container.open const object = {}
formData.forEach((value, key) => {
if (!Reflect.has(object, key)) {
object[key] = value
return
}
if (!Array.isArray(object[key])) {
object[key] = [object[key]]
}
object[key].push(value)
})
return object
} }
close() { getFocusable() {
this.container.close() return [
...this.dialog.querySelectorAll(
'button,[href],select,textarea,input:not([type="hidden"]),[tabindex]:not([tabindex="-1"])'
),
]
}
init() {
this.dialogSupported = typeof HTMLDialogElement === 'function'
this.dialog = document.createElement('dialog')
this.dialog.role = 'dialog'
this.dialog.dataset.component = this.dialogSupported ? 'dialog' : 'no-dialog'
this.dialog.innerHTML = `
<form method="dialog" data-ref="form">
<ul class="buttons">
<li><i class="icon icon-16 icon-close" data-close></i></li>
</ul>
<h3 data-ref="message" id="${Math.round(Date.now()).toString(36)}"></h3>
<fieldset data-ref="fieldset" role="document">
<div data-ref="template"></div>
</fieldset>
<menu>
<button${this.dialogSupported ? '' : ` type="button"`} class="button" data-ref="cancel" data-close value="cancel"></button>
<button${this.dialogSupported ? ' type="submit"' : ` type="button"`} class="button" data-ref="accept" value="accept"></button>
</menu>
</form>`
document.body.appendChild(this.dialog)
this.elements = {}
this.focusable = []
this.dialog
.querySelectorAll('[data-ref]')
.forEach((el) => (this.elements[el.dataset.ref] = el))
this.dialog.setAttribute('aria-labelledby', this.elements.message.id)
this.dialog.addEventListener('click', (event) => {
if (event.target.closest('[data-close]')) {
this.dialog.close('cancel')
}
})
this.dialog.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
if (!this.dialogSupported) e.preventDefault()
console.log('Enter')
this.elements.accept.click()
}
if (e.key === 'Escape') {
e.stopPropagation()
this.dialog.close('cancel')
}
})
this.toggle()
} }
currentZIndex() { currentZIndex() {
@ -27,65 +94,84 @@ export class Dialog {
) )
} }
open({ className, content, modal } = {}) { open(settings = {}) {
this.container.innerHTML = '' const dialog = Object.assign({}, this.settings, settings)
const currentZIndex = this.currentZIndex() this.dialog.className = 'umap-dialog window'
if (currentZIndex) this.container.style.zIndex = currentZIndex + 1 if (dialog.className) {
if (modal) this.container.showModal() this.dialog.classList.add(...dialog.className.split(' '))
else this.container.show()
if (className) {
// Reset
this.container.className = this.className
this.container.classList.add(...className.split(' '))
} }
const buttonsContainer = DomUtil.create('ul', 'buttons', this.container) this.elements.accept.textContent = dialog.accept
const closeButton = DomUtil.createButtonIcon( this.elements.accept.hidden = !dialog.accept
DomUtil.create('li', '', buttonsContainer), this.elements.cancel.textContent = dialog.cancel
'icon-close', this.elements.cancel.hidden = !dialog.cancel
translate('Close') this.elements.message.textContent = dialog.message
this.elements.message.hidden = !dialog.message
this.elements.target = dialog.target || ''
this.elements.template.innerHTML = ''
if (dialog.template?.nodeType === 1) {
this.elements.template.appendChild(dialog.template)
} else {
this.elements.template.innerHTML = dialog.template || ''
}
this.focusable = this.getFocusable()
this.hasFormData = this.elements.fieldset.elements.length > 0
const currentZIndex = this.currentZIndex()
if (currentZIndex) this.dialog.style.zIndex = currentZIndex + 1
this.toggle(true)
if (this.hasFormData) this.focusable[0].focus()
else this.elements.accept.focus()
return this.waitForUser()
}
close() {
this.toggle(false)
}
toggle(open = false) {
if (this.dialogSupported && open) this.dialog.show()
if (!this.dialogSupported) {
this.dialog.hidden = !open
if (this.elements.target && !open) {
this.elements.target.focus()
}
}
}
waitForUser() {
return new Promise((resolve) => {
this.dialog.addEventListener(
'close',
(status) => {
this.toggle()
if (this.dialog.returnValue === 'accept') {
const value = this.hasFormData
? this.collectFormData(new FormData(this.elements.form))
: true
resolve(value)
}
},
{ once: true }
)
})
}
alert(config) {
return this.open(
Object.assign({}, config, { cancel: false, message, template: false })
) )
DomEvent.on(closeButton, 'click', this.close, this) }
this.container.appendChild(buttonsContainer)
this.container.appendChild(content) confirm(message, config = {}) {
DomEvent.once(this.container, 'keydown', (e) => { return this.open(Object.assign({}, config, { message, template: false }))
DomEvent.stop(e) }
if (e.key === 'Escape') this.close()
}) prompt(message, fallback = '', config = {}) {
} const template = `<input type="text" name="prompt" value="${fallback}">`
} return this.open(Object.assign({}, config, { message, template }))
const PROMPT = `
<form>
<h3></h3>
<input type="text" name="prompt" />
<input type="submit" value="${translate('Ok')}" />
</form>
`
export class Prompt extends Dialog {
get input() {
return this.container.querySelector('input[name="prompt"]')
}
get title() {
return this.container.querySelector('h3')
}
get form() {
return this.container.querySelector('form')
}
open({ className, title } = {}) {
const content = DomUtil.element({ tagName: 'div', safeHTML: PROMPT })
super.open({ className, content })
this.title.textContent = title
const promise = new Promise((resolve, reject) => {
DomEvent.on(this.form, 'submit', (e) => {
DomEvent.stop(e)
resolve(this.input.value)
this.close()
})
})
return promise
} }
} }

View file

@ -194,16 +194,9 @@ U.FeatureMixin = {
}, },
getAdvancedEditActions: function (container) { getAdvancedEditActions: function (container) {
L.DomUtil.createButton( L.DomUtil.createButton('button umap-delete', container, L._('Delete'), (e) => {
'button umap-delete', this.confirmDelete().then(() => this.map.editPanel.close())
container, })
L._('Delete'),
function (e) {
L.DomEvent.stop(e)
if (this.confirmDelete()) this.map.editPanel.close()
},
this
)
}, },
appendEditFieldsets: function (container) { appendEditFieldsets: function (container) {
@ -272,8 +265,11 @@ U.FeatureMixin = {
this.bindPopup(new Class(this)) this.bindPopup(new Class(this))
}, },
confirmDelete: function () { confirmDelete: async function () {
if (confirm(L._('Are you sure you want to delete the feature?'))) { const confirmed = await this.map.dialog.confirm(
L._('Are you sure you want to delete the feature?')
)
if (confirmed) {
this.del() this.del()
return true return true
} }

View file

@ -56,6 +56,7 @@ U.Map = L.Map.extend({
this.urls = new U.URLs(this.options.urls) this.urls = new U.URLs(this.options.urls)
this.panel = new U.Panel(this) this.panel = new U.Panel(this)
this.dialog = new U.Dialog({ className: 'dark' })
this.tooltip = new U.Tooltip(this._controlContainer) this.tooltip = new U.Tooltip(this._controlContainer)
if (this.hasEditMode()) { if (this.hasEditMode()) {
this.editPanel = new U.EditPanel(this) this.editPanel = new U.EditPanel(this)
@ -1639,9 +1640,12 @@ U.Map = L.Map.extend({
}, },
askForReset: function (e) { askForReset: function (e) {
if (!confirm(L._('Are you sure you want to cancel your changes?'))) return this.dialog
this.reset() .confirm(L._('Are you sure you want to cancel your changes?'))
this.disableEdit() .then(() => {
this.reset()
this.disableEdit()
})
}, },
startMarker: function () { startMarker: function () {

View file

@ -21,35 +21,8 @@ U.TableEditor = L.Class.extend({
const rename = L.DomUtil.create('i', 'umap-edit', container) const rename = L.DomUtil.create('i', 'umap-edit', container)
del.title = L._('Delete this property on all the features') del.title = L._('Delete this property on all the features')
rename.title = L._('Rename this property on all the features') rename.title = L._('Rename this property on all the features')
const doDelete = function () { L.DomEvent.on(del, 'click', () => this.deleteProperty(property))
if ( L.DomEvent.on(rename, 'click', () => this.renameProperty(property))
confirm(
L._('Are you sure you want to delete this property on all the features?')
)
) {
this.datalayer.eachLayer((feature) => {
feature.deleteProperty(property)
})
this.datalayer.deindexProperty(property)
this.resetProperties()
this.edit()
}
}
const doRename = function () {
const newName = prompt(
L._('Please enter the new name of this property'),
property
)
if (!newName || !this.validateName(newName)) return
this.datalayer.eachLayer((feature) => {
feature.renameProperty(property, newName)
})
this.datalayer.deindexProperty(property)
this.datalayer.indexProperty(newName)
this.edit()
}
L.DomEvent.on(del, 'click', doDelete, this)
L.DomEvent.on(rename, 'click', doRename, this)
}, },
renderRow: function (feature) { renderRow: function (feature) {
@ -89,15 +62,41 @@ U.TableEditor = L.Class.extend({
return true return true
}, },
addProperty: function () { renameProperty: function (property) {
new U.Prompt() this.datalayer.map.dialog
.open({ .prompt(L._('Please enter the new name of this property'))
className: 'dark', .then(({ prompt }) => {
title: L._('Please enter the name of the property'), if (!prompt || !this.validateName(prompt)) return
this.datalayer.eachLayer((feature) => {
feature.renameProperty(property, prompt)
})
this.datalayer.deindexProperty(property)
this.datalayer.indexProperty(prompt)
this.edit()
}) })
.then((newName) => { },
if (!newName || !this.validateName(newName)) return
this.datalayer.indexProperty(newName) deleteProperty: function (property) {
this.datalayer.map.dialog
.confirm(
L._('Are you sure you want to delete this property on all the features?')
)
.then(() => {
this.datalayer.eachLayer((feature) => {
feature.deleteProperty(property)
})
this.datalayer.deindexProperty(property)
this.resetProperties()
this.edit()
})
},
addProperty: function () {
this.datalayer.map.dialog
.prompt(L._('Please enter the name of the property'))
.then(({ prompt }) => {
if (!prompt || !this.validateName(prompt)) return
this.datalayer.indexProperty(prompt)
this.edit() this.edit()
}) })
}, },

View file

@ -5,7 +5,7 @@
</style> </style>
<template id="umap-alert-template"> <template id="umap-alert-template">
<div role="dialog" class="dark window"> <div role="dialog" class="dark window umap-alert">
<div> <div>
<p role="alert"></p> <p role="alert"></p>
</div> </div>
@ -20,7 +20,7 @@
<umap-alert></umap-alert> <umap-alert></umap-alert>
<template id="umap-alert-creation-template"> <template id="umap-alert-creation-template">
<div role="dialog" class="dark window"> <div role="dialog" class="dark window umap-alert">
<div> <div>
<h3 role="alert"></h3> <h3 role="alert"></h3>
{% url "login" as login_url %} {% url "login" as login_url %}
@ -55,7 +55,7 @@
<umap-alert-creation></umap-alert-creation> <umap-alert-creation></umap-alert-creation>
<template id="umap-alert-conflict-template"> <template id="umap-alert-conflict-template">
<div role="dialog" class="dark window"> <div role="dialog" class="dark window umap-alert">
<div> <div>
<p role="alert"></p> <p role="alert"></p>
<div id="conflict-wrapper"> <div id="conflict-wrapper">

View file

@ -320,10 +320,11 @@ def test_should_redraw_list_on_feature_delete(live_server, openmap, page, bootst
page.get_by_role("button", name="Edit").click() page.get_by_role("button", name="Edit").click()
buttons = page.locator(".umap-browser .datalayer li .icon-delete") buttons = page.locator(".umap-browser .datalayer li .icon-delete")
expect(buttons).to_have_count(3) expect(buttons).to_have_count(3)
page.on("dialog", lambda dialog: dialog.accept()) buttons.first.click()
buttons.nth(0).click() page.locator("dialog").get_by_role("button", name="OK").click()
expect(buttons).to_have_count(2) expect(buttons).to_have_count(2)
page.get_by_role("button", name="Cancel edits").click() page.get_by_role("button", name="Cancel edits").click()
page.locator("dialog").get_by_role("button", name="OK").click()
expect(buttons).to_have_count(3) expect(buttons).to_have_count(3)

View file

@ -67,6 +67,7 @@ def test_cancel_deleting_datalayer_should_restore(
expect(page.get_by_text("test datalayer")).to_be_hidden() expect(page.get_by_text("test datalayer")).to_be_hidden()
page.once("dialog", lambda dialog: dialog.accept()) page.once("dialog", lambda dialog: dialog.accept())
page.get_by_role("button", name="Cancel edits").click() page.get_by_role("button", name="Cancel edits").click()
page.locator("dialog").get_by_role("button", name="OK").click()
expect(markers).to_have_count(1) expect(markers).to_have_count(1)
expect(page.locator(".umap-browser").get_by_text("test datalayer")).to_be_visible() expect(page.locator(".umap-browser").get_by_text("test datalayer")).to_be_visible()

View file

@ -117,8 +117,8 @@ def test_should_reset_style_on_cancel(live_server, openmap, page, bootstrap):
expect(page.locator(".leaflet-overlay-pane path[fill='GoldenRod']")).to_have_count( expect(page.locator(".leaflet-overlay-pane path[fill='GoldenRod']")).to_have_count(
1 1
) )
page.once("dialog", lambda dialog: dialog.accept())
page.get_by_role("button", name="Cancel edits").click() page.get_by_role("button", name="Cancel edits").click()
page.locator("dialog").get_by_role("button", name="OK").click()
expect(page.locator(".leaflet-overlay-pane path[fill='DarkBlue']")).to_have_count(1) expect(page.locator(".leaflet-overlay-pane path[fill='DarkBlue']")).to_have_count(1)

View file

@ -9,12 +9,14 @@ def test_table_editor(live_server, openmap, datalayer, page):
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit") page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
page.get_by_role("link", name="Manage layers").click() page.get_by_role("link", name="Manage layers").click()
page.locator(".panel").get_by_title("Edit properties in a table").click() page.locator(".panel").get_by_title("Edit properties in a table").click()
page.once("dialog", lambda dialog: dialog.accept(prompt_text="newprop"))
page.get_by_text("Add a new property").click() page.get_by_text("Add a new property").click()
page.locator("dialog").locator("input").fill("newprop")
page.locator("dialog").get_by_role("button", name="OK").click()
page.locator('input[name="newprop"]').fill("newvalue") page.locator('input[name="newprop"]').fill("newvalue")
page.once("dialog", lambda dialog: dialog.accept()) page.once("dialog", lambda dialog: dialog.accept())
page.hover(".umap-table-editor .tcell") page.hover(".umap-table-editor .tcell")
page.get_by_title("Delete this property on all").first.click() page.get_by_title("Delete this property on all").first.click()
page.locator("dialog").get_by_role("button", name="OK").click()
with page.expect_response(re.compile(r".*/datalayer/update/.*")): with page.expect_response(re.compile(r".*/datalayer/update/.*")):
page.get_by_role("button", name="Save").click() page.get_by_role("button", name="Save").click()
saved = DataLayer.objects.last() saved = DataLayer.objects.last()