wip: first step in moving features to modules (work in progress)

This commit is contained in:
Yohan Boniface 2024-07-24 23:18:34 +02:00
parent f196bda25f
commit 7aa07709b3
12 changed files with 1579 additions and 95 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,3 @@
// Uses U.Marker, U.Polygon, U.Polyline, U.TableEditor not yet ES modules
// Uses U.FormBuilder not available as ESM
// FIXME: this module should not depend on Leaflet
@ -19,6 +18,7 @@ import {
} from '../../components/alerts/alert.js'
import { translate } from '../i18n.js'
import { DataLayerPermissions } from '../permissions.js'
import { Point, LineString, Polygon } from './features.js'
export const LAYER_TYPES = [DefaultLayer, Cluster, Heat, Choropleth, Categorized]
@ -32,7 +32,7 @@ export class DataLayer {
this.map = map
this.sync = map.sync_engine.proxy(this)
this._index = Array()
this._layers = {}
this._features = {}
this._geojson = null
this._propertiesIndex = []
this._loaded = false // Are layer metadata loaded
@ -188,23 +188,14 @@ export class DataLayer {
if (visible) this.map.removeLayer(this.layer)
const Class = LAYER_MAP[this.options.type] || DefaultLayer
this.layer = new Class(this)
this.eachLayer(this.showFeature)
this.eachFeature(this.showFeature)
if (visible) this.show()
this.propagateRemote()
}
eachLayer(method, context) {
for (const i in this._layers) {
method.call(context || this, this._layers[i])
}
return this
}
eachFeature(method, context) {
if (this.isBrowsable()) {
for (let i = 0; i < this._index.length; i++) {
method.call(context || this, this._layers[this._index[i]])
}
for (const idx of this._index) {
method.call(context || this, this._features[idx])
}
return this
}
@ -254,7 +245,7 @@ export class DataLayer {
clear() {
this.layer.clearLayers()
this._layers = {}
this._features = {}
this._index = Array()
if (this._geojson) {
this.backupData()
@ -268,13 +259,9 @@ export class DataLayer {
}
reindex() {
const features = []
this.eachFeature((feature) => features.push(feature))
const features = Object.values(this._features)
Utils.sortFeatures(features, this.map.getOption('sortKey'), L.lang)
this._index = []
for (let i = 0; i < features.length; i++) {
this._index.push(stamp(features[i]))
}
this._index = features.map((feature) => stamp(feature))
}
showAtZoom() {
@ -371,28 +358,28 @@ export class DataLayer {
showFeature(feature) {
if (feature.isFiltered()) return
this.layer.addLayer(feature)
this.layer.addLayer(feature.ui)
}
addLayer(feature) {
addFeature(feature) {
const id = stamp(feature)
feature.connectToDataLayer(this)
this._index.push(id)
this._layers[id] = feature
this._features[id] = feature
this.indexProperties(feature)
this.map.features_index[feature.getSlug()] = feature
this.showFeature(feature)
if (this.hasDataLoaded()) this.dataChanged()
}
removeLayer(feature, sync) {
removeFeature(feature, sync) {
const id = stamp(feature)
if (sync !== false) feature.sync.delete()
this.layer.removeLayer(feature)
// if (sync !== false) feature.sync.delete()
this.layer.removeLayer(feature.ui)
delete this.map.features_index[feature.getSlug()]
feature.disconnectFromDataLayer(this)
this._index.splice(this._index.indexOf(id), 1)
delete this._layers[id]
delete this.map.features_index[feature.getSlug()]
delete this._features[id]
if (this.hasDataLoaded() && this.isVisible()) this.dataChanged()
}
@ -416,7 +403,7 @@ export class DataLayer {
}
sortedValues(property) {
return Object.values(this._layers)
return Object.values(this._features)
.map((feature) => feature.properties[property])
.filter((val, idx, arr) => arr.indexOf(val) === idx)
.sort(Utils.naturalSort)
@ -454,7 +441,7 @@ export class DataLayer {
const feature = this.geoJSONToLeaflet({ geometry, geojson })
if (feature) {
this.addLayer(feature)
this.addFeature(feature)
if (sync) feature.onCommit()
return feature
}
@ -497,7 +484,8 @@ export class DataLayer {
feature.setLatLng(latlng)
return feature
}
return this._pointToLayer(geojson, latlng, id)
return new Point(this, geojson)
// return this._pointToLayer(geojson, latlng, id)
case 'MultiLineString':
case 'LineString':
@ -510,7 +498,8 @@ export class DataLayer {
feature.setLatLngs(latlngs)
return feature
}
return this._lineToLayer(geojson, latlngs, id)
return new LineString(this, geojson)
// return this._lineToLayer(geojson, latlngs, id)
case 'MultiPolygon':
case 'Polygon':
@ -519,7 +508,8 @@ export class DataLayer {
feature.setLatLngs(latlngs)
return feature
}
return this._polygonToLayer(geojson, latlngs, id)
return new Polygon(this, geojson)
// return this._polygonToLayer(geojson, latlngs, id)
case 'GeometryCollection':
return this.geojsonToFeatures(geometry.geometries)
@ -966,7 +956,7 @@ export class DataLayer {
featuresToGeoJSON() {
const features = []
this.eachLayer((layer) => features.push(layer.toGeoJSON()))
this.eachFeature((feature) => features.push(feature.toGeoJSON()))
return features
}
@ -1031,19 +1021,21 @@ export class DataLayer {
getFeatureByIndex(index) {
if (index === -1) index = this._index.length - 1
const id = this._index[index]
return this._layers[id]
return this._features[id]
}
// TODO Add an index
// For now, iterate on all the features.
getFeatureById(id) {
return Object.values(this._layers).find((feature) => feature.id === id)
return Object.values(this._features).find((feature) => feature.id === id)
}
getNextFeature(feature) {
const id = this._index.indexOf(stamp(feature))
const nextId = this._index[id + 1]
return nextId ? this._layers[nextId] : this.getNextBrowsable().getFeatureByIndex(0)
return nextId
? this._features[nextId]
: this.getNextBrowsable().getFeatureByIndex(0)
}
getPreviousFeature(feature) {
@ -1053,7 +1045,7 @@ export class DataLayer {
const id = this._index.indexOf(stamp(feature))
const previousId = this._index[id - 1]
return previousId
? this._layers[previousId]
? this._features[previousId]
: this.getPreviousBrowsable().getFeatureByIndex(-1)
}

View file

@ -28,6 +28,8 @@ import URLs from './urls.js'
import * as Utils from './utils.js'
import { DataLayer, LAYER_TYPES } from './data/layer.js'
import { DataLayerPermissions, MapPermissions } from './permissions.js'
import { Point, LineString, Polygon } from './data/features.js'
import { LeafletMarker, LeafletPolyline, LeafletPolygon } from './rendering/ui.js'
// Import modules and export them to the global scope.
// For the not yet module-compatible JS out there.
@ -52,10 +54,16 @@ window.U = {
HTTPError,
Importer,
LAYER_TYPES,
LeafletMarker,
LeafletPolygon,
LeafletPolyline,
LineString,
MapPermissions,
NOKError,
Orderable,
Panel,
Point,
Polygon,
Request,
RequestError,
Rules,

View file

@ -1,5 +1,11 @@
// Uses global L.HeatLayer, not exposed as ESM
import { Marker, LatLng, latLngBounds, Bounds, point } from '../../../../vendors/leaflet/leaflet-src.esm.js'
import {
Marker,
LatLng,
latLngBounds,
Bounds,
point,
} from '../../../../vendors/leaflet/leaflet-src.esm.js'
import { LayerMixin } from './base.js'
import * as Utils from '../../utils.js'
import { translate } from '../../i18n.js'
@ -27,7 +33,7 @@ export const Heat = L.HeatLayer.extend({
let alt
if (this.datalayer.options.heat?.intensityProperty) {
alt = Number.parseFloat(
layer.properties[this.datalayer.options.heat.intensityProperty || 0]
layer.feature.properties[this.datalayer.options.heat.intensityProperty || 0]
)
latlng = new LatLng(latlng.lat, latlng.lng, alt)
}

View file

@ -56,8 +56,8 @@ const RelativeColorLayerMixin = {
getValues: function () {
const values = []
this.datalayer.eachLayer((layer) => {
const value = this._getValue(layer)
this.datalayer.eachFeature((feature) => {
const value = this._getValue(feature)
if (value !== undefined) values.push(value)
})
return values

View file

@ -0,0 +1,215 @@
import {
Marker,
Polyline,
Polygon,
DomUtil,
} from '../../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from '../i18n.js'
const FeatureMixin = {
initialize: function (feature) {
this.feature = feature
this.parentClass.prototype.initialize.call(this, this.feature.coordinates)
},
onAdd: function (map) {
this.addInteractions()
return this.parentClass.prototype.onAdd.call(this, map)
},
onRemove: function (map) {
this.parentClass.prototype.onRemove.call(this, map)
if (map.editedFeature === this.feature) {
this.feature._marked_for_deletion = true
this.feature.endEdit()
map.editPanel.close()
}
},
addInteractions: function () {
this.on('contextmenu editable:vertex:contextmenu', this.feature._showContextMenu, this.feature)
},
onVertexRawClick: function (e) {
new L.Toolbar.Popup(e.latlng, {
className: 'leaflet-inplace-toolbar',
actions: this.getVertexActions(e),
}).addTo(this.map, this, e.latlng, e.vertex)
},
}
export const LeafletMarker = Marker.extend({
parentClass: Marker,
includes: [FeatureMixin],
initialize: function (feature) {
FeatureMixin.initialize.call(this, feature)
this.setIcon(this.getIcon())
},
onCommit: function () {
this.feature.coordinates = this._latlng
this.feature.onCommit()
},
addInteractions() {
FeatureMixin.addInteractions.call(this)
this.on(
'dragend',
function (e) {
this.isDirty = true
this.feature.edit(e)
this.feature.sync.update('geometry', this.getGeometry())
},
this
)
this.on('editable:drawing:commit', this.onCommit)
if (!this.feature.isReadOnly()) this.on('mouseover', this._enableDragging)
this.on('mouseout', this._onMouseOut)
this._popupHandlersAdded = true // prevent Leaflet from binding event on bindPopup
this.on('popupopen', this.highlight)
this.on('popupclose', this.resetHighlight)
},
_onMouseOut: function () {
if (this.dragging?._draggable && !this.dragging._draggable._moving) {
// Do not disable if the mouse went out while dragging
this._disableDragging()
}
},
_enableDragging: function () {
// TODO: start dragging after 1 second on mouse down
if (this._map.editEnabled) {
if (!this.editEnabled()) this.enableEdit()
// Enabling dragging on the marker override the Draggable._OnDown
// event, which, as it stopPropagation, refrain the call of
// _onDown with map-pane element, which is responsible to
// set the _moved to false, and thus to enable the click.
// We should find a cleaner way to handle this.
this._map.dragging._draggable._moved = false
}
},
_disableDragging: function () {
if (this._map.editEnabled) {
if (this.editor?.drawing) return // when creating a new marker, the mouse can trigger the mouseover/mouseout event
// do not listen to them
this.disableEdit()
}
},
_initIcon: function () {
this.options.icon = this.getIcon()
Marker.prototype._initIcon.call(this)
// Allow to run code when icon is actually part of the DOM
this.options.icon.onAdd()
// this.resetTooltip()
},
getIconClass: function () {
return this.feature.getOption('iconClass')
},
getIcon: function () {
const Class = U.Icon[this.getIconClass()] || U.Icon.Default
return new Class({ feature: this.feature })
},
_getTooltipAnchor: function () {
const anchor = this.options.icon.options.tooltipAnchor.clone()
const direction = this.getOption('labelDirection')
if (direction === 'left') {
anchor.x *= -1
} else if (direction === 'bottom') {
anchor.x = 0
anchor.y = 0
} else if (direction === 'top') {
anchor.x = 0
}
return anchor
},
_redraw: function () {
this._initIcon()
this.update()
},
getCenter: function () {
return this._latlng
},
})
const PathMixin = {
_onMouseOver: function () {
if (this._map.measureTools?.enabled()) {
this._map.tooltip.open({ content: this.feature.getMeasure(), anchor: this })
} else if (this._map.editEnabled && !this._map.editedFeature) {
this._map.tooltip.open({ content: translate('Click to edit'), anchor: this })
}
},
onCommit: function () {
this.feature.coordinates = this._latlngs
this.feature.onCommit()
},
addInteractions: function () {
FeatureMixin.addInteractions.call(this)
this.on('editable:disable', this.onCommit)
this.on('mouseover', this._onMouseOver)
this.on('drag editable:drag', this._onDrag)
this.on('popupopen', this.highlightPath)
this.on('popupclose', this._redraw)
},
highlightPath: function () {
this.parentClass.prototype.setStyle.call(this, {
fillOpacity: Math.sqrt(this.feature.getDynamicOption('fillOpacity', 1.0)),
opacity: 1.0,
weight: 1.3 * this.feature.getDynamicOption('weight'),
})
},
_onDrag: function () {
this.feature.coordinates = this._latlngs
if (this._tooltip) this._tooltip.setLatLng(this.getCenter())
},
onAdd: function (map) {
this._container = null
this.setStyle()
FeatureMixin.onAdd.call(this, map)
if (this.editing?.enabled()) this.editing.addHooks()
// this.resetTooltip()
this._path.dataset.feature = this.feature.id
},
onRemove: function (map) {
if (this.editing?.enabled()) this.editing.removeHooks()
FeatureMixin.onRemove.call(this, map)
},
setStyle: function (options = {}) {
for (const option of this.feature.getStyleOptions()) {
options[option] = this.feature.getDynamicOption(option)
}
options.pointerEvents = options.interactive ? 'visiblePainted' : 'stroke'
this.parentClass.prototype.setStyle.call(this, options)
},
_redraw: function () {
this.setStyle()
// this.resetTooltip()
},
}
export const LeafletPolyline = Polyline.extend({
parentClass: Polyline,
includes: [FeatureMixin, PathMixin],
})
export const LeafletPolygon = Polygon.extend({
parentClass: Polygon,
includes: [FeatureMixin, PathMixin],
})

View file

@ -89,15 +89,15 @@ export default class TableEditor extends WithTemplate {
const bounds = this.map.getBounds()
const inBbox = this.map.browser.options.inBbox
let html = ''
for (const feature of Object.values(this.datalayer._layers)) {
if (feature.isFiltered()) continue
if (inBbox && !feature.isOnScreen(bounds)) continue
this.datalayer.eachFeature((feature) => {
if (feature.isFiltered()) return
if (inBbox && !feature.isOnScreen(bounds)) return
const tds = this.properties.map(
(prop) =>
`<td tabindex="0" data-property="${prop}">${feature.properties[prop] || ''}</td>`
)
html += `<tr data-feature="${feature.id}"><th><input type="checkbox" /></th>${tds.join('')}</tr>`
}
})
this.elements.body.innerHTML = html
}
@ -125,7 +125,7 @@ export default class TableEditor extends WithTemplate {
.prompt(translate('Please enter the new name of this property'))
.then(({ prompt }) => {
if (!prompt || !this.validateName(prompt)) return
this.datalayer.eachLayer((feature) => {
this.datalayer.eachFeature((feature) => {
feature.renameProperty(property, prompt)
})
this.datalayer.deindexProperty(property)
@ -140,7 +140,7 @@ export default class TableEditor extends WithTemplate {
translate('Are you sure you want to delete this property on all the features?')
)
.then(() => {
this.datalayer.eachLayer((feature) => {
this.datalayer.eachFeature((feature) => {
feature.deleteProperty(property)
})
this.datalayer.deindexProperty(property)

View file

@ -142,7 +142,7 @@ U.AddPolylineShapeAction = U.BaseAction.extend({
},
addHooks: function () {
this.map.editedFeature.editor.newShape()
this.map.editedFeature.ui.editor.newShape()
},
})
@ -155,6 +155,7 @@ U.AddPolygonShapeAction = U.AddPolylineShapeAction.extend({
U.BaseFeatureAction = L.ToolbarAction.extend({
initialize: function (map, feature, latlng) {
console.log("Toolbar init", latlng)
this.map = map
this.feature = feature
this.latlng = latlng
@ -310,7 +311,7 @@ U.DrawToolbar = L.Toolbar.Control.extend({
}
if (this.map.options.enablePolylineDraw) {
this.options.actions.push(U.DrawPolylineAction)
if (this.map.editedFeature && this.map.editedFeature instanceof U.Polyline) {
if (this.map.editedFeature && this.map.editedFeature instanceof U.LineString) {
this.options.actions.push(U.AddPolylineShapeAction)
}
}
@ -1145,56 +1146,81 @@ U.Editable = L.Editable.extend({
initialize: function (map, options) {
L.Editable.prototype.initialize.call(this, map, options)
this.on('editable:drawing:click editable:drawing:move', this.drawingTooltip)
this.on('editable:drawing:end', (e) => {
this.on('editable:drawing:end', (event) => {
this.map.tooltip.close()
// Leaflet.Editable will delete the drawn shape if invalid
// (eg. line has only one drawn point)
// So let's check if the layer has no more shape
if (!e.layer.hasGeom()) e.layer.del()
else e.layer.edit()
})
// Layer for items added by users
this.on('editable:drawing:cancel', (e) => {
if (e.layer instanceof U.Marker) e.layer.del()
})
this.on('editable:drawing:commit', function (e) {
e.layer.isDirty = true
if (this.map.editedFeature !== e.layer) e.layer.edit(e)
})
this.on('editable:editing', (e) => {
const layer = e.layer
layer.isDirty = true
if (layer._tooltip && layer.isTooltipOpen()) {
layer._tooltip.setLatLng(layer.getCenter())
layer._tooltip.update()
console.log(event.layer.feature.coordinates, event.layer.feature.hasGeom())
if (!event.layer.feature.hasGeom()) {
event.layer.feature.del()
} else {
event.layer.feature.edit()
}
})
this.on('editable:vertex:ctrlclick', (e) => {
const index = e.vertex.getIndex()
if (index === 0 || (index === e.vertex.getLastIndex() && e.vertex.continue))
e.vertex.continue()
// Layer for items added by users
this.on('editable:drawing:cancel', (event) => {
if (event.layer instanceof U.LeafletMarker) event.layer.feature.del()
})
this.on('editable:vertex:altclick', (e) => {
if (e.vertex.editor.vertexCanBeDeleted(e.vertex)) e.vertex.delete()
this.on('editable:drawing:commit', function (event) {
event.layer.feature.isDirty = true
if (this.map.editedFeature !== event.layer) event.layer.feature.edit(event)
})
this.on('editable:editing', (event) => {
const layer = event.layer
layer.feature.isDirty = true
console.log('editing')
if (layer instanceof L.Marker) {
layer.feature.coordinates = layer._latlng
} else {
layer.feature.coordinates = layer._latlngs
}
// if (layer._tooltip && layer.isTooltipOpen()) {
// layer._tooltip.setLatLng(layer.getCenter())
// layer._tooltip.update()
// }
})
this.on('editable:vertex:ctrlclick', (event) => {
const index = event.vertex.getIndex()
if (
index === 0 ||
(index === event.vertex.getLastIndex() && event.vertex.continue)
)
event.vertex.continue()
})
this.on('editable:vertex:altclick', (event) => {
if (event.vertex.editor.vertexCanBeDeleted(event.vertex)) event.vertex.delete()
})
this.on('editable:vertex:rawclick', this.onVertexRawClick)
},
createPolyline: function (latlngs) {
return new U.Polyline(this.map, latlngs, this._getDefaultProperties())
const datalayer = this.map.defaultEditDataLayer()
const point = new U.LineString(datalayer, {
geometry: { type: 'LineString', coordinates: [] },
})
return point.ui
},
createPolygon: function (latlngs) {
return new U.Polygon(this.map, latlngs, this._getDefaultProperties())
const datalayer = this.map.defaultEditDataLayer()
const point = new U.Polygon(datalayer, {
geometry: { type: 'Polygon', coordinates: [] },
})
return point.ui
},
createMarker: function (latlng) {
return new U.Marker(this.map, latlng, this._getDefaultProperties())
const datalayer = this.map.defaultEditDataLayer()
const point = new U.Point(datalayer, {
geometry: { type: 'Point', coordinates: [latlng.lng, latlng.lat] },
})
return point.ui
},
_getDefaultProperties: function () {
const result = {}
if (this.map.options.featuresHaveOwner && this.map.options.hasOwnProperty('user')) {
if (this.map.options.featuresHaveOwner?.user) {
result.geojson = { properties: { owner: this.map.options.user.id } }
}
return result
@ -1203,7 +1229,7 @@ U.Editable = L.Editable.extend({
connectCreatedToMap: function (layer) {
// Overrided from Leaflet.Editable
const datalayer = this.map.defaultEditDataLayer()
datalayer.addLayer(layer)
datalayer.addFeature(layer.feature)
layer.isDirty = true
return layer
},
@ -1231,7 +1257,7 @@ U.Editable = L.Editable.extend({
} else {
const tmpLatLngs = e.layer.editor._drawnLatLngs.slice()
tmpLatLngs.push(e.latlng)
measure = e.layer.getMeasure(tmpLatLngs)
measure = e.layer.feature.getMeasure(tmpLatLngs)
if (e.layer.editor._drawnLatLngs.length < e.layer.editor.MIN_VERTEX) {
// when drawing second point
@ -1243,7 +1269,7 @@ U.Editable = L.Editable.extend({
}
} else {
// when moving an existing point
measure = e.layer.getMeasure()
measure = e.layer.feature.getMeasure()
}
if (measure) {
if (e.layer instanceof L.Polygon) {

View file

@ -360,7 +360,6 @@ L.FormBuilder.DataLayerSwitcher = L.FormBuilder.Select.extend({
getOptions: function () {
const options = []
this.builder.map.eachDataLayerReverse((datalayer) => {
console.log(datalayer.isLoaded(), datalayer.isDataReadOnly(), datalayer.isBrowsable())
if (
datalayer.isLoaded() &&
!datalayer.isDataReadOnly() &&

View file

@ -2,11 +2,10 @@ U.Icon = L.DivIcon.extend({
statics: {
RECENT: [],
},
initialize: function (map, options) {
this.map = map
initialize: function (options) {
const default_options = {
iconSize: null, // Made in css
iconUrl: this.map.getDefaultOption('iconUrl'),
iconUrl: U.SCHEMA.iconUrl.default,
feature: null,
}
options = L.Util.extend({}, default_options, options)
@ -40,13 +39,13 @@ U.Icon = L.DivIcon.extend({
let color
if (this.feature) color = this.feature.getDynamicOption('color')
else if (this.options.color) color = this.options.color
else color = this.map.getDefaultOption('color')
else color = U.SCHEMA.color.default
return color
},
_getOpacity: function () {
if (this.feature) return this.feature.getOption('iconOpacity')
return this.map.getDefaultOption('iconOpacity')
return U.SCHEMA.iconOpacity.default
},
formatUrl: (url, feature) =>
@ -63,9 +62,9 @@ U.Icon.Default = U.Icon.extend({
className: 'umap-div-icon',
},
initialize: function (map, options) {
initialize: function (options) {
options = L.Util.extend({}, this.default_options, options)
U.Icon.prototype.initialize.call(this, map, options)
U.Icon.prototype.initialize.call(this, options)
},
_setIconStyles: function (img, name) {
@ -92,6 +91,7 @@ U.Icon.Default = U.Icon.extend({
'icon_container',
this.elements.main
)
this.elements.main.dataset.feature = this.feature?.id
this.elements.arrow = L.DomUtil.create('div', 'icon_arrow', this.elements.main)
const src = this._getIconUrl('icon')
if (src) {
@ -103,14 +103,14 @@ U.Icon.Default = U.Icon.extend({
})
U.Icon.Circle = U.Icon.extend({
initialize: function (map, options) {
initialize: function (options) {
const default_options = {
popupAnchor: new L.Point(0, -6),
tooltipAnchor: new L.Point(6, 0),
className: 'umap-circle-icon',
}
options = L.Util.extend({}, default_options, options)
U.Icon.prototype.initialize.call(this, map, options)
U.Icon.prototype.initialize.call(this, options)
},
_setIconStyles: function (img, name) {
@ -124,6 +124,7 @@ U.Icon.Circle = U.Icon.extend({
this.elements.main = L.DomUtil.create('div')
this.elements.main.innerHTML = '&nbsp;'
this._setIconStyles(this.elements.main, 'icon')
this.elements.main.dataset.feature = this.feature?.id
return this.elements.main
},
})
@ -153,6 +154,7 @@ U.Icon.Ball = U.Icon.Default.extend({
'icon_container',
this.elements.main
)
this.elements.main.dataset.feature = this.feature?.id
this.elements.arrow = L.DomUtil.create('div', 'icon_arrow', this.elements.main)
this._setIconStyles(this.elements.main, 'icon')
return this.elements.main

View file

@ -193,7 +193,7 @@ U.Map = L.Map.extend({
window.onbeforeunload = () => (this.editEnabled && this.isDirty) || null
this.backup()
this.initContextMenu()
this.on('click contextmenu.show', this.closeInplaceToolbar)
this.on('click', this.onClick)
},
initSyncEngine: async function () {
@ -863,7 +863,7 @@ U.Map = L.Map.extend({
},
eachFeature: function (callback, context) {
this.eachDataLayer((datalayer) => {
this.eachBrowsableDataLayer((datalayer) => {
if (datalayer.isVisible()) datalayer.eachFeature(callback, context)
})
},
@ -1555,6 +1555,7 @@ U.Map = L.Map.extend({
this.editPanel.close()
this.fullPanel.close()
this.sync.stop()
this.closeInplaceToolbar()
},
hasEditMode: function () {
@ -1832,6 +1833,45 @@ U.Map = L.Map.extend({
return url
},
getFeatureById: function (id) {
let feature
for (const datalayer of Object.values(this.datalayers)) {
feature = datalayer.getFeatureById(id)
if (feature) return feature
}
},
onClick: function (event) {
const container = event.originalEvent.target.closest('[data-feature]')
if (container) {
const feature = this.getFeatureById(container.dataset.feature)
if (this.measureTools?.enabled()) return
this._popupHandlersAdded = true // Prevent leaflet from managing event
if (!this.editEnabled) {
feature.view(event)
} else if (!feature.isReadOnly()) {
if (event.originalEvent.shiftKey) {
if (event.originalEvent.ctrlKey || event.originalEvent.metaKey) {
feature.datalayer.edit(event)
} else {
if (feature._toggleEditing) feature._toggleEditing(event)
else feature.edit(event)
}
} else {
console.log('should show toolbar')
new L.Toolbar.Popup(event.latlng, {
className: 'leaflet-inplace-toolbar',
anchor: feature.getPopupToolbarAnchor(),
actions: feature.getInplaceToolbarActions(event),
}).addTo(this, feature, event.latlng)
}
}
L.DomEvent.stop(event)
} else {
this.closeInplaceToolbar()
}
},
closeInplaceToolbar: function () {
const toolbar = this._toolbars[L.Toolbar.Popup._toolbar_class_id]
if (toolbar) toolbar.remove()

View file

@ -46,7 +46,6 @@
<script src="{% static 'umap/js/umap.popup.js' %}" defer></script>
<script src="{% static 'umap/js/umap.forms.js' %}" defer></script>
<script src="{% static 'umap/js/umap.icon.js' %}" defer></script>
<script src="{% static 'umap/js/umap.features.js' %}" defer></script>
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
<script src="{% static 'umap/js/umap.js' %}" defer></script>
<script src="{% static 'umap/js/components/fragment.js' %}" defer></script>