chore: use our own contextmenu (#2109)

Let's start from here to clean the contextmenu items.

Current status:

### Some entries are generic, and always present:

In preview mode:


![image](https://github.com/user-attachments/assets/ea5cf2d3-f8f7-4d6a-8bf5-4c0334334bbc)

In edit mode:


![image](https://github.com/user-attachments/assets/2d2fa5c1-a275-4037-8780-d18a5e31949b)

### Some entries are specific to a clicked feature

In preview mode (Marker):


![image](https://github.com/user-attachments/assets/2d03f640-fb62-4be8-893d-d1ffc09d66f1)

In edit mode (Marker):


![image](https://github.com/user-attachments/assets/3f5ede89-fe3e-41a0-ac9a-575ef04e7a41)

In preview mode (Polygon):


![image](https://github.com/user-attachments/assets/fa88b13a-ac53-4bdd-896c-ada025640c35)

In edit mode (Polygon):


![image](https://github.com/user-attachments/assets/c2b46f2a-4f6d-4660-9654-d4f775386898)

In preview mode (Line):


![image](https://github.com/user-attachments/assets/74cd5c21-ad17-4592-b3da-f82f12c60baf)

In edit mode (Line):


![image](https://github.com/user-attachments/assets/4c9f89c6-a30e-4f40-84fe-3fff306358aa)


@Aurelie-Jallut Do you want to make suggestion on what to show or not in
this menu, depending on the context (feature or not, preview or edit
mode) ?
This commit is contained in:
Yohan Boniface 2024-10-04 16:34:06 +02:00 committed by GitHub
commit d95f06abab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 262 additions and 295 deletions

View file

@ -46,7 +46,6 @@
"georsstogeojson": "^0.2.0", "georsstogeojson": "^0.2.0",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",
"leaflet": "1.9.4", "leaflet": "1.9.4",
"leaflet-contextmenu": "^1.4.0",
"leaflet-editable": "^1.3.0", "leaflet-editable": "^1.3.0",
"leaflet-editinosm": "0.2.3", "leaflet-editinosm": "0.2.3",
"leaflet-formbuilder": "0.2.10", "leaflet-formbuilder": "0.2.10",

View file

@ -14,7 +14,6 @@ mkdir -p umap/static/umap/vendors/minimap/ && cp -r node_modules/leaflet-minimap
mkdir -p umap/static/umap/vendors/loading/ && cp -r node_modules/leaflet-loading/src/Control.Loading.* umap/static/umap/vendors/loading/ mkdir -p umap/static/umap/vendors/loading/ && cp -r node_modules/leaflet-loading/src/Control.Loading.* umap/static/umap/vendors/loading/
mkdir -p umap/static/umap/vendors/markercluster/ && cp -r node_modules/leaflet.markercluster/dist/leaflet.markercluster.* umap/static/umap/vendors/markercluster/ mkdir -p umap/static/umap/vendors/markercluster/ && cp -r node_modules/leaflet.markercluster/dist/leaflet.markercluster.* umap/static/umap/vendors/markercluster/
mkdir -p umap/static/umap/vendors/markercluster/ && cp -r node_modules/leaflet.markercluster/dist/MarkerCluster.* umap/static/umap/vendors/markercluster/ mkdir -p umap/static/umap/vendors/markercluster/ && cp -r node_modules/leaflet.markercluster/dist/MarkerCluster.* umap/static/umap/vendors/markercluster/
mkdir -p umap/static/umap/vendors/contextmenu/ && cp -r node_modules/leaflet-contextmenu/dist/leaflet.contextmenu.min.* umap/static/umap/vendors/contextmenu/
mkdir -p umap/static/umap/vendors/heat/ && cp -r node_modules/leaflet.heat/dist/leaflet-heat.js umap/static/umap/vendors/heat/ mkdir -p umap/static/umap/vendors/heat/ && cp -r node_modules/leaflet.heat/dist/leaflet-heat.js umap/static/umap/vendors/heat/
mkdir -p umap/static/umap/vendors/fullscreen/ && cp -r node_modules/leaflet-fullscreen/dist/** umap/static/umap/vendors/fullscreen/ mkdir -p umap/static/umap/vendors/fullscreen/ && cp -r node_modules/leaflet-fullscreen/dist/** umap/static/umap/vendors/fullscreen/
mkdir -p umap/static/umap/vendors/toolbar/ && cp -r node_modules/leaflet-toolbar/dist/leaflet.toolbar.* umap/static/umap/vendors/toolbar/ mkdir -p umap/static/umap/vendors/toolbar/ && cp -r node_modules/leaflet-toolbar/dist/leaflet.toolbar.* umap/static/umap/vendors/toolbar/

View file

@ -9,3 +9,8 @@
.umap-contextmenu li + li { .umap-contextmenu li + li {
margin-top: var(--text-margin); margin-top: var(--text-margin);
} }
.umap-contextmenu hr {
margin-top: var(--text-margin);
margin-bottom: var(--text-margin);
}

View file

@ -596,6 +596,55 @@ class Feature {
} }
} }
} }
getContextMenuItems(event) {
const permalink = this.getPermalink()
let items = []
if (permalink) {
items.push({
label: translate('Permalink'),
action: () => {
window.open(permalink)
},
})
}
items.push({
label: translate('Copy as GeoJSON'),
action: () => {
L.Util.copyToClipboard(JSON.stringify(this.toGeoJSON()))
this.map.tooltip.open({ content: L._('✅ Copied!') })
},
})
if (this.map.editEnabled && !this.isReadOnly()) {
items = items.concat(this.getContextMenuEditItems(event))
}
return items
}
getContextMenuEditItems() {
let items = ['-']
if (this.map.editedFeature !== this) {
items.push({
label: `${translate('Edit this feature')} (⇧+Click)`,
action: () => this.edit(),
})
}
items = items.concat(
{
label: this.map.help.displayLabel('EDIT_FEATURE_LAYER'),
action: () => this.datalayer.edit(),
},
{
label: translate('Delete this feature'),
action: () => this.confirmDelete(),
},
{
label: translate('Clone this feature'),
action: () => this.clone(),
}
)
return items
}
} }
export class Point extends Feature { export class Point extends Feature {
@ -768,6 +817,62 @@ class Path extends Feature {
} }
if (callback) callback.call(this) if (callback) callback.call(this)
} }
getContextMenuItems(event) {
const items = super.getContextMenuItems(event)
items.push({
label: translate('Display measure'),
action: () => Alert.info(this.ui.getMeasure()),
})
if (this.map.editEnabled && !this.isReadOnly() && this.isMulti()) {
items.push(...this.getContextMenuMultiItems(event))
}
return items
}
getContextMenuMultiItems(event) {
const items = [
'-',
{
label: translate('Remove shape from the multi'),
action: () => {
this.ui.enableEdit().deleteShapeAt(event.latlng)
},
},
]
const shape = this.ui.shapeAt(event.latlng)
if (this.ui._latlngs.indexOf(shape) > 0) {
items.push({
label: translate('Make main shape'),
action: () => {
this.ui.enableEdit().deleteShape(shape)
this.ui.editor.prependShape(shape)
},
})
}
return items
}
getContextMenuEditItems(event) {
const items = super.getContextMenuEditItems(event)
if (this.map?.editedFeature !== this && this.isSameClass(this.map.editedFeature)) {
items.push({
label: translate('Transfer shape to edited feature'),
action: () => {
this.transferShape(event.latlng, this.map.editedFeature)
},
})
}
if (this.isMulti()) {
items.push({
label: translate('Extract shape to separate feature'),
action: () => {
this.ui.isolateShape(event.latlng)
},
})
}
return items
}
} }
export class LineString extends Path { export class LineString extends Path {
@ -882,6 +987,41 @@ export class LineString extends Path {
isMulti() { isMulti() {
return !LineUtil.isFlat(this.coordinates) && this.coordinates.length > 1 return !LineUtil.isFlat(this.coordinates) && this.coordinates.length > 1
} }
getContextMenuEditItems(event) {
const items = super.getContextMenuEditItems(event)
const vertexClicked = event.vertex
if (!this.isMulti()) {
items.push({
label: translate('Transform to polygon'),
action: () => this.toPolygon(),
})
}
if (vertexClicked) {
const index = event.vertex.getIndex()
if (index !== 0 && index !== event.vertex.getLastIndex()) {
items.push({
label: translate('Split line'),
action: () => event.vertex.split(),
})
} else if (index === 0 || index === event.vertex.getLastIndex()) {
items.push({
label: this.map.help.displayLabel('CONTINUE_LINE'),
action: () => event.vertex.continue(),
})
}
}
return items
}
getContextMenuMultiItems(event) {
const items = super.getContextMenuMultiItems(event)
items.push({
label: translate('Merge lines'),
action: () => this.mergeShapes(),
})
return items
}
} }
export class Polygon extends Path { export class Polygon extends Path {
@ -992,4 +1132,21 @@ export class Polygon extends Path {
items.push(U.CreateHoleAction) items.push(U.CreateHoleAction)
return items return items
} }
getContextMenuEditItems(event) {
const items = super.getContextMenuEditItems(event)
const shape = this.ui.shapeAt(event.latlng)
// No multi and no holes.
if (shape && !this.isMulti() && (LineUtil.isFlat(shape) || shape.length === 1)) {
items.push({
label: translate('Transform to lines'),
action: () => this.toLineString(),
})
}
items.push({
label: translate('Start a hole here'),
action: () => this.ui.startHole(event),
})
return items
}
} }

View file

@ -9,6 +9,7 @@ import {
} from './autocomplete.js' } from './autocomplete.js'
import Browser from './browser.js' import Browser from './browser.js'
import Caption from './caption.js' import Caption from './caption.js'
import ContextMenu from './ui/contextmenu.js'
import Facets from './facets.js' import Facets from './facets.js'
import { Formatter } from './formatter.js' import { Formatter } from './formatter.js'
import Help from './help.js' import Help from './help.js'
@ -43,6 +44,7 @@ window.U = {
AutocompleteDatalist, AutocompleteDatalist,
Browser, Browser,
Caption, Caption,
ContextMenu,
DataLayer, DataLayer,
DataLayerPermissions, DataLayerPermissions,
Dialog, Dialog,

View file

@ -9,6 +9,7 @@ import {
latLng, latLng,
LatLng, LatLng,
LatLngBounds, LatLngBounds,
DomEvent,
} from '../../../vendors/leaflet/leaflet-src.esm.js' } from '../../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from '../i18n.js' import { translate } from '../i18n.js'
import { uMapAlert as Alert } from '../../components/alerts/alert.js' import { uMapAlert as Alert } from '../../components/alerts/alert.js'
@ -36,7 +37,7 @@ const FeatureMixin = {
}, },
addInteractions: function () { addInteractions: function () {
this.on('contextmenu editable:vertex:contextmenu', this._showContextMenu) this.on('contextmenu editable:vertex:contextmenu', this.onContextMenu)
this.on('click', this.onClick) this.on('click', this.onClick)
}, },
@ -61,7 +62,7 @@ const FeatureMixin = {
}).addTo(this._map, this.feature, event.latlng) }).addTo(this._map, this.feature, event.latlng)
} }
} }
L.DomEvent.stop(event) DomEvent.stop(event)
}, },
resetTooltip: function () { resetTooltip: function () {
@ -83,67 +84,14 @@ const FeatureMixin = {
} }
}, },
_showContextMenu: function (event) { onContextMenu: function (event) {
L.DomEvent.stop(event) DomEvent.stop(event)
const pt = this._map.mouseEventToContainerPoint(event.originalEvent) const items = this._map.getContextMenuItems(event)
event.relatedTarget = this items.push('-', ...this.feature.getContextMenuItems(event))
this._map.contextmenu.showAt(pt, event) this._map.contextmenu.open(
}, [event.originalEvent.clientX, event.originalEvent.clientY],
items
getContextMenuItems: function (event) {
const permalink = this.feature.getPermalink()
let items = []
if (permalink) {
items.push({
text: translate('Permalink'),
callback: () => {
window.open(permalink)
},
})
}
items.push({
text: translate('Copy as GeoJSON'),
callback: () => {
L.Util.copyToClipboard(JSON.stringify(this.feature.toGeoJSON()))
this._map.tooltip.open({ content: L._('✅ Copied!') })
},
})
if (this._map.editEnabled && !this.feature.isReadOnly()) {
items = items.concat(this.getContextMenuEditItems(event))
}
return items
},
getContextMenuEditItems: function () {
let items = ['-']
if (this._map.editedFeature !== this) {
items.push({
text: `${translate('Edit this feature')} (⇧+Click)`,
callback: this.feature.edit,
context: this.feature,
iconCls: 'umap-edit',
})
}
items = items.concat(
{
text: this._map.help.displayLabel('EDIT_FEATURE_LAYER'),
callback: this.feature.datalayer.edit,
context: this.feature.datalayer,
iconCls: 'umap-edit',
},
{
text: translate('Delete this feature'),
callback: this.feature.confirmDelete,
context: this.feature,
iconCls: 'umap-delete',
},
{
text: translate('Clone this feature'),
callback: this.feature.clone,
context: this.feature,
}
) )
return items
}, },
onCommit: function () { onCommit: function () {
@ -360,65 +308,6 @@ const PathMixin = {
}).addTo(this._map, this, event.latlng, event.vertex) }).addTo(this._map, this, event.latlng, event.vertex)
}, },
getContextMenuItems: function (event) {
let items = FeatureMixin.getContextMenuItems.call(this, event)
items.push({
text: translate('Display measure'),
callback: () => Alert.info(this.getMeasure()),
})
if (this._map.editEnabled && !this.feature.isReadOnly() && this.feature.isMulti()) {
items = items.concat(this.getContextMenuMultiItems(event))
}
return items
},
getContextMenuMultiItems: function (event) {
const items = [
'-',
{
text: translate('Remove shape from the multi'),
callback: () => {
this.enableEdit().deleteShapeAt(event.latlng)
},
},
]
const shape = this.shapeAt(event.latlng)
if (this._latlngs.indexOf(shape) > 0) {
items.push({
text: translate('Make main shape'),
callback: () => {
this.enableEdit().deleteShape(shape)
this.editor.prependShape(shape)
},
})
}
return items
},
getContextMenuEditItems: function (event) {
const items = FeatureMixin.getContextMenuEditItems.call(this, event)
if (
this._map?.editedFeature !== this.feature &&
this.feature.isSameClass(this._map.editedFeature)
) {
items.push({
text: translate('Transfer shape to edited feature'),
callback: () => {
this.feature.transferShape(event.latlng, this._map.editedFeature)
},
})
}
if (this.feature.isMulti()) {
items.push({
text: translate('Extract shape to separate feature'),
callback: () => {
this.isolateShape(event.latlng)
},
})
}
return items
},
isolateShape: function (atLatLng) { isolateShape: function (atLatLng) {
if (!this.feature.isMulti()) return if (!this.feature.isMulti()) return
const shape = this.enableEdit().deleteShapeAt(atLatLng) const shape = this.enableEdit().deleteShapeAt(atLatLng)
@ -463,46 +352,6 @@ export const LeafletPolyline = Polyline.extend({
return actions return actions
}, },
getContextMenuEditItems: function (event) {
const items = PathMixin.getContextMenuEditItems.call(this, event)
const vertexClicked = event.vertex
let index
if (!this.feature.isMulti()) {
items.push({
text: translate('Transform to polygon'),
callback: this.feature.toPolygon,
context: this.feature,
})
}
if (vertexClicked) {
index = event.vertex.getIndex()
if (index !== 0 && index !== event.vertex.getLastIndex()) {
items.push({
text: translate('Split line'),
callback: event.vertex.split,
context: event.vertex,
})
} else if (index === 0 || index === event.vertex.getLastIndex()) {
items.push({
text: this._map.help.displayLabel('CONTINUE_LINE'),
callback: event.vertex.continue,
context: event.vertex.continue,
})
}
}
return items
},
getContextMenuMultiItems: function (event) {
const items = PathMixin.getContextMenuMultiItems.call(this, event)
items.push({
text: translate('Merge lines'),
callback: this.feature.mergeShapes,
context: this.feature,
})
return items
},
getMeasure: function (shape) { getMeasure: function (shape) {
// FIXME: compute from data in feature (with TurfJS) // FIXME: compute from data in feature (with TurfJS)
const length = L.GeoUtil.lineLength(this._map, shape || this._defaultShape()) const length = L.GeoUtil.lineLength(this._map, shape || this._defaultShape())
@ -516,29 +365,6 @@ export const LeafletPolygon = Polygon.extend({
getClass: () => LeafletPolygon, getClass: () => LeafletPolygon,
getContextMenuEditItems: function (event) {
const items = PathMixin.getContextMenuEditItems.call(this, event)
const shape = this.shapeAt(event.latlng)
// No multi and no holes.
if (
shape &&
!this.feature.isMulti() &&
(LineUtil.isFlat(shape) || shape.length === 1)
) {
items.push({
text: translate('Transform to lines'),
callback: this.feature.toLineString,
context: this.feature,
})
}
items.push({
text: translate('Start a hole here'),
callback: this.startHole,
context: this,
})
return items
},
startHole: function (event) { startHole: function (event) {
this.enableEdit().newHole(event.latlng) this.enableEdit().newHole(event.latlng)
}, },

View file

@ -72,9 +72,9 @@ export class Positioned {
left = x - this.container.offsetWidth left = x - this.container.offsetWidth
} }
if (y < window.innerHeight / 2) { if (y < window.innerHeight / 2) {
top = y top = Math.min(y, window.innerHeight - this.container.offsetHeight)
} else { } else {
top = y - this.container.offsetHeight top = Math.max(0, y - this.container.offsetHeight)
} }
this.setPosition({ left, top }) this.setPosition({ left, top })
} }

View file

@ -18,14 +18,18 @@ export default class ContextMenu extends Positioned {
open([x, y], items) { open([x, y], items) {
this.container.innerHTML = '' this.container.innerHTML = ''
for (const item of items) { for (const item of items) {
const li = loadTemplate( if (item === '-') {
`<li class="${item.className || ''}"><button tabindex="0" class="flat">${item.label}</button></li>` this.container.appendChild(document.createElement('hr'))
) } else {
li.addEventListener('click', () => { const li = loadTemplate(
this.close() `<li class="${item.className || ''}"><button tabindex="0" class="flat">${item.label}</button></li>`
item.action() )
}) li.addEventListener('click', () => {
this.container.appendChild(li) this.close()
item.action()
})
this.container.appendChild(li)
}
} }
document.body.appendChild(this.container) document.body.appendChild(this.container)
this.computePosition([x, y]) this.computePosition([x, y])

View file

@ -1140,23 +1140,6 @@ L.Control.Loading.include({
}, },
}) })
/*
* Make it dynamic
*/
U.ContextMenu = L.Map.ContextMenu.extend({
_createItems: function (e) {
this._map.setContextMenuItems(e)
L.Map.ContextMenu.prototype._createItems.call(this)
},
_showAtPoint: function (pt, e) {
this._items = []
this._container.innerHTML = ''
this._createItems(e)
L.Map.ContextMenu.prototype._showAtPoint.call(this, pt, e)
},
})
U.Editable = L.Editable.extend({ U.Editable = L.Editable.extend({
initialize: function (map, options) { initialize: function (map, options) {
L.Editable.prototype.initialize.call(this, map, options) L.Editable.prototype.initialize.call(this, map, options)

View file

@ -58,6 +58,7 @@ U.Map = L.Map.extend({
this.panel = new U.Panel(this) this.panel = new U.Panel(this)
this.dialog = new U.Dialog({ className: 'dark' }) this.dialog = new U.Dialog({ className: 'dark' })
this.tooltip = new U.Tooltip(this._controlContainer) this.tooltip = new U.Tooltip(this._controlContainer)
this.contextmenu = new U.ContextMenu()
if (this.hasEditMode()) { if (this.hasEditMode()) {
this.editPanel = new U.EditPanel(this) this.editPanel = new U.EditPanel(this)
this.fullPanel = new U.FullPanel(this) this.fullPanel = new U.FullPanel(this)
@ -197,8 +198,8 @@ U.Map = L.Map.extend({
window.onbeforeunload = () => (this.editEnabled && this.isDirty) || null window.onbeforeunload = () => (this.editEnabled && this.isDirty) || null
this.backup() this.backup()
this.initContextMenu()
this.on('click', this.closeInplaceToolbar) this.on('click', this.closeInplaceToolbar)
this.on('contextmenu', this.onContextMenu)
}, },
initSyncEngine: async function () { initSyncEngine: async function () {
@ -1683,118 +1684,107 @@ U.Map = L.Map.extend({
this.loader.onAdd(this) this.loader.onAdd(this)
}, },
initContextMenu: function () { getContextMenuItems: function (event) {
this.contextmenu = new U.ContextMenu(this) const items = []
this.contextmenu.enable()
},
setContextMenuItems: function (e) {
let items = []
if (this._zoom !== this.getMaxZoom()) { if (this._zoom !== this.getMaxZoom()) {
items.push({ items.push({
text: L._('Zoom in'), label: L._('Zoom in'),
callback: function () { action: () => this.zoomIn(),
this.zoomIn()
},
}) })
} }
if (this._zoom !== this.getMinZoom()) { if (this._zoom !== this.getMinZoom()) {
items.push({ items.push({
text: L._('Zoom out'), label: L._('Zoom out'),
callback: function () { action: () => this.zoomOut(),
this.zoomOut()
},
}) })
} }
if (e?.relatedTarget) {
if (e.relatedTarget.getContextMenuItems) {
items = items.concat(e.relatedTarget.getContextMenuItems(e))
}
}
if (this.hasEditMode()) { if (this.hasEditMode()) {
items.push('-') items.push('-')
if (this.editEnabled) { if (this.editEnabled) {
if (!this.isDirty) { if (!this.isDirty) {
items.push({ items.push({
text: this.help.displayLabel('STOP_EDIT'), label: this.help.displayLabel('STOP_EDIT'),
callback: this.disableEdit, action: () => this.disableEdit(),
}) })
} }
if (this.options.enableMarkerDraw) { if (this.options.enableMarkerDraw) {
items.push({ items.push({
text: this.help.displayLabel('DRAW_MARKER'), label: this.help.displayLabel('DRAW_MARKER'),
callback: this.startMarker, action: () => this.startMarker(event),
context: this,
}) })
} }
if (this.options.enablePolylineDraw) { if (this.options.enablePolylineDraw) {
items.push({ items.push({
text: this.help.displayLabel('DRAW_POLYGON'), label: this.help.displayLabel('DRAW_POLYGON'),
callback: this.startPolygon, action: () => this.startPolygon(event),
context: this,
}) })
} }
if (this.options.enablePolygonDraw) { if (this.options.enablePolygonDraw) {
items.push({ items.push({
text: this.help.displayLabel('DRAW_LINE'), label: this.help.displayLabel('DRAW_LINE'),
callback: this.startPolyline, action: () => this.startPolyline(event),
context: this,
}) })
} }
items.push('-') items.push('-')
items.push({ items.push({
text: L._('Help'), label: L._('Help'),
callback: function () { action: () => this.help.show('edit'),
this.help.show('edit')
},
}) })
} else { } else {
items.push({ items.push({
text: this.help.displayLabel('TOGGLE_EDIT'), label: this.help.displayLabel('TOGGLE_EDIT'),
callback: this.enableEdit, action: () => this.enableEdit(),
}) })
} }
} }
items.push( items.push(
'-', '-',
{ {
text: L._('Open browser'), label: L._('Open browser'),
callback: () => this.openBrowser('layers'), action: () => this.openBrowser('layers'),
}, },
{ {
text: L._('Browse data'), label: L._('Browse data'),
callback: () => this.openBrowser('data'), action: () => this.openBrowser('data'),
} }
) )
if (this.options.facetKey) { if (this.options.facetKey) {
items.push({ items.push({
text: L._('Filter data'), label: L._('Filter data'),
callback: () => this.openBrowser('filters'), action: () => this.openBrowser('filters'),
}) })
} }
items.push( items.push(
{ {
text: L._('Open caption'), label: L._('Open caption'),
callback: this.openCaption, action: () => this.openCaption(),
}, },
{ {
text: this.help.displayLabel('SEARCH'), label: this.help.displayLabel('SEARCH'),
callback: this.search, action: () => this.search(event),
} }
) )
if (this.options.urls.routing) { if (this.options.urls.routing) {
items.push('-', { items.push('-', {
text: L._('Directions from here'), label: L._('Directions from here'),
callback: this.openExternalRouting, action: () => this.openExternalRouting(event),
}) })
} }
if (this.options.urls.edit_in_osm) { if (this.options.urls.edit_in_osm) {
items.push('-', { items.push('-', {
text: L._('Edit in OpenStreetMap'), label: L._('Edit in OpenStreetMap'),
callback: this.editInOSM, action: () => this.editInOSM(event),
}) })
} }
this.options.contextmenuItems = items return items
},
onContextMenu: function (event) {
const items = this.getContextMenuItems(event)
this.contextmenu.open(
[event.originalEvent.clientX, event.originalEvent.clientY],
items
)
}, },
editInOSM: function (e) { editInOSM: function (e) {

View file

@ -1 +0,0 @@
.leaflet-contextmenu{display:none;box-shadow:0 1px 7px rgba(0,0,0,0.4);-webkit-border-radius:4px;border-radius:4px;padding:4px 0;background-color:#fff;cursor:default;-webkit-user-select:none;-moz-user-select:none;user-select:none}.leaflet-contextmenu a.leaflet-contextmenu-item{display:block;color:#222;font-size:12px;line-height:20px;text-decoration:none;padding:0 12px;border-top:1px solid transparent;border-bottom:1px solid transparent;cursor:default;outline:0}.leaflet-contextmenu a.leaflet-contextmenu-item-disabled{opacity:.5}.leaflet-contextmenu a.leaflet-contextmenu-item.over{background-color:#f4f4f4;border-top:1px solid #f0f0f0;border-bottom:1px solid #f0f0f0}.leaflet-contextmenu a.leaflet-contextmenu-item-disabled.over{background-color:inherit;border-top:1px solid transparent;border-bottom:1px solid transparent}.leaflet-contextmenu-icon{margin:2px 8px 0 0;width:16px;height:16px;float:left;border:0}.leaflet-contextmenu-separator{border-bottom:1px solid #ccc;margin:5px 0}

File diff suppressed because one or more lines are too long

View file

@ -25,8 +25,6 @@
<script src="{% static 'umap/vendors/csv2geojson/csv2geojson.js' %}" defer></script> <script src="{% static 'umap/vendors/csv2geojson/csv2geojson.js' %}" defer></script>
<script src="{% static 'umap/vendors/osmtogeojson/osmtogeojson.js' %}" defer></script> <script src="{% static 'umap/vendors/osmtogeojson/osmtogeojson.js' %}" defer></script>
<script src="{% static 'umap/vendors/loading/Control.Loading.js' %}" defer></script> <script src="{% static 'umap/vendors/loading/Control.Loading.js' %}" defer></script>
<script src="{% static 'umap/vendors/contextmenu/leaflet.contextmenu.min.js' %}"
defer></script>
<script src="{% static 'umap/vendors/photon/leaflet.photon.js' %}" defer></script> <script src="{% static 'umap/vendors/photon/leaflet.photon.js' %}" defer></script>
<script src="{% static 'umap/vendors/fullscreen/Leaflet.fullscreen.min.js' %}" <script src="{% static 'umap/vendors/fullscreen/Leaflet.fullscreen.min.js' %}"
defer></script> defer></script>

View file

@ -161,8 +161,10 @@ def test_can_draw_multi(live_server, page, tilelayer):
page.keyboard.press("Escape") page.keyboard.press("Escape")
expect(multi_button).to_be_hidden() expect(multi_button).to_be_hidden()
polygons.first.click(button="right", position={"x": 10, "y": 10}) polygons.first.click(button="right", position={"x": 10, "y": 10})
expect(page.get_by_role("link", name="Transform to lines")).to_be_hidden() expect(page.get_by_role("button", name="Transform to lines")).to_be_hidden()
expect(page.get_by_role("link", name="Remove shape from the multi")).to_be_visible() expect(
page.get_by_role("button", name="Remove shape from the multi")
).to_be_visible()
def test_can_draw_hole(page, live_server, tilelayer): def test_can_draw_hole(page, live_server, tilelayer):
@ -196,7 +198,7 @@ def test_can_draw_hole(page, live_server, tilelayer):
expect(vertices).to_have_count(8) expect(vertices).to_have_count(8)
# Click on the polygon but not in the hole # Click on the polygon but not in the hole
polygons.first.click(button="right", position={"x": 10, "y": 10}) polygons.first.click(button="right", position={"x": 10, "y": 10})
expect(page.get_by_role("link", name="Transform to lines")).to_be_hidden() expect(page.get_by_role("button", name="Transform to lines")).to_be_hidden()
def test_can_transfer_shape_from_simple_polygon(live_server, page, tilelayer): def test_can_transfer_shape_from_simple_polygon(live_server, page, tilelayer):
@ -228,7 +230,7 @@ def test_can_transfer_shape_from_simple_polygon(live_server, page, tilelayer):
# Now that polygon 2 is selected, right click on first one # Now that polygon 2 is selected, right click on first one
# and transfer shape # and transfer shape
polygons.first.click(position={"x": 20, "y": 20}, button="right") polygons.first.click(position={"x": 20, "y": 20}, button="right")
page.get_by_role("link", name="Transfer shape to edited feature").click() page.get_by_role("button", name="Transfer shape to edited feature").click()
expect(polygons).to_have_count(1) expect(polygons).to_have_count(1)
@ -246,7 +248,9 @@ def test_can_extract_shape(live_server, page, tilelayer, settings):
# Click again to finish # Click again to finish
map.click(position={"x": 100, "y": 100}) map.click(position={"x": 100, "y": 100})
expect(polygons).to_have_count(1) expect(polygons).to_have_count(1)
extract_button = page.get_by_role("link", name="Extract shape to separate feature") extract_button = page.get_by_role(
"button", name="Extract shape to separate feature"
)
expect(extract_button).to_be_hidden() expect(extract_button).to_be_hidden()
page.get_by_title("Add a polygon to the current multi").click() page.get_by_title("Add a polygon to the current multi").click()
map.click(position={"x": 250, "y": 200}) map.click(position={"x": 250, "y": 200})
@ -326,7 +330,9 @@ def test_cannot_transfer_shape_to_line(live_server, page, tilelayer):
# Click again to finish # Click again to finish
map.click(position={"x": 100, "y": 100}) map.click(position={"x": 100, "y": 100})
expect(polygons).to_have_count(1) expect(polygons).to_have_count(1)
extract_button = page.get_by_role("link", name="Extract shape to separate feature") extract_button = page.get_by_role(
"button", name="Extract shape to separate feature"
)
polygons.first.click(position={"x": 20, "y": 20}, button="right") polygons.first.click(position={"x": 20, "y": 20}, button="right")
expect(extract_button).to_be_hidden() expect(extract_button).to_be_hidden()
page.get_by_title("Draw a polyline").click() page.get_by_title("Draw a polyline").click()
@ -352,7 +358,9 @@ def test_cannot_transfer_shape_to_marker(live_server, page, tilelayer):
# Click again to finish # Click again to finish
map.click(position={"x": 100, "y": 100}) map.click(position={"x": 100, "y": 100})
expect(polygons).to_have_count(1) expect(polygons).to_have_count(1)
extract_button = page.get_by_role("link", name="Extract shape to separate feature") extract_button = page.get_by_role(
"button", name="Extract shape to separate feature"
)
polygons.first.click(position={"x": 20, "y": 20}, button="right") polygons.first.click(position={"x": 20, "y": 20}, button="right")
expect(extract_button).to_be_hidden() expect(extract_button).to_be_hidden()
page.get_by_title("Draw a marker").click() page.get_by_title("Draw a marker").click()
@ -377,7 +385,7 @@ def test_can_clone_polygon(live_server, page, tilelayer, settings):
map.click(position={"x": 100, "y": 100}) map.click(position={"x": 100, "y": 100})
expect(polygons).to_have_count(1) expect(polygons).to_have_count(1)
polygons.first.click(button="right") polygons.first.click(button="right")
page.get_by_role("link", name="Clone this feature").click() page.get_by_role("button", name="Clone this feature").click()
expect(polygons).to_have_count(2) expect(polygons).to_have_count(2)
data = save_and_get_json(page) data = save_and_get_json(page)
assert len(data["features"]) == 2 assert len(data["features"]) == 2
@ -402,7 +410,7 @@ def test_can_transform_polygon_to_line(live_server, page, tilelayer, settings):
expect(polygons).to_have_count(1) expect(polygons).to_have_count(1)
expect(paths).to_have_count(1) expect(paths).to_have_count(1)
polygons.first.click(button="right") polygons.first.click(button="right")
page.get_by_role("link", name="Transform to lines").click() page.get_by_role("button", name="Transform to lines").click()
# No more polygons (will fill), but one path, it must be a line # No more polygons (will fill), but one path, it must be a line
expect(polygons).to_have_count(0) expect(polygons).to_have_count(0)
expect(paths).to_have_count(1) expect(paths).to_have_count(1)

View file

@ -157,8 +157,10 @@ def test_can_draw_multi(live_server, page, tilelayer):
page.keyboard.press("Escape") page.keyboard.press("Escape")
expect(add_shape).to_be_hidden() expect(add_shape).to_be_hidden()
lines.first.click(button="right", position={"x": 10, "y": 1}) lines.first.click(button="right", position={"x": 10, "y": 1})
expect(page.get_by_role("link", name="Transform to polygon")).to_be_hidden() expect(page.get_by_role("button", name="Transform to polygon")).to_be_hidden()
expect(page.get_by_role("link", name="Remove shape from the multi")).to_be_visible() expect(
page.get_by_role("button", name="Remove shape from the multi")
).to_be_visible()
def test_can_transfer_shape_from_simple_polyline(live_server, page, tilelayer): def test_can_transfer_shape_from_simple_polyline(live_server, page, tilelayer):
@ -188,7 +190,7 @@ def test_can_transfer_shape_from_simple_polyline(live_server, page, tilelayer):
# Now that line 2 is selected, right click on first one # Now that line 2 is selected, right click on first one
# and transfer shape # and transfer shape
lines.first.click(position={"x": 10, "y": 1}, button="right") lines.first.click(position={"x": 10, "y": 1}, button="right")
page.get_by_role("link", name="Transfer shape to edited feature").click() page.get_by_role("button", name="Transfer shape to edited feature").click()
expect(lines).to_have_count(1) expect(lines).to_have_count(1)
@ -227,7 +229,7 @@ def test_can_transfer_shape_from_multi(live_server, page, tilelayer, settings):
# Now that line 2 is selected, right click on first one # Now that line 2 is selected, right click on first one
# and transfer shape # and transfer shape
lines.first.click(position={"x": 10, "y": 1}, button="right") lines.first.click(position={"x": 10, "y": 1}, button="right")
page.get_by_role("link", name="Transfer shape to edited feature").click() page.get_by_role("button", name="Transfer shape to edited feature").click()
expect(lines).to_have_count(2) expect(lines).to_have_count(2)
data = save_and_get_json(page) data = save_and_get_json(page)
assert data["features"][0]["geometry"] == { assert data["features"][0]["geometry"] == {
@ -259,7 +261,9 @@ def test_can_extract_shape(live_server, page, tilelayer):
# Click again to finish # Click again to finish
map.click(position={"x": 100, "y": 200}) map.click(position={"x": 100, "y": 200})
expect(lines).to_have_count(1) expect(lines).to_have_count(1)
extract_button = page.get_by_role("link", name="Extract shape to separate feature") extract_button = page.get_by_role(
"button", name="Extract shape to separate feature"
)
expect(extract_button).to_be_hidden() expect(extract_button).to_be_hidden()
page.get_by_title("Add a line to the current multi").click() page.get_by_title("Add a line to the current multi").click()
map.click(position={"x": 250, "y": 250}) map.click(position={"x": 250, "y": 250})
@ -287,7 +291,7 @@ def test_can_clone_polyline(live_server, page, tilelayer, settings):
map.click(position={"x": 100, "y": 200}) map.click(position={"x": 100, "y": 200})
expect(lines).to_have_count(1) expect(lines).to_have_count(1)
lines.first.click(position={"x": 10, "y": 1}, button="right") lines.first.click(position={"x": 10, "y": 1}, button="right")
page.get_by_role("link", name="Clone this feature").click() page.get_by_role("button", name="Clone this feature").click()
expect(lines).to_have_count(2) expect(lines).to_have_count(2)
data = save_and_get_json(page) data = save_and_get_json(page)
assert len(data["features"]) == 2 assert len(data["features"]) == 2
@ -313,7 +317,7 @@ def test_can_transform_polyline_to_polygon(live_server, page, tilelayer, setting
expect(paths).to_have_count(1) expect(paths).to_have_count(1)
expect(polygons).to_have_count(0) expect(polygons).to_have_count(0)
paths.first.click(position={"x": 10, "y": 1}, button="right") paths.first.click(position={"x": 10, "y": 1}, button="right")
page.get_by_role("link", name="Transform to polygon").click() page.get_by_role("button", name="Transform to polygon").click()
expect(polygons).to_have_count(1) expect(polygons).to_have_count(1)
expect(paths).to_have_count(1) expect(paths).to_have_count(1)
data = save_and_get_json(page) data = save_and_get_json(page)

View file

@ -69,7 +69,7 @@ def test_websocket_connection_can_sync_markers(
# Delete a marker from peer A and check it's been deleted on peer B # Delete a marker from peer A and check it's been deleted on peer B
a_first_marker.click(button="right") a_first_marker.click(button="right")
peerA.get_by_role("link", name="Delete this feature").click() peerA.get_by_role("button", name="Delete this feature").click()
peerA.locator("dialog").get_by_role("button", name="OK").click() peerA.locator("dialog").get_by_role("button", name="OK").click()
expect(a_marker_pane).to_have_count(1) expect(a_marker_pane).to_have_count(1)
expect(b_marker_pane).to_have_count(1) expect(b_marker_pane).to_have_count(1)
@ -153,7 +153,7 @@ def test_websocket_connection_can_sync_polygons(
# Delete a polygon from peer A and check it's been deleted on peer B # Delete a polygon from peer A and check it's been deleted on peer B
a_polygon.click(button="right") a_polygon.click(button="right")
peerA.get_by_role("link", name="Delete this feature").click() peerA.get_by_role("button", name="Delete this feature").click()
peerA.locator("dialog").get_by_role("button", name="OK").click() peerA.locator("dialog").get_by_role("button", name="OK").click()
expect(a_polygons).to_have_count(0) expect(a_polygons).to_have_count(0)
expect(b_polygons).to_have_count(0) expect(b_polygons).to_have_count(0)
@ -268,7 +268,7 @@ def test_websocket_connection_can_sync_cloned_polygons(
# Clone on peer B and save # Clone on peer B and save
b_polygon.click(button="right") b_polygon.click(button="right")
peerB.get_by_role("link", name="Clone this feature").click() peerB.get_by_role("button", name="Clone this feature").click()
expect(peerB.locator("path")).to_have_count(2) expect(peerB.locator("path")).to_have_count(2)