mirror of
https://github.com/umap-project/umap.git
synced 2025-04-29 11:52:38 +02:00
wip: rework dialog class
This commit is contained in:
parent
186025e0f0
commit
91badcdb5e
20 changed files with 296 additions and 180 deletions
|
@ -14,7 +14,28 @@
|
|||
height: fit-content;
|
||||
max-height: 90vh;
|
||||
}
|
||||
.umap-dialog .umap-close-link {
|
||||
float: right;
|
||||
width: 100px;
|
||||
:where([data-component="no-dialog"]:not([hidden])) {
|
||||
display: block;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
position: sticky;
|
||||
top: 0;
|
||||
height: var(--panel-header-height);
|
||||
float: right;
|
||||
}
|
||||
.window .buttons li {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[role="dialog"] {
|
||||
.umap-alert[role="dialog"] {
|
||||
box-sizing: border-box;
|
||||
min-height: 46px;
|
||||
line-height: 46px;
|
||||
|
@ -20,36 +20,36 @@
|
|||
width: max-content;
|
||||
z-index: var(--zindex-alert);
|
||||
}
|
||||
[role="dialog"] > div {
|
||||
.umap-alert[role="dialog"] > div {
|
||||
margin: 0 auto;
|
||||
min-width: 60%;
|
||||
background-size: 20px;
|
||||
background-position: 0 15px;
|
||||
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-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-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-repeat: no-repeat;
|
||||
}
|
||||
[role="dialog"][data-level="error"] {
|
||||
.umap-alert[role="dialog"][data-level="error"] {
|
||||
background-color: var(--color-darkRed);
|
||||
}
|
||||
[role="dialog"] a {
|
||||
.umap-alert[role="dialog"] a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
[role="dialog"] label {
|
||||
.umap-alert[role="dialog"] label {
|
||||
font-size: .8rem;
|
||||
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;
|
||||
padding-right: 14px;
|
||||
background-size: 12px;
|
||||
|
@ -127,7 +127,7 @@ h3[role="alert"] + p {
|
|||
#link-wrapper {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
[role="dialog"] #conflict-wrapper a[target="_blank"] {
|
||||
.umap-alert[role="dialog"] #conflict-wrapper a[target="_blank"] {
|
||||
background-position-y: 16px;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ export class BaseAutocomplete {
|
|||
placeholder: this.options.placeholder,
|
||||
autocomplete: 'off',
|
||||
className: this.options.className,
|
||||
name: this.options.name || 'autocomplete'
|
||||
})
|
||||
DomEvent.on(this.input, 'keydown', this.onKeyDown, this)
|
||||
DomEvent.on(this.input, 'keyup', this.onKeyUp, this)
|
||||
|
|
|
@ -14,7 +14,7 @@ import { HTTPError, NOKError, Request, RequestError, ServerRequest } from './req
|
|||
import Rules from './rules.js'
|
||||
import { SCHEMA } from './schema.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 Tooltip from './ui/tooltip.js'
|
||||
import URLs from './urls.js'
|
||||
|
@ -42,7 +42,6 @@ window.U = {
|
|||
NOKError,
|
||||
Orderable,
|
||||
Panel,
|
||||
Prompt,
|
||||
Request,
|
||||
RequestError,
|
||||
Rules,
|
||||
|
|
|
@ -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) {
|
||||
|
@ -245,7 +245,7 @@ export default class Help {
|
|||
DomUtil.add('i', action.options.className, actionContainer)
|
||||
DomUtil.add('span', '', actionContainer, action.options.tooltip)
|
||||
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?')
|
||||
for (const id in this.map.helpMenuActions) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
|
|||
import { uMapAlert as Alert } from '../components/alerts/alert.js'
|
||||
import { translate } from './i18n.js'
|
||||
import { SCHEMA } from './schema.js'
|
||||
import { Dialog } from './ui/dialog.js'
|
||||
import Dialog from './ui/dialog.js'
|
||||
import * as Utils from './utils.js'
|
||||
|
||||
const TEMPLATE = `
|
||||
|
@ -114,7 +114,7 @@ export default class Importer {
|
|||
}
|
||||
|
||||
get action() {
|
||||
return this.qs('[name=action]:checked').value
|
||||
return this.qs('[name=action]:checked')?.value
|
||||
}
|
||||
|
||||
get layerId() {
|
||||
|
@ -234,7 +234,7 @@ export default class Importer {
|
|||
}
|
||||
|
||||
submit() {
|
||||
let hasErrors = false
|
||||
let hasErrors
|
||||
if (this.format === 'umap') {
|
||||
hasErrors = !this.full()
|
||||
} else if (!this.url) {
|
||||
|
@ -242,7 +242,7 @@ export default class Importer {
|
|||
} else if (this.action) {
|
||||
hasErrors = !this[this.action]()
|
||||
}
|
||||
if (!hasErrors) {
|
||||
if (hasErrors === false) {
|
||||
Alert.info(translate('Data successfully imported!'))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,8 +37,10 @@ export class Importer {
|
|||
this.autocomplete = new Autocomplete(container, options)
|
||||
|
||||
importer.dialog.open({
|
||||
content: container,
|
||||
template: container,
|
||||
className: `${this.id} importer dark`,
|
||||
cancel: false,
|
||||
accept: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,13 +30,15 @@ export class Importer {
|
|||
importer.format = select.options[select.selectedIndex].dataset.format
|
||||
importer.layerName = select.options[select.selectedIndex].textContent
|
||||
}
|
||||
importer.dialog.close()
|
||||
}
|
||||
L.DomUtil.createButton('', container, translate('Choose this dataset'), confirm)
|
||||
|
||||
importer.dialog.open({
|
||||
content: container,
|
||||
importer.dialog
|
||||
.open({
|
||||
template: container,
|
||||
className: `${this.id} importer dark`,
|
||||
accept: translate('Choose this dataset'),
|
||||
cancel: false,
|
||||
})
|
||||
.then(confirm)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ const TEMPLATE = `
|
|||
</label>
|
||||
<label id="boundary">
|
||||
</label>
|
||||
<button class="button">${translate('Choose this data')}</button>
|
||||
`
|
||||
|
||||
class Autocomplete extends SingleMixin(BaseAjax) {
|
||||
|
@ -66,7 +65,6 @@ export class Importer {
|
|||
} else {
|
||||
console.error(response)
|
||||
}
|
||||
const asPoint = container.querySelector('[name=aspoint]')
|
||||
this.autocomplete = new Autocomplete(container.querySelector('#boundary'), {
|
||||
placeholder: translate('Search admin boundary'),
|
||||
url: `${this.baseUrl}/boundaries/search?text={q}`,
|
||||
|
@ -75,21 +73,23 @@ export class Importer {
|
|||
boundaryName = choice.item.label
|
||||
},
|
||||
})
|
||||
const confirm = () => {
|
||||
const confirm = (form) => {
|
||||
if (!boundary || !select.value) {
|
||||
Alert.error(translate('Please choose a theme and a boundary first.'))
|
||||
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.layerName = `${boundaryName} — ${select.options[select.selectedIndex].textContent}`
|
||||
importer.dialog.close()
|
||||
}
|
||||
DomEvent.on(container.querySelector('button'), 'click', confirm)
|
||||
|
||||
importer.dialog.open({
|
||||
content: container,
|
||||
importer.dialog
|
||||
.open({
|
||||
template: container,
|
||||
className: `${this.id} importer dark`,
|
||||
accept: translate('Choose this data'),
|
||||
cancel: false,
|
||||
})
|
||||
.then(confirm)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ const TEMPLATE = `
|
|||
</label>
|
||||
<label>
|
||||
${translate('Geometry mode')}
|
||||
<select name="out-mode">
|
||||
<select name="out">
|
||||
<option value="geom" selected>${translate('Default')}</option>
|
||||
<option value="center">${translate('Only geometry centers')}</option>
|
||||
</select>
|
||||
|
@ -58,27 +58,28 @@ export class Importer {
|
|||
})
|
||||
this.map.help.parse(container)
|
||||
|
||||
const confirm = () => {
|
||||
let tags = container.querySelector('[name=tags]').value
|
||||
if (!tags) {
|
||||
Alert.error(translate('Please define an expression for the query first'))
|
||||
const confirm = (form) => {
|
||||
if (!form.tags) {
|
||||
Alert.error(translate('Expression is empty'))
|
||||
return
|
||||
}
|
||||
const outMode = container.querySelector('[name=out-mode]').value
|
||||
let tags = form.tags
|
||||
if (!tags.startsWith('[')) tags = `[${tags}]`
|
||||
let area = '{south},{west},{north},{east}'
|
||||
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}`
|
||||
if (boundary) importer.layerName = boundaryName
|
||||
importer.format = 'osm'
|
||||
importer.dialog.close()
|
||||
}
|
||||
L.DomUtil.createButton('', container, translate('Choose this data'), confirm)
|
||||
|
||||
importer.dialog.open({
|
||||
content: container,
|
||||
importer.dialog
|
||||
.open({
|
||||
template: container,
|
||||
className: `${this.id} importer dark`,
|
||||
accept: translate('Choose this data'),
|
||||
cancel: false
|
||||
})
|
||||
.then(confirm)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,89 @@
|
|||
import { DomEvent, DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
||||
import { translate } from '../i18n.js'
|
||||
|
||||
export class Dialog {
|
||||
constructor() {
|
||||
this.className = 'umap-dialog window'
|
||||
this.container = DomUtil.create('dialog', this.className, document.body)
|
||||
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)
|
||||
// From https://css-tricks.com/replace-javascript-dialogs-html-dialog-element/
|
||||
export default class Dialog {
|
||||
constructor(settings = {}) {
|
||||
this.settings = Object.assign(
|
||||
{
|
||||
accept: translate('OK'),
|
||||
cancel: translate('Cancel'),
|
||||
className: '',
|
||||
message: '',
|
||||
template: '',
|
||||
},
|
||||
settings
|
||||
)
|
||||
this.init()
|
||||
}
|
||||
|
||||
get visible() {
|
||||
return this.container.open
|
||||
collectFormData(formData) {
|
||||
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() {
|
||||
this.container.close()
|
||||
getFocusable() {
|
||||
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() {
|
||||
|
@ -27,65 +94,84 @@ export class Dialog {
|
|||
)
|
||||
}
|
||||
|
||||
open({ className, content, modal } = {}) {
|
||||
this.container.innerHTML = ''
|
||||
open(settings = {}) {
|
||||
const dialog = Object.assign({}, this.settings, settings)
|
||||
this.dialog.className = 'umap-dialog window'
|
||||
if (dialog.className) {
|
||||
this.dialog.classList.add(...dialog.className.split(' '))
|
||||
}
|
||||
this.elements.accept.textContent = dialog.accept
|
||||
this.elements.accept.hidden = !dialog.accept
|
||||
this.elements.cancel.textContent = dialog.cancel
|
||||
this.elements.cancel.hidden = !dialog.cancel
|
||||
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.container.style.zIndex = currentZIndex + 1
|
||||
if (modal) this.container.showModal()
|
||||
else this.container.show()
|
||||
if (className) {
|
||||
// Reset
|
||||
this.container.className = this.className
|
||||
this.container.classList.add(...className.split(' '))
|
||||
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()
|
||||
}
|
||||
const buttonsContainer = DomUtil.create('ul', 'buttons', this.container)
|
||||
const closeButton = DomUtil.createButtonIcon(
|
||||
DomUtil.create('li', '', buttonsContainer),
|
||||
'icon-close',
|
||||
translate('Close')
|
||||
|
||||
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 }
|
||||
)
|
||||
DomEvent.on(closeButton, 'click', this.close, this)
|
||||
this.container.appendChild(buttonsContainer)
|
||||
this.container.appendChild(content)
|
||||
DomEvent.once(this.container, 'keydown', (e) => {
|
||||
DomEvent.stop(e)
|
||||
if (e.key === 'Escape') this.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
alert(config) {
|
||||
return this.open(
|
||||
Object.assign({}, config, { cancel: false, message, template: false })
|
||||
)
|
||||
}
|
||||
|
||||
confirm(message, config = {}) {
|
||||
return this.open(Object.assign({}, config, { message, template: false }))
|
||||
}
|
||||
|
||||
prompt(message, fallback = '', config = {}) {
|
||||
const template = `<input type="text" name="prompt" value="${fallback}">`
|
||||
return this.open(Object.assign({}, config, { message, template }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,16 +194,9 @@ U.FeatureMixin = {
|
|||
},
|
||||
|
||||
getAdvancedEditActions: function (container) {
|
||||
L.DomUtil.createButton(
|
||||
'button umap-delete',
|
||||
container,
|
||||
L._('Delete'),
|
||||
function (e) {
|
||||
L.DomEvent.stop(e)
|
||||
if (this.confirmDelete()) this.map.editPanel.close()
|
||||
},
|
||||
this
|
||||
)
|
||||
L.DomUtil.createButton('button umap-delete', container, L._('Delete'), (e) => {
|
||||
this.confirmDelete().then(() => this.map.editPanel.close())
|
||||
})
|
||||
},
|
||||
|
||||
appendEditFieldsets: function (container) {
|
||||
|
@ -272,8 +265,11 @@ U.FeatureMixin = {
|
|||
this.bindPopup(new Class(this))
|
||||
},
|
||||
|
||||
confirmDelete: function () {
|
||||
if (confirm(L._('Are you sure you want to delete the feature?'))) {
|
||||
confirmDelete: async function () {
|
||||
const confirmed = await this.map.dialog.confirm(
|
||||
L._('Are you sure you want to delete the feature?')
|
||||
)
|
||||
if (confirmed) {
|
||||
this.del()
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ U.Map = L.Map.extend({
|
|||
this.urls = new U.URLs(this.options.urls)
|
||||
|
||||
this.panel = new U.Panel(this)
|
||||
this.dialog = new U.Dialog({ className: 'dark' })
|
||||
this.tooltip = new U.Tooltip(this._controlContainer)
|
||||
if (this.hasEditMode()) {
|
||||
this.editPanel = new U.EditPanel(this)
|
||||
|
@ -1639,9 +1640,12 @@ U.Map = L.Map.extend({
|
|||
},
|
||||
|
||||
askForReset: function (e) {
|
||||
if (!confirm(L._('Are you sure you want to cancel your changes?'))) return
|
||||
this.dialog
|
||||
.confirm(L._('Are you sure you want to cancel your changes?'))
|
||||
.then(() => {
|
||||
this.reset()
|
||||
this.disableEdit()
|
||||
})
|
||||
},
|
||||
|
||||
startMarker: function () {
|
||||
|
|
|
@ -21,35 +21,8 @@ U.TableEditor = L.Class.extend({
|
|||
const rename = L.DomUtil.create('i', 'umap-edit', container)
|
||||
del.title = L._('Delete this property on all the features')
|
||||
rename.title = L._('Rename this property on all the features')
|
||||
const doDelete = function () {
|
||||
if (
|
||||
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)
|
||||
L.DomEvent.on(del, 'click', () => this.deleteProperty(property))
|
||||
L.DomEvent.on(rename, 'click', () => this.renameProperty(property))
|
||||
},
|
||||
|
||||
renderRow: function (feature) {
|
||||
|
@ -89,15 +62,41 @@ U.TableEditor = L.Class.extend({
|
|||
return true
|
||||
},
|
||||
|
||||
addProperty: function () {
|
||||
new U.Prompt()
|
||||
.open({
|
||||
className: 'dark',
|
||||
title: L._('Please enter the name of the property'),
|
||||
renameProperty: function (property) {
|
||||
this.datalayer.map.dialog
|
||||
.prompt(L._('Please enter the new name of this property'))
|
||||
.then(({ prompt }) => {
|
||||
if (!prompt || !this.validateName(prompt)) return
|
||||
this.datalayer.eachLayer((feature) => {
|
||||
feature.renameProperty(property, prompt)
|
||||
})
|
||||
.then((newName) => {
|
||||
if (!newName || !this.validateName(newName)) return
|
||||
this.datalayer.indexProperty(newName)
|
||||
this.datalayer.deindexProperty(property)
|
||||
this.datalayer.indexProperty(prompt)
|
||||
this.edit()
|
||||
})
|
||||
},
|
||||
|
||||
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()
|
||||
})
|
||||
},
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</style>
|
||||
|
||||
<template id="umap-alert-template">
|
||||
<div role="dialog" class="dark window">
|
||||
<div role="dialog" class="dark window umap-alert">
|
||||
<div>
|
||||
<p role="alert"></p>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<umap-alert></umap-alert>
|
||||
|
||||
<template id="umap-alert-creation-template">
|
||||
<div role="dialog" class="dark window">
|
||||
<div role="dialog" class="dark window umap-alert">
|
||||
<div>
|
||||
<h3 role="alert"></h3>
|
||||
{% url "login" as login_url %}
|
||||
|
@ -55,7 +55,7 @@
|
|||
<umap-alert-creation></umap-alert-creation>
|
||||
|
||||
<template id="umap-alert-conflict-template">
|
||||
<div role="dialog" class="dark window">
|
||||
<div role="dialog" class="dark window umap-alert">
|
||||
<div>
|
||||
<p role="alert"></p>
|
||||
<div id="conflict-wrapper">
|
||||
|
|
|
@ -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()
|
||||
buttons = page.locator(".umap-browser .datalayer li .icon-delete")
|
||||
expect(buttons).to_have_count(3)
|
||||
page.on("dialog", lambda dialog: dialog.accept())
|
||||
buttons.nth(0).click()
|
||||
buttons.first.click()
|
||||
page.locator("dialog").get_by_role("button", name="OK").click()
|
||||
expect(buttons).to_have_count(2)
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ def test_cancel_deleting_datalayer_should_restore(
|
|||
expect(page.get_by_text("test datalayer")).to_be_hidden()
|
||||
page.once("dialog", lambda dialog: dialog.accept())
|
||||
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(page.locator(".umap-browser").get_by_text("test datalayer")).to_be_visible()
|
||||
|
||||
|
|
|
@ -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(
|
||||
1
|
||||
)
|
||||
page.once("dialog", lambda dialog: dialog.accept())
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -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.get_by_role("link", name="Manage layers").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.locator("dialog").locator("input").fill("newprop")
|
||||
page.locator("dialog").get_by_role("button", name="OK").click()
|
||||
page.locator('input[name="newprop"]').fill("newvalue")
|
||||
page.once("dialog", lambda dialog: dialog.accept())
|
||||
page.hover(".umap-table-editor .tcell")
|
||||
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/.*")):
|
||||
page.get_by_role("button", name="Save").click()
|
||||
saved = DataLayer.objects.last()
|
||||
|
|
Loading…
Reference in a new issue