mirror of
https://github.com/umap-project/umap.git
synced 2025-04-28 19:42:36 +02:00
chore: cleaning core utils and removing DomUtil/DomEvent from modules (#2671)
This commit is contained in:
commit
efaa765b82
10 changed files with 205 additions and 199 deletions
|
@ -7,6 +7,7 @@ import {
|
||||||
import { translate } from './i18n.js'
|
import { translate } from './i18n.js'
|
||||||
import { Request, ServerRequest } from './request.js'
|
import { Request, ServerRequest } from './request.js'
|
||||||
import { escapeHTML, generateId } from './utils.js'
|
import { escapeHTML, generateId } from './utils.js'
|
||||||
|
import * as Utils from './utils.js'
|
||||||
|
|
||||||
export class BaseAutocomplete {
|
export class BaseAutocomplete {
|
||||||
constructor(parent, options) {
|
constructor(parent, options) {
|
||||||
|
@ -291,10 +292,9 @@ class BaseServerAjax extends BaseAjax {
|
||||||
export const SingleMixin = (Base) =>
|
export const SingleMixin = (Base) =>
|
||||||
class extends Base {
|
class extends Base {
|
||||||
initSelectedContainer() {
|
initSelectedContainer() {
|
||||||
return DomUtil.after(
|
const el = Utils.loadTemplate('<div class="umap-singleresult"></div>')
|
||||||
this.input,
|
this.input.parentNode.insertBefore(el, this.input.nextSibling)
|
||||||
DomUtil.element({ tagName: 'div', className: 'umap-singleresult' })
|
return el
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
displaySelected(result) {
|
displaySelected(result) {
|
||||||
|
@ -322,10 +322,9 @@ export const SingleMixin = (Base) =>
|
||||||
export const MultipleMixin = (Base) =>
|
export const MultipleMixin = (Base) =>
|
||||||
class extends Base {
|
class extends Base {
|
||||||
initSelectedContainer() {
|
initSelectedContainer() {
|
||||||
return DomUtil.after(
|
const el = Utils.loadTemplate('<ul class="umap-multiresult"></ul>')
|
||||||
this.input,
|
this.input.parentNode.insertBefore(el, this.input.nextSibling)
|
||||||
DomUtil.element({ tagName: 'ul', className: 'umap-multiresult' })
|
return el
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
displaySelected(result) {
|
displaySelected(result) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { translate } from './i18n.js'
|
||||||
import * as Icon from './rendering/icon.js'
|
import * as Icon from './rendering/icon.js'
|
||||||
import ContextMenu from './ui/contextmenu.js'
|
import ContextMenu from './ui/contextmenu.js'
|
||||||
import * as Utils from './utils.js'
|
import * as Utils from './utils.js'
|
||||||
|
import { SCHEMA } from './schema.js'
|
||||||
|
|
||||||
export default class Browser {
|
export default class Browser {
|
||||||
constructor(umap, leafletMap) {
|
constructor(umap, leafletMap) {
|
||||||
|
@ -21,35 +22,24 @@ export default class Browser {
|
||||||
addFeature(feature, parent) {
|
addFeature(feature, parent) {
|
||||||
if (feature.isFiltered()) return
|
if (feature.isFiltered()) return
|
||||||
if (this.options.inBbox && !feature.isOnScreen(this.bounds)) return
|
if (this.options.inBbox && !feature.isOnScreen(this.bounds)) return
|
||||||
const row = DomUtil.create('li', `${feature.getClassName()} feature`)
|
const template = `
|
||||||
const zoom_to = DomUtil.createButtonIcon(
|
<li class="feature ${feature.getClassName()}">
|
||||||
row,
|
<button class="icon icon-16 icon-zoom" title="${translate('Bring feature to center')}" data-ref=zoom></button>
|
||||||
'icon-zoom',
|
<button class="icon icon-16 show-on-edit icon-edit" title="${translate('Edit this feature')}" data-ref=edit></button>
|
||||||
translate('Bring feature to center')
|
<button class="icon icon-16 show-on-edit icon-delete" title="${translate('Delete this feature')}" data-ref=remove></button>
|
||||||
)
|
<i class="icon icon-16 icon-${feature.getClassName()} feature-color" data-ref=colorBox></i>
|
||||||
const edit = DomUtil.createButtonIcon(
|
<span class="feature-title" data-ref=label></span>
|
||||||
row,
|
</li>
|
||||||
'show-on-edit icon-edit',
|
`
|
||||||
translate('Edit this feature')
|
const [row, { zoom, edit, remove, colorBox, label }] =
|
||||||
)
|
Utils.loadTemplateWithRefs(template)
|
||||||
const del = DomUtil.createButtonIcon(
|
label.textContent = label.title = feature.getDisplayName() || '—'
|
||||||
row,
|
|
||||||
'show-on-edit icon-delete',
|
|
||||||
translate('Delete this feature')
|
|
||||||
)
|
|
||||||
const colorBox = DomUtil.create(
|
|
||||||
'i',
|
|
||||||
`icon icon-16 icon-${feature.getClassName()} feature-color`,
|
|
||||||
row
|
|
||||||
)
|
|
||||||
const title = DomUtil.create('span', 'feature-title', row)
|
|
||||||
const symbol = feature._getIconUrl
|
const symbol = feature._getIconUrl
|
||||||
? Icon.formatUrl(feature._getIconUrl(), feature)
|
? Icon.formatUrl(feature._getIconUrl(), feature)
|
||||||
: null
|
: null
|
||||||
title.textContent = title.title = feature.getDisplayName() || '—'
|
|
||||||
const bgcolor = feature.getPreviewColor()
|
const bgcolor = feature.getPreviewColor()
|
||||||
colorBox.style.backgroundColor = bgcolor
|
colorBox.style.backgroundColor = bgcolor
|
||||||
if (symbol && symbol !== U.SCHEMA.iconUrl.default) {
|
if (symbol && symbol !== SCHEMA.iconUrl.default) {
|
||||||
const icon = Icon.makeElement(symbol, colorBox)
|
const icon = Icon.makeElement(symbol, colorBox)
|
||||||
Icon.setContrast(icon, colorBox, symbol, bgcolor)
|
Icon.setContrast(icon, colorBox, symbol, bgcolor)
|
||||||
} else if (DomUtil.contrastedColor(colorBox, bgcolor)) {
|
} else if (DomUtil.contrastedColor(colorBox, bgcolor)) {
|
||||||
|
@ -58,10 +48,10 @@ export default class Browser {
|
||||||
const viewFeature = (e) => {
|
const viewFeature = (e) => {
|
||||||
feature.zoomTo({ ...e, callback: () => feature.view() })
|
feature.zoomTo({ ...e, callback: () => feature.view() })
|
||||||
}
|
}
|
||||||
DomEvent.on(zoom_to, 'click', viewFeature)
|
zoom.addEventListener('click', viewFeature)
|
||||||
DomEvent.on(title, 'click', viewFeature)
|
label.addEventListener('click', viewFeature)
|
||||||
DomEvent.on(edit, 'click', feature.edit, feature)
|
edit.addEventListener('click', () => feature.edit())
|
||||||
DomEvent.on(del, 'click', feature.del, feature)
|
remove.addEventListener('click', () => feature.del())
|
||||||
// HOTFIX. Remove when this is released:
|
// HOTFIX. Remove when this is released:
|
||||||
// https://github.com/Leaflet/Leaflet/pull/9052
|
// https://github.com/Leaflet/Leaflet/pull/9052
|
||||||
DomEvent.disableClickPropagation(row)
|
DomEvent.disableClickPropagation(row)
|
||||||
|
@ -75,45 +65,51 @@ export default class Browser {
|
||||||
addDataLayer(datalayer, parent) {
|
addDataLayer(datalayer, parent) {
|
||||||
let className = `datalayer ${datalayer.getHidableClass()}`
|
let className = `datalayer ${datalayer.getHidableClass()}`
|
||||||
if (this.mode !== 'layers') className += ' show-list'
|
if (this.mode !== 'layers') className += ' show-list'
|
||||||
const container = DomUtil.create('div', className, parent)
|
const [container, { headline, toolbox, toggle, label }] =
|
||||||
const headline = DomUtil.create('h5', '', container)
|
Utils.loadTemplateWithRefs(`
|
||||||
container.id = this.datalayerId(datalayer)
|
<div class="${className}" id="${this.datalayerId(datalayer)}">
|
||||||
const ul = DomUtil.create('ul', '', container)
|
<h5 data-ref=headline>
|
||||||
|
<i class="icon icon-16 datalayer-toggle-list" data-ref=toggle></i>
|
||||||
|
<span data-ref=toolbox></span>
|
||||||
|
<span class="datalayer-name" data-id="${datalayer.id}" data-ref=label></span>
|
||||||
|
<span class="datalayer-counter"></span>
|
||||||
|
</h5>
|
||||||
|
<ul></ul>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
datalayer.renderToolbox(toolbox)
|
||||||
|
parent.appendChild(container)
|
||||||
|
const toggleList = () => parent.classList.toggle('show-list')
|
||||||
|
toggle.addEventListener('click', toggleList)
|
||||||
|
label.addEventListener('click', toggleList)
|
||||||
this.updateDatalayer(datalayer)
|
this.updateDatalayer(datalayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDatalayer(datalayer) {
|
updateDatalayer(datalayer) {
|
||||||
// Compute once, but use it for each feature later.
|
// Compute once, but use it for each feature later.
|
||||||
this.bounds = this._leafletMap.getBounds()
|
this.bounds = this._leafletMap.getBounds()
|
||||||
const parent = DomUtil.get(this.datalayerId(datalayer))
|
const id = this.datalayerId(datalayer)
|
||||||
|
const parent = document.getElementById(id)
|
||||||
// Panel is not open
|
// Panel is not open
|
||||||
if (!parent) return
|
if (!parent) return
|
||||||
parent.classList.toggle('off', !datalayer.isVisible())
|
parent.classList.toggle('off', !datalayer.isVisible())
|
||||||
|
const label = parent.querySelector('.datalayer-name')
|
||||||
const container = parent.querySelector('ul')
|
const container = parent.querySelector('ul')
|
||||||
const headline = parent.querySelector('h5')
|
|
||||||
const toggleList = () => parent.classList.toggle('show-list')
|
|
||||||
headline.innerHTML = ''
|
|
||||||
const toggle = DomUtil.create('i', 'icon icon-16 datalayer-toggle-list', headline)
|
|
||||||
DomEvent.on(toggle, 'click', toggleList)
|
|
||||||
datalayer.renderToolbox(headline)
|
|
||||||
const name = DomUtil.create('span', 'datalayer-name', headline)
|
|
||||||
name.textContent = name.title = datalayer.options.name
|
|
||||||
DomEvent.on(name, 'click', toggleList)
|
|
||||||
container.innerHTML = ''
|
container.innerHTML = ''
|
||||||
datalayer.eachFeature((feature) => this.addFeature(feature, container))
|
datalayer.eachFeature((feature) => this.addFeature(feature, container))
|
||||||
|
datalayer.propagate(['properties.name'])
|
||||||
const total = datalayer.count()
|
const total = datalayer.count()
|
||||||
if (!total) return
|
if (!total) return
|
||||||
const current = container.querySelectorAll('li').length
|
const current = container.querySelectorAll('li').length
|
||||||
const count = total === current ? total : `${current}/${total}`
|
const count = total === current ? total : `${current}/${total}`
|
||||||
const counter = DomUtil.create('span', 'datalayer-counter', headline)
|
const counter = parent.querySelector('.datalayer-counter')
|
||||||
counter.textContent = `(${count})`
|
counter.textContent = `(${count})`
|
||||||
counter.title = translate(`Features in this layer: ${count}`)
|
counter.title = translate(`Features in this layer: ${count}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleBadge() {
|
toggleBadge() {
|
||||||
U.Utils.toggleBadge(this.filtersTitle, this.hasFilters())
|
Utils.toggleBadge(this.filtersTitle, this.hasFilters())
|
||||||
U.Utils.toggleBadge('.umap-control-browse', this.hasFilters())
|
Utils.toggleBadge('.umap-control-browse', this.hasFilters())
|
||||||
}
|
}
|
||||||
|
|
||||||
onFormChange() {
|
onFormChange() {
|
||||||
|
@ -157,21 +153,51 @@ export default class Browser {
|
||||||
open(mode) {
|
open(mode) {
|
||||||
// Force only if mode is known, otherwise keep current mode.
|
// Force only if mode is known, otherwise keep current mode.
|
||||||
if (mode) this.mode = mode
|
if (mode) this.mode = mode
|
||||||
const container = DomUtil.create('div')
|
const template = `
|
||||||
|
<div>
|
||||||
|
<h3><i class="icon icon-16 icon-layers"></i>${translate('Data browser')}</h3>
|
||||||
|
<details class="filters" data-ref="details">
|
||||||
|
<summary data-ref=filtersTitle><i class="icon icon-16 icon-filters"></i>${translate('Filters')}</summary>
|
||||||
|
<fieldset>
|
||||||
|
<div data-ref=formContainer>
|
||||||
|
</div>
|
||||||
|
<button class="flat" type="button" data-ref=reset><i class="icon icon-16 icon-restore" title=""></i>${translate('Reset all')}</button>
|
||||||
|
</fieldset>
|
||||||
|
</details>
|
||||||
|
<div class="main-toolbox">
|
||||||
|
<i class="icon icon-16 icon-eye" title="${translate('show/hide all layers')}" data-ref="toggle"></i>
|
||||||
|
<i class="icon icon-16 icon-zoom" title="${translate('zoom to data extent')}" data-ref="fitBounds"></i>
|
||||||
|
<i class="icon icon-16 icon-download" title="${translate('download visible data')}" data-ref="download"></i>
|
||||||
|
</div>
|
||||||
|
<div data-ref=dataContainer></div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
const [
|
||||||
|
container,
|
||||||
|
{
|
||||||
|
details,
|
||||||
|
filtersTitle,
|
||||||
|
toggle,
|
||||||
|
fitBounds,
|
||||||
|
download,
|
||||||
|
dataContainer,
|
||||||
|
formContainer,
|
||||||
|
reset,
|
||||||
|
},
|
||||||
|
] = Utils.loadTemplateWithRefs(template)
|
||||||
// HOTFIX. Remove when this is released:
|
// HOTFIX. Remove when this is released:
|
||||||
// https://github.com/Leaflet/Leaflet/pull/9052
|
// https://github.com/Leaflet/Leaflet/pull/9052
|
||||||
DomEvent.disableClickPropagation(container)
|
DomEvent.disableClickPropagation(container)
|
||||||
|
details.open = this.mode === 'filters'
|
||||||
|
toggle.addEventListener('click', () => this.toggleLayers())
|
||||||
|
fitBounds.addEventListener('click', () => this._umap.fitDataBounds())
|
||||||
|
download.addEventListener('click', () => this.downloadVisible(download))
|
||||||
|
download.hidden = this._umap.getProperty('embedControl') === false
|
||||||
|
|
||||||
DomUtil.createTitle(container, translate('Data browser'), 'icon-layers')
|
this.filtersTitle = filtersTitle
|
||||||
this.formContainer = DomUtil.createFieldset(container, L._('Filters'), {
|
this.dataContainer = dataContainer
|
||||||
on: this.mode === 'filters',
|
this.formContainer = formContainer
|
||||||
className: 'filters',
|
|
||||||
icon: 'icon-filters',
|
|
||||||
})
|
|
||||||
this.filtersTitle = container.querySelector('summary')
|
|
||||||
this.toggleBadge()
|
this.toggleBadge()
|
||||||
this.addMainToolbox(container)
|
|
||||||
this.dataContainer = DomUtil.create('div', '', container)
|
|
||||||
|
|
||||||
let fields = [
|
let fields = [
|
||||||
[
|
[
|
||||||
|
@ -184,27 +210,19 @@ export default class Browser {
|
||||||
builder.on('set', () => this.onFormChange())
|
builder.on('set', () => this.onFormChange())
|
||||||
let filtersBuilder
|
let filtersBuilder
|
||||||
this.formContainer.appendChild(builder.build())
|
this.formContainer.appendChild(builder.build())
|
||||||
DomEvent.on(builder.form, 'reset', () => {
|
builder.form.addEventListener('reset', () => {
|
||||||
window.setTimeout(builder.syncAll.bind(builder))
|
window.setTimeout(builder.syncAll.bind(builder))
|
||||||
})
|
})
|
||||||
if (this._umap.properties.facetKey) {
|
if (this._umap.properties.facetKey) {
|
||||||
fields = this._umap.facets.build()
|
fields = this._umap.facets.build()
|
||||||
filtersBuilder = new Form(this._umap.facets, fields)
|
filtersBuilder = new Form(this._umap.facets, fields)
|
||||||
filtersBuilder.on('set', () => this.onFormChange())
|
filtersBuilder.on('set', () => this.onFormChange())
|
||||||
DomEvent.on(filtersBuilder.form, 'reset', () => {
|
filtersBuilder.form.addEventListener('reset', () => {
|
||||||
window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder))
|
window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder))
|
||||||
})
|
})
|
||||||
this.formContainer.appendChild(filtersBuilder.build())
|
this.formContainer.appendChild(filtersBuilder.build())
|
||||||
}
|
}
|
||||||
const reset = DomUtil.createButton('flat', this.formContainer, '', () =>
|
reset.addEventListener('click', () => this.resetFilters())
|
||||||
this.resetFilters()
|
|
||||||
)
|
|
||||||
DomUtil.createIcon(reset, 'icon-restore')
|
|
||||||
DomUtil.element({
|
|
||||||
tagName: 'span',
|
|
||||||
parent: reset,
|
|
||||||
textContent: translate('Reset all'),
|
|
||||||
})
|
|
||||||
|
|
||||||
this._umap.panel.open({
|
this._umap.panel.open({
|
||||||
content: container,
|
content: container,
|
||||||
|
@ -220,21 +238,6 @@ export default class Browser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addMainToolbox(container) {
|
|
||||||
const [toolbox, { toggle, fitBounds, download }] = Utils.loadTemplateWithRefs(`
|
|
||||||
<div class="main-toolbox">
|
|
||||||
<i class="icon icon-16 icon-eye" title="${translate('show/hide all layers')}" data-ref="toggle"></i>
|
|
||||||
<i class="icon icon-16 icon-zoom" title="${translate('zoom to data extent')}" data-ref="fitBounds"></i>
|
|
||||||
<i class="icon icon-16 icon-download" title="${translate('download visible data')}" data-ref="download"></i>
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
container.appendChild(toolbox)
|
|
||||||
toggle.addEventListener('click', () => this.toggleLayers())
|
|
||||||
fitBounds.addEventListener('click', () => this._umap.fitDataBounds())
|
|
||||||
download.addEventListener('click', () => this.downloadVisible(download))
|
|
||||||
download.hidden = this._umap.getProperty('embedControl') === false
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadVisible(element) {
|
downloadVisible(element) {
|
||||||
const menu = new ContextMenu({ fixed: true })
|
const menu = new ContextMenu({ fixed: true })
|
||||||
const items = []
|
const items = []
|
||||||
|
@ -265,15 +268,13 @@ export default class Browser {
|
||||||
}
|
}
|
||||||
|
|
||||||
static backButton(umap) {
|
static backButton(umap) {
|
||||||
const button = DomUtil.createButtonIcon(
|
const button = Utils.loadTemplate(
|
||||||
DomUtil.create('li', '', undefined),
|
`<button class="icon icon-16 icon-back" title="${translate('Back to browser')}"></button>`
|
||||||
'icon-back',
|
|
||||||
translate('Back to browser')
|
|
||||||
)
|
)
|
||||||
// Fixme: remove me when this is merged and released
|
// Fixme: remove me when this is merged and released
|
||||||
// https://github.com/Leaflet/Leaflet/pull/9052
|
// https://github.com/Leaflet/Leaflet/pull/9052
|
||||||
DomEvent.disableClickPropagation(button)
|
DomEvent.disableClickPropagation(button)
|
||||||
DomEvent.on(button, 'click', () => umap.openBrowser())
|
button.addEventListener('click', () => umap.openBrowser())
|
||||||
return button
|
return button
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,6 +130,10 @@ export class DataLayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
render(fields, builder) {
|
render(fields, builder) {
|
||||||
|
// Propagate will remove the fields it has already
|
||||||
|
// processed
|
||||||
|
fields = this.propagate(fields)
|
||||||
|
|
||||||
const impacts = Utils.getImpactsFromSchema(fields)
|
const impacts = Utils.getImpactsFromSchema(fields)
|
||||||
|
|
||||||
for (const impact of impacts) {
|
for (const impact of impacts) {
|
||||||
|
@ -153,6 +157,29 @@ export class DataLayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This method does a targeted update of the UI,
|
||||||
|
// it whould be merged with `render`` method and the
|
||||||
|
// SCHEMA at some point
|
||||||
|
propagate(fields = []) {
|
||||||
|
const impacts = {
|
||||||
|
'properties.name': () => {
|
||||||
|
Utils.eachElement('.datalayer-name', (el) => {
|
||||||
|
if (el.dataset.id === this.id) {
|
||||||
|
el.textContent = this.getName()
|
||||||
|
el.title = this.getName()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for (const [field, impact] of Object.entries(impacts)) {
|
||||||
|
if (!fields.length || fields.includes(field)) {
|
||||||
|
impact()
|
||||||
|
fields = fields.filter((item) => item !== field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
showAtLoad() {
|
showAtLoad() {
|
||||||
return this.autoLoaded() && this.showAtZoom()
|
return this.autoLoaded() && this.showAtZoom()
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,10 @@ const BaseButton = Control.extend({
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
const [container, { button }] = Utils.loadTemplateWithRefs(template)
|
const [container, { button }] = Utils.loadTemplateWithRefs(template)
|
||||||
button.addEventListener('click', () => this.onClick())
|
button.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
this.onClick()
|
||||||
|
})
|
||||||
button.addEventListener('dblclick', (event) => {
|
button.addEventListener('dblclick', (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { DomEvent, DomUtil, stamp } from '../../vendors/leaflet/leaflet-src.esm.js'
|
import { stamp } from '../../vendors/leaflet/leaflet-src.esm.js'
|
||||||
import { AutocompleteDatalist } from './autocomplete.js'
|
import { AutocompleteDatalist } from './autocomplete.js'
|
||||||
import { MutatingForm } from './form/builder.js'
|
import { MutatingForm } from './form/builder.js'
|
||||||
import { translate } from './i18n.js'
|
import { translate } from './i18n.js'
|
||||||
|
@ -119,10 +119,9 @@ class Rule {
|
||||||
'options.smoothFactor',
|
'options.smoothFactor',
|
||||||
'options.dashArray',
|
'options.dashArray',
|
||||||
]
|
]
|
||||||
const container = DomUtil.create('div')
|
|
||||||
const builder = new MutatingForm(this, options)
|
const builder = new MutatingForm(this, options)
|
||||||
const defaultShapeProperties = DomUtil.add('div', '', container)
|
const container = document.createElement('div')
|
||||||
defaultShapeProperties.appendChild(builder.build())
|
container.appendChild(builder.build())
|
||||||
const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input)
|
const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input)
|
||||||
const properties = this._umap.allProperties()
|
const properties = this._umap.allProperties()
|
||||||
autocomplete.suggestions = properties
|
autocomplete.suggestions = properties
|
||||||
|
@ -137,43 +136,45 @@ class Rule {
|
||||||
.map((str) => `${value}${str || ''}`)
|
.map((str) => `${value}${str || ''}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this._umap.editPanel.open({ content: container, highlight: 'settings' })
|
const backButton = Utils.loadTemplate(`
|
||||||
|
<button class="flat" type="button" data-ref="add">
|
||||||
|
<i class="icon icon-16 icon-back" title="${translate('Back to list')}"></i>
|
||||||
|
</button>`)
|
||||||
|
backButton.addEventListener('click', () =>
|
||||||
|
this._umap.edit().then(() => {
|
||||||
|
this._umap.editPanel.container.querySelector('details#rules').open = true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
this._umap.editPanel.open({
|
||||||
|
content: container,
|
||||||
|
highlight: 'settings',
|
||||||
|
actions: [backButton],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToolbox(row) {
|
renderToolbox(ul) {
|
||||||
row.classList.toggle('off', !this.active)
|
const template = `
|
||||||
const toggle = DomUtil.createButtonIcon(
|
<li data-id="${stamp(this)}" class="orderable">
|
||||||
row,
|
<button class="icon icon-16 icon-eye" title="${translate('Toggle rule')}" data-ref=toggle></button>
|
||||||
'icon-eye',
|
<button class="icon icon-16 icon-edit show-on-edit" title="${translate('Edit')}" data-ref=edit></button>
|
||||||
translate('Show/hide layer')
|
<button class="icon icon-16 icon-delete show-on-edit" title="${translate('Delete rule')}" data-ref=remove></button>
|
||||||
)
|
<span>${this.condition || translate('empty rule')}</span>
|
||||||
const edit = DomUtil.createButtonIcon(
|
<i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
|
||||||
row,
|
</li>
|
||||||
'icon-edit show-on-edit',
|
`
|
||||||
translate('Edit')
|
const [li, { toggle, edit, remove }] = Utils.loadTemplateWithRefs(template)
|
||||||
)
|
ul.appendChild(li)
|
||||||
const remove = DomUtil.createButtonIcon(
|
li.classList.toggle('off', !this.active)
|
||||||
row,
|
edit.addEventListener('click', () => this.edit())
|
||||||
'icon-delete show-on-edit',
|
remove.addEventListener('click', () => {
|
||||||
translate('Delete layer')
|
if (!confirm(translate('Are you sure you want to delete this rule?'))) return
|
||||||
)
|
this._delete()
|
||||||
DomEvent.on(edit, 'click', this.edit, this)
|
this._umap.editPanel.close()
|
||||||
DomEvent.on(
|
})
|
||||||
remove,
|
toggle.addEventListener('click', () => {
|
||||||
'click',
|
|
||||||
function () {
|
|
||||||
if (!confirm(translate('Are you sure you want to delete this rule?'))) return
|
|
||||||
this._delete()
|
|
||||||
this._umap.editPanel.close()
|
|
||||||
},
|
|
||||||
this
|
|
||||||
)
|
|
||||||
DomUtil.add('span', '', row, this.condition || translate('empty rule'))
|
|
||||||
DomUtil.createIcon(row, 'icon-drag', translate('Drag to reorder'))
|
|
||||||
row.dataset.id = stamp(this)
|
|
||||||
DomEvent.on(toggle, 'click', () => {
|
|
||||||
this.active = !this.active
|
this.active = !this.active
|
||||||
row.classList.toggle('off', !this.active)
|
li.classList.toggle('off', !this.active)
|
||||||
this._umap.render(['rules'])
|
this._umap.render(['rules'])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -207,8 +208,9 @@ export default class Rules {
|
||||||
}
|
}
|
||||||
|
|
||||||
onReorder(src, dst, initialIndex, finalIndex) {
|
onReorder(src, dst, initialIndex, finalIndex) {
|
||||||
const moved = this.rules.find((rule) => stamp(rule) === src.dataset.id)
|
const oldRules = Utils.CopyJSON(this._umap.properties.rules || {})
|
||||||
const reference = this.rules.find((rule) => stamp(rule) === dst.dataset.id)
|
const moved = this.rules.find((rule) => stamp(rule) === +src.dataset.id)
|
||||||
|
const reference = this.rules.find((rule) => stamp(rule) === +dst.dataset.id)
|
||||||
const movedIdx = this.rules.indexOf(moved)
|
const movedIdx = this.rules.indexOf(moved)
|
||||||
let referenceIdx = this.rules.indexOf(reference)
|
let referenceIdx = this.rules.indexOf(reference)
|
||||||
const minIndex = Math.min(movedIdx, referenceIdx)
|
const minIndex = Math.min(movedIdx, referenceIdx)
|
||||||
|
@ -222,20 +224,28 @@ export default class Rules {
|
||||||
this.rules.splice(newIdx, 0, moved)
|
this.rules.splice(newIdx, 0, moved)
|
||||||
this._umap.render(['rules'])
|
this._umap.render(['rules'])
|
||||||
this.commit()
|
this.commit()
|
||||||
|
this._umap.sync.update('properties.rules', this._umap.properties.rules, oldRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
edit(container) {
|
edit(container) {
|
||||||
const body = DomUtil.createFieldset(container, translate('Conditional style rules'))
|
const template = `
|
||||||
|
<details id="rules">
|
||||||
|
<summary>${translate('Conditional style rules')}</summary>
|
||||||
|
<fieldset>
|
||||||
|
<ul data-ref=ul></ul>
|
||||||
|
<button class="umap-add" type="button" data-ref=add>${translate('Add rule')}</button>
|
||||||
|
</fieldset>
|
||||||
|
</details>
|
||||||
|
`
|
||||||
|
const [body, { ul, add }] = Utils.loadTemplateWithRefs(template)
|
||||||
if (this.rules.length) {
|
if (this.rules.length) {
|
||||||
const ul = DomUtil.create('ul', '', body)
|
|
||||||
for (const rule of this.rules) {
|
for (const rule of this.rules) {
|
||||||
rule.renderToolbox(DomUtil.create('li', 'orderable', ul))
|
rule.renderToolbox(ul)
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderable = new Orderable(ul, this.onReorder.bind(this))
|
const orderable = new Orderable(ul, this.onReorder.bind(this))
|
||||||
}
|
}
|
||||||
|
add.addEventListener('click', () => this.addRule())
|
||||||
DomUtil.createButton('umap-add', body, translate('Add rule'), this.addRule, this)
|
container.appendChild(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
addRule() {
|
addRule() {
|
||||||
|
|
|
@ -1182,7 +1182,7 @@ export default class Umap {
|
||||||
}
|
}
|
||||||
this._advancedActions(container)
|
this._advancedActions(container)
|
||||||
|
|
||||||
this.editPanel.open({
|
return this.editPanel.open({
|
||||||
content: container,
|
content: container,
|
||||||
className: 'dark',
|
className: 'dark',
|
||||||
highlight: 'settings',
|
highlight: 'settings',
|
||||||
|
@ -1513,12 +1513,17 @@ export default class Umap {
|
||||||
|
|
||||||
editDatalayers() {
|
editDatalayers() {
|
||||||
if (!this.editEnabled) return
|
if (!this.editEnabled) return
|
||||||
const container = DomUtil.create('div')
|
const template = `
|
||||||
DomUtil.createTitle(container, translate('Manage layers'), 'icon-layers')
|
<div>
|
||||||
const ul = DomUtil.create('ul', '', container)
|
<h3><i class="icon icon-16 icon-layers"></i>${translate('Manage layers')}</h3>
|
||||||
|
<ul data-ref=ul></ul>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
const [container, { ul }] = Utils.loadTemplateWithRefs(template)
|
||||||
this.eachDataLayerReverse((datalayer) => {
|
this.eachDataLayerReverse((datalayer) => {
|
||||||
const row = DomUtil.create('li', 'orderable', ul)
|
const row = Utils.loadTemplate(
|
||||||
DomUtil.createIcon(row, 'icon-drag', translate('Drag to reorder'))
|
`<li class="orderable"><i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i></li>`
|
||||||
|
)
|
||||||
datalayer.renderToolbox(row)
|
datalayer.renderToolbox(row)
|
||||||
const builder = new MutatingForm(
|
const builder = new MutatingForm(
|
||||||
datalayer,
|
datalayer,
|
||||||
|
@ -1529,6 +1534,7 @@ export default class Umap {
|
||||||
row.appendChild(form)
|
row.appendChild(form)
|
||||||
row.classList.toggle('off', !datalayer.isVisible())
|
row.classList.toggle('off', !datalayer.isVisible())
|
||||||
row.dataset.id = datalayer.id
|
row.dataset.id = datalayer.id
|
||||||
|
ul.appendChild(row)
|
||||||
})
|
})
|
||||||
const onReorder = (src, dst, initialIndex, finalIndex) => {
|
const onReorder = (src, dst, initialIndex, finalIndex) => {
|
||||||
const movedLayer = this.datalayers[src.dataset.id]
|
const movedLayer = this.datalayers[src.dataset.id]
|
||||||
|
|
|
@ -65,18 +65,6 @@ L.DomUtil.createButton = (className, container, content, callback, context) => {
|
||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
|
|
||||||
L.DomUtil.createLink = (className, container, content, url, target, title) => {
|
|
||||||
const el = L.DomUtil.add('a', className, container, content)
|
|
||||||
el.href = url
|
|
||||||
if (target) {
|
|
||||||
el.target = target
|
|
||||||
}
|
|
||||||
if (title) {
|
|
||||||
el.title = title
|
|
||||||
}
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
L.DomUtil.createIcon = (parent, className, title, size = 16) => {
|
L.DomUtil.createIcon = (parent, className, title, size = 16) => {
|
||||||
return L.DomUtil.element({
|
return L.DomUtil.element({
|
||||||
tagName: 'i',
|
tagName: 'i',
|
||||||
|
@ -140,16 +128,6 @@ L.DomUtil.element = ({ tagName, parent, ...attrs }) => {
|
||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
|
|
||||||
L.DomUtil.before = (target, el) => {
|
|
||||||
target.parentNode.insertBefore(el, target)
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
L.DomUtil.after = (target, el) => {
|
|
||||||
target.parentNode.insertBefore(el, target.nextSibling)
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
// From https://gist.github.com/Accudio/b9cb16e0e3df858cef0d31e38f1fe46f
|
// From https://gist.github.com/Accudio/b9cb16e0e3df858cef0d31e38f1fe46f
|
||||||
// convert colour in range 0-255 to the modifier used within luminance calculation
|
// convert colour in range 0-255 to the modifier used within luminance calculation
|
||||||
L.DomUtil.colourMod = (colour) => {
|
L.DomUtil.colourMod = (colour) => {
|
||||||
|
@ -214,24 +192,6 @@ L.DomUtil.contrastedColor = (el, bgcolor) => {
|
||||||
if (bgcolor) _CACHE_CONSTRAST[bgcolor] = out
|
if (bgcolor) _CACHE_CONSTRAST[bgcolor] = out
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
L.DomEvent.once = (el, types, fn, context) => {
|
|
||||||
// cf https://github.com/Leaflet/Leaflet/pull/3528#issuecomment-134551575
|
|
||||||
|
|
||||||
if (typeof types === 'object') {
|
|
||||||
for (const type in types) {
|
|
||||||
L.DomEvent.once(el, type, types[type], fn)
|
|
||||||
}
|
|
||||||
return L.DomEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
const handler = L.bind(() => {
|
|
||||||
L.DomEvent.off(el, types, fn, context).off(el, types, handler, context)
|
|
||||||
}, L.DomEvent)
|
|
||||||
|
|
||||||
// add a listener that's executed once and removed after that
|
|
||||||
return L.DomEvent.on(el, types, fn, context).on(el, types, handler, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
L.LatLng.prototype.isValid = function () {
|
L.LatLng.prototype.isValid = function () {
|
||||||
return (
|
return (
|
||||||
Number.isFinite(this.lat) &&
|
Number.isFinite(this.lat) &&
|
||||||
|
|
|
@ -465,19 +465,19 @@ def test_main_toolbox_toggle_all_layers(live_server, map, page):
|
||||||
expect(page.locator(".datalayer.off")).to_have_count(1)
|
expect(page.locator(".datalayer.off")).to_have_count(1)
|
||||||
|
|
||||||
# Click on button
|
# Click on button
|
||||||
page.locator(".umap-browser [data-ref=toggle]").click()
|
page.locator(".umap-browser").get_by_title("Show/hide all layers").click()
|
||||||
# Should have hidden the two other layers
|
# Should have hidden the two other layers
|
||||||
expect(page.locator(".datalayer.off")).to_have_count(3)
|
expect(page.locator(".datalayer.off")).to_have_count(3)
|
||||||
expect(markers).to_have_count(0)
|
expect(markers).to_have_count(0)
|
||||||
|
|
||||||
# Click again
|
# Click again
|
||||||
page.locator(".umap-browser [data-ref=toggle]").click()
|
page.locator(".umap-browser").get_by_title("Show/hide all layers").click()
|
||||||
# Should shown all layers
|
# Should shown all layers
|
||||||
expect(page.locator(".datalayer.off")).to_have_count(0)
|
expect(page.locator(".datalayer.off")).to_have_count(0)
|
||||||
expect(markers).to_have_count(3)
|
expect(markers).to_have_count(3)
|
||||||
|
|
||||||
# Click again
|
# Click again
|
||||||
page.locator(".umap-browser [data-ref=toggle]").click()
|
page.locator(".umap-browser").get_by_title("Show/hide all layers").click()
|
||||||
# Should hidden again all layers
|
# Should hidden again all layers
|
||||||
expect(page.locator(".datalayer.off")).to_have_count(3)
|
expect(page.locator(".datalayer.off")).to_have_count(3)
|
||||||
expect(markers).to_have_count(0)
|
expect(markers).to_have_count(0)
|
||||||
|
|
|
@ -281,10 +281,10 @@ def test_can_deactive_rule_from_list(live_server, page, openmap):
|
||||||
page.get_by_role("button", name="Edit").click()
|
page.get_by_role("button", name="Edit").click()
|
||||||
page.get_by_role("button", name="Map advanced properties").click()
|
page.get_by_role("button", name="Map advanced properties").click()
|
||||||
page.get_by_text("Conditional style rules").click()
|
page.get_by_text("Conditional style rules").click()
|
||||||
page.get_by_role("button", name="Show/hide layer").click()
|
page.get_by_role("button", name="Toggle rule").click()
|
||||||
colors = getColors(markers)
|
colors = getColors(markers)
|
||||||
assert colors.count("rgb(240, 248, 255)") == 0
|
assert colors.count("rgb(240, 248, 255)") == 0
|
||||||
page.get_by_role("button", name="Show/hide layer").click()
|
page.get_by_role("button", name="Toggle rule").click()
|
||||||
colors = getColors(markers)
|
colors = getColors(markers)
|
||||||
assert colors.count("rgb(240, 248, 255)") == 3
|
assert colors.count("rgb(240, 248, 255)") == 3
|
||||||
|
|
||||||
|
|
|
@ -180,9 +180,9 @@ def test_sortkey_impacts_datalayerindex(map, live_server, page):
|
||||||
first_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(0)
|
first_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(0)
|
||||||
second_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(1)
|
second_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(1)
|
||||||
third_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(2)
|
third_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(2)
|
||||||
assert "X Third" == first_listed_feature.text_content()
|
assert "X Third" == first_listed_feature.text_content().strip()
|
||||||
assert "Y Second" == second_listed_feature.text_content()
|
assert "Y Second" == second_listed_feature.text_content().strip()
|
||||||
assert "Z First" == third_listed_feature.text_content()
|
assert "Z First" == third_listed_feature.text_content().strip()
|
||||||
|
|
||||||
# Change the default sortkey to be "key"
|
# Change the default sortkey to be "key"
|
||||||
page.get_by_role("button", name="Edit").click()
|
page.get_by_role("button", name="Edit").click()
|
||||||
|
@ -201,9 +201,9 @@ def test_sortkey_impacts_datalayerindex(map, live_server, page):
|
||||||
first_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(0)
|
first_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(0)
|
||||||
second_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(1)
|
second_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(1)
|
||||||
third_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(2)
|
third_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(2)
|
||||||
assert "Z First" == first_listed_feature.text_content()
|
assert "Z First" == first_listed_feature.text_content().strip()
|
||||||
assert "Y Second" == second_listed_feature.text_content()
|
assert "Y Second" == second_listed_feature.text_content().strip()
|
||||||
assert "X Third" == third_listed_feature.text_content()
|
assert "X Third" == third_listed_feature.text_content().strip()
|
||||||
|
|
||||||
|
|
||||||
def test_hover_tooltip_setting_should_be_persistent(live_server, map, page):
|
def test_hover_tooltip_setting_should_be_persistent(live_server, map, page):
|
||||||
|
|
Loading…
Reference in a new issue