From 9d727bd01f90f8f5d7bbc09ea2a219818677ac59 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Thu, 5 Sep 2024 20:14:51 +0200 Subject: [PATCH] wip: use our own contextmenu --- package.json | 1 - scripts/vendorsjs.sh | 1 - umap/static/umap/css/contextmenu.css | 5 + umap/static/umap/js/modules/data/features.js | 157 ++++++++++++++ umap/static/umap/js/modules/global.js | 2 + umap/static/umap/js/modules/rendering/ui.js | 194 +----------------- umap/static/umap/js/modules/ui/base.js | 4 +- umap/static/umap/js/modules/ui/contextmenu.js | 20 +- umap/static/umap/js/umap.controls.js | 17 -- umap/static/umap/js/umap.js | 96 ++++----- .../contextmenu/leaflet.contextmenu.min.css | 1 - .../contextmenu/leaflet.contextmenu.min.js | 7 - umap/templates/umap/js.html | 2 - umap/tests/integration/test_draw_polygon.py | 26 ++- umap/tests/integration/test_draw_polyline.py | 18 +- umap/tests/integration/test_websocket_sync.py | 6 +- 16 files changed, 262 insertions(+), 295 deletions(-) delete mode 100644 umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.css delete mode 100644 umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.js diff --git a/package.json b/package.json index 6b1e0522..836a6260 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "georsstogeojson": "^0.1.0", "jsdom": "^24.0.0", "leaflet": "1.9.4", - "leaflet-contextmenu": "^1.4.0", "leaflet-editable": "^1.3.0", "leaflet-editinosm": "0.2.3", "leaflet-formbuilder": "0.2.10", diff --git a/scripts/vendorsjs.sh b/scripts/vendorsjs.sh index a4835707..46d5993c 100755 --- a/scripts/vendorsjs.sh +++ b/scripts/vendorsjs.sh @@ -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/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/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/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/ diff --git a/umap/static/umap/css/contextmenu.css b/umap/static/umap/css/contextmenu.css index da55f1c8..e7ed2508 100644 --- a/umap/static/umap/css/contextmenu.css +++ b/umap/static/umap/css/contextmenu.css @@ -9,3 +9,8 @@ .umap-contextmenu li + li { margin-top: var(--text-margin); } + +.umap-contextmenu hr { + margin-top: var(--text-margin); + margin-bottom: var(--text-margin); +} diff --git a/umap/static/umap/js/modules/data/features.js b/umap/static/umap/js/modules/data/features.js index 0b334c81..2db9d99d 100644 --- a/umap/static/umap/js/modules/data/features.js +++ b/umap/static/umap/js/modules/data/features.js @@ -591,6 +591,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 { @@ -762,6 +811,62 @@ class Path extends Feature { } 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 { @@ -875,6 +980,41 @@ export class LineString extends Path { isMulti() { 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 { @@ -985,4 +1125,21 @@ export class Polygon extends Path { items.push(U.CreateHoleAction) 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 + } } diff --git a/umap/static/umap/js/modules/global.js b/umap/static/umap/js/modules/global.js index 61a92802..52b23fa1 100644 --- a/umap/static/umap/js/modules/global.js +++ b/umap/static/umap/js/modules/global.js @@ -9,6 +9,7 @@ import { } from './autocomplete.js' import Browser from './browser.js' import Caption from './caption.js' +import ContextMenu from './ui/contextmenu.js' import Facets from './facets.js' import { Formatter } from './formatter.js' import Help from './help.js' @@ -43,6 +44,7 @@ window.U = { AutocompleteDatalist, Browser, Caption, + ContextMenu, DataLayer, DataLayerPermissions, Dialog, diff --git a/umap/static/umap/js/modules/rendering/ui.js b/umap/static/umap/js/modules/rendering/ui.js index 72a3354a..64f037d2 100644 --- a/umap/static/umap/js/modules/rendering/ui.js +++ b/umap/static/umap/js/modules/rendering/ui.js @@ -9,6 +9,7 @@ import { latLng, LatLng, LatLngBounds, + DomEvent, } from '../../../vendors/leaflet/leaflet-src.esm.js' import { translate } from '../i18n.js' import { uMapAlert as Alert } from '../../components/alerts/alert.js' @@ -36,7 +37,7 @@ const FeatureMixin = { }, addInteractions: function () { - this.on('contextmenu editable:vertex:contextmenu', this._showContextMenu) + this.on('contextmenu editable:vertex:contextmenu', this.onContextMenu) this.on('click', this.onClick) }, @@ -61,7 +62,7 @@ const FeatureMixin = { }).addTo(this._map, this.feature, event.latlng) } } - L.DomEvent.stop(event) + DomEvent.stop(event) }, resetTooltip: function () { @@ -83,67 +84,14 @@ const FeatureMixin = { } }, - _showContextMenu: function (event) { - L.DomEvent.stop(event) - const pt = this._map.mouseEventToContainerPoint(event.originalEvent) - event.relatedTarget = this - this._map.contextmenu.showAt(pt, event) - }, - - 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, - } + onContextMenu: function (event) { + DomEvent.stop(event) + const items = this._map.getContextMenuItems(event) + items.push('-', ...this.feature.getContextMenuItems(event)) + this._map.contextmenu.open( + [event.originalEvent.clientX, event.originalEvent.clientY], + items ) - return items }, onCommit: function () { @@ -360,65 +308,6 @@ const PathMixin = { }).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) { if (!this.feature.isMulti()) return const shape = this.enableEdit().deleteShapeAt(atLatLng) @@ -463,46 +352,6 @@ export const LeafletPolyline = Polyline.extend({ 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) { // FIXME: compute from data in feature (with TurfJS) const length = L.GeoUtil.lineLength(this._map, shape || this._defaultShape()) @@ -516,29 +365,6 @@ export const LeafletPolygon = Polygon.extend({ 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) { this.enableEdit().newHole(event.latlng) }, diff --git a/umap/static/umap/js/modules/ui/base.js b/umap/static/umap/js/modules/ui/base.js index 50b55487..725e3c9b 100644 --- a/umap/static/umap/js/modules/ui/base.js +++ b/umap/static/umap/js/modules/ui/base.js @@ -72,9 +72,9 @@ export class Positioned { left = x - this.container.offsetWidth } if (y < window.innerHeight / 2) { - top = y + top = Math.min(y, window.innerHeight - this.container.offsetHeight) } else { - top = y - this.container.offsetHeight + top = Math.max(0, y - this.container.offsetHeight) } this.setPosition({ left, top }) } diff --git a/umap/static/umap/js/modules/ui/contextmenu.js b/umap/static/umap/js/modules/ui/contextmenu.js index 1ff33a5d..9231129a 100644 --- a/umap/static/umap/js/modules/ui/contextmenu.js +++ b/umap/static/umap/js/modules/ui/contextmenu.js @@ -18,14 +18,18 @@ export default class ContextMenu extends Positioned { open([x, y], items) { this.container.innerHTML = '' for (const item of items) { - const li = loadTemplate( - `
  • ` - ) - li.addEventListener('click', () => { - this.close() - item.action() - }) - this.container.appendChild(li) + if (item === '-') { + this.container.appendChild(document.createElement('hr')) + } else { + const li = loadTemplate( + `
  • ` + ) + li.addEventListener('click', () => { + this.close() + item.action() + }) + this.container.appendChild(li) + } } document.body.appendChild(this.container) this.computePosition([x, y]) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 8fc6a5df..ed969d94 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -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({ initialize: function (map, options) { L.Editable.prototype.initialize.call(this, map, options) diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index 66194aab..6c5aa3d9 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -58,6 +58,7 @@ U.Map = L.Map.extend({ this.panel = new U.Panel(this) this.dialog = new U.Dialog({ className: 'dark' }) this.tooltip = new U.Tooltip(this._controlContainer) + this.contextmenu = new U.ContextMenu() if (this.hasEditMode()) { this.editPanel = new U.EditPanel(this) this.fullPanel = new U.FullPanel(this) @@ -197,8 +198,8 @@ U.Map = L.Map.extend({ window.onbeforeunload = () => (this.editEnabled && this.isDirty) || null this.backup() - this.initContextMenu() this.on('click', this.closeInplaceToolbar) + this.on('contextmenu', this.onContextMenu) }, initSyncEngine: async function () { @@ -1679,118 +1680,107 @@ U.Map = L.Map.extend({ this.loader.onAdd(this) }, - initContextMenu: function () { - this.contextmenu = new U.ContextMenu(this) - this.contextmenu.enable() - }, - - setContextMenuItems: function (e) { - let items = [] + getContextMenuItems: function (event) { + const items = [] if (this._zoom !== this.getMaxZoom()) { items.push({ - text: L._('Zoom in'), - callback: function () { - this.zoomIn() - }, + label: L._('Zoom in'), + action: () => this.zoomIn(), }) } if (this._zoom !== this.getMinZoom()) { items.push({ - text: L._('Zoom out'), - callback: function () { - this.zoomOut() - }, + label: L._('Zoom out'), + action: () => this.zoomOut(), }) } - if (e?.relatedTarget) { - if (e.relatedTarget.getContextMenuItems) { - items = items.concat(e.relatedTarget.getContextMenuItems(e)) - } - } if (this.hasEditMode()) { items.push('-') if (this.editEnabled) { if (!this.isDirty) { items.push({ - text: this.help.displayLabel('STOP_EDIT'), - callback: this.disableEdit, + label: this.help.displayLabel('STOP_EDIT'), + action: () => this.disableEdit(), }) } if (this.options.enableMarkerDraw) { items.push({ - text: this.help.displayLabel('DRAW_MARKER'), - callback: this.startMarker, - context: this, + label: this.help.displayLabel('DRAW_MARKER'), + action: () => this.startMarker(event), }) } if (this.options.enablePolylineDraw) { items.push({ - text: this.help.displayLabel('DRAW_POLYGON'), - callback: this.startPolygon, - context: this, + label: this.help.displayLabel('DRAW_POLYGON'), + action: () => this.startPolygon(event), }) } if (this.options.enablePolygonDraw) { items.push({ - text: this.help.displayLabel('DRAW_LINE'), - callback: this.startPolyline, - context: this, + label: this.help.displayLabel('DRAW_LINE'), + action: () => this.startPolyline(event), }) } items.push('-') items.push({ - text: L._('Help'), - callback: function () { - this.help.show('edit') - }, + label: L._('Help'), + action: () => this.help.show('edit'), }) } else { items.push({ - text: this.help.displayLabel('TOGGLE_EDIT'), - callback: this.enableEdit, + label: this.help.displayLabel('TOGGLE_EDIT'), + action: () => this.enableEdit(), }) } } items.push( '-', { - text: L._('Open browser'), - callback: () => this.openBrowser('layers'), + label: L._('Open browser'), + action: () => this.openBrowser('layers'), }, { - text: L._('Browse data'), - callback: () => this.openBrowser('data'), + label: L._('Browse data'), + action: () => this.openBrowser('data'), } ) if (this.options.facetKey) { items.push({ - text: L._('Filter data'), - callback: () => this.openBrowser('filters'), + label: L._('Filter data'), + action: () => this.openBrowser('filters'), }) } items.push( { - text: L._('Open caption'), - callback: this.openCaption, + label: L._('Open caption'), + action: () => this.openCaption(), }, { - text: this.help.displayLabel('SEARCH'), - callback: this.search, + label: this.help.displayLabel('SEARCH'), + action: () => this.search(event), } ) if (this.options.urls.routing) { items.push('-', { - text: L._('Directions from here'), - callback: this.openExternalRouting, + label: L._('Directions from here'), + action: () => this.openExternalRouting(event), }) } if (this.options.urls.edit_in_osm) { items.push('-', { - text: L._('Edit in OpenStreetMap'), - callback: this.editInOSM, + label: L._('Edit in OpenStreetMap'), + 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) { diff --git a/umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.css b/umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.css deleted file mode 100644 index ef6c6a0e..00000000 --- a/umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.css +++ /dev/null @@ -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} diff --git a/umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.js b/umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.js deleted file mode 100644 index 6aca0f6c..00000000 --- a/umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - Leaflet.contextmenu, a context menu for Leaflet. - (c) 2015, Adam Ratcliffe, GeoSmart Maps Limited - - @preserve -*/ -(function(t){var e;if(typeof define==="function"&&define.amd){define(["leaflet"],t)}else if(typeof module==="object"&&typeof module.exports==="object"){e=require("leaflet");module.exports=t(e)}else{if(typeof window.L==="undefined"){throw new Error("Leaflet must be loaded first")}t(window.L)}})(function(t){t.Map.mergeOptions({contextmenuItems:[]});t.Map.ContextMenu=t.Handler.extend({_touchstart:t.Browser.msPointer?"MSPointerDown":t.Browser.pointer?"pointerdown":"touchstart",statics:{BASE_CLS:"leaflet-contextmenu"},initialize:function(e){t.Handler.prototype.initialize.call(this,e);this._items=[];this._visible=false;var n=this._container=t.DomUtil.create("div",t.Map.ContextMenu.BASE_CLS,e._container);n.style.zIndex=1e4;n.style.position="absolute";if(e.options.contextmenuWidth){n.style.width=e.options.contextmenuWidth+"px"}this._createItems();t.DomEvent.on(n,"click",t.DomEvent.stop).on(n,"mousedown",t.DomEvent.stop).on(n,"dblclick",t.DomEvent.stop).on(n,"contextmenu",t.DomEvent.stop)},addHooks:function(){var e=this._map.getContainer();t.DomEvent.on(e,"mouseleave",this._hide,this).on(document,"keydown",this._onKeyDown,this);if(t.Browser.touch){t.DomEvent.on(document,this._touchstart,this._hide,this)}this._map.on({contextmenu:this._show,mousedown:this._hide,movestart:this._hide,zoomstart:this._hide},this)},removeHooks:function(){var e=this._map.getContainer();t.DomEvent.off(e,"mouseleave",this._hide,this).off(document,"keydown",this._onKeyDown,this);if(t.Browser.touch){t.DomEvent.off(document,this._touchstart,this._hide,this)}this._map.off({contextmenu:this._show,mousedown:this._hide,movestart:this._hide,zoomstart:this._hide},this)},showAt:function(e,n){if(e instanceof t.LatLng){e=this._map.latLngToContainerPoint(e)}this._showAtPoint(e,n)},hide:function(){this._hide()},addItem:function(t){return this.insertItem(t)},insertItem:function(t,e){e=e!==undefined?e:this._items.length;var n=this._createItem(this._container,t,e);this._items.push(n);this._sizeChanged=true;this._map.fire("contextmenu.additem",{contextmenu:this,el:n.el,index:e});return n.el},removeItem:function(e){var n=this._container;if(!isNaN(e)){e=n.children[e]}if(e){this._removeItem(t.Util.stamp(e));this._sizeChanged=true;this._map.fire("contextmenu.removeitem",{contextmenu:this,el:e});return e}return null},removeAllItems:function(){var e=this._container.children,n;while(e.length){n=e[0];this._removeItem(t.Util.stamp(n))}return e},hideAllItems:function(){var t,e,n;for(e=0,n=this._items.length;e'}else if(m){u=''}h.innerHTML=u+n.text;h.href="#";t.DomEvent.on(h,"mouseover",this._onItemMouseOver,this).on(h,"mouseout",this._onItemMouseOut,this).on(h,"mousedown",t.DomEvent.stopPropagation).on(h,"click",r);if(t.Browser.touch){t.DomEvent.on(h,this._touchstart,t.DomEvent.stopPropagation)}if(!t.Browser.pointer){t.DomEvent.on(h,"click",this._onItemMouseOut,this)}return{id:t.Util.stamp(h),el:h,callback:r}},_removeItem:function(e){var n,i,o,s,h;for(o=0,s=this._items.length;on.x){i.style.left="auto";i.style.right=Math.min(Math.max(n.x-e.x,0),n.x-o.x-1)+"px"}else{i.style.left=Math.max(e.x,0)+"px";i.style.right="auto"}if(e.y+o.y>n.y){i.style.top="auto";i.style.bottom=Math.min(Math.max(n.y-e.y,0),n.y-o.y-1)+"px"}else{i.style.top=Math.max(e.y,0)+"px";i.style.bottom="auto"}},_getElementSize:function(t){var e=this._size,n=t.style.display;if(!e||this._sizeChanged){e={};t.style.left="-999999px";t.style.right="auto";t.style.display="block";e.x=t.offsetWidth;e.y=t.offsetHeight;t.style.left="auto";t.style.display=n;this._sizeChanged=false}return e},_onKeyDown:function(t){var e=t.keyCode;if(e===27){this._hide()}},_onItemMouseOver:function(e){t.DomUtil.addClass(e.target||e.srcElement,"over")},_onItemMouseOut:function(e){t.DomUtil.removeClass(e.target||e.srcElement,"over")}});t.Map.addInitHook("addHandler","contextmenu",t.Map.ContextMenu);t.Mixin.ContextMenu={bindContextMenu:function(e){t.setOptions(this,e);this._initContextMenu();return this},unbindContextMenu:function(){this.off("contextmenu",this._showContextMenu,this);return this},addContextMenuItem:function(t){this.options.contextmenuItems.push(t)},removeContextMenuItemWithIndex:function(t){var e=[];for(var n=0;n - diff --git a/umap/tests/integration/test_draw_polygon.py b/umap/tests/integration/test_draw_polygon.py index 97f14443..b58f91f7 100644 --- a/umap/tests/integration/test_draw_polygon.py +++ b/umap/tests/integration/test_draw_polygon.py @@ -161,8 +161,10 @@ def test_can_draw_multi(live_server, page, tilelayer): page.keyboard.press("Escape") expect(multi_button).to_be_hidden() 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("link", name="Remove shape from the multi")).to_be_visible() + expect(page.get_by_role("button", name="Transform to lines")).to_be_hidden() + expect( + page.get_by_role("button", name="Remove shape from the multi") + ).to_be_visible() 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) # Click on the polygon but not in the hole 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): @@ -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 # and transfer shape 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) @@ -246,7 +248,9 @@ def test_can_extract_shape(live_server, page, tilelayer, settings): # Click again to finish map.click(position={"x": 100, "y": 100}) 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() page.get_by_title("Add a polygon to the current multi").click() 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 map.click(position={"x": 100, "y": 100}) 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") expect(extract_button).to_be_hidden() 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 map.click(position={"x": 100, "y": 100}) 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") expect(extract_button).to_be_hidden() 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}) expect(polygons).to_have_count(1) 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) data = save_and_get_json(page) 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(paths).to_have_count(1) 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 expect(polygons).to_have_count(0) expect(paths).to_have_count(1) diff --git a/umap/tests/integration/test_draw_polyline.py b/umap/tests/integration/test_draw_polyline.py index 6043fa94..918904d7 100644 --- a/umap/tests/integration/test_draw_polyline.py +++ b/umap/tests/integration/test_draw_polyline.py @@ -157,8 +157,10 @@ def test_can_draw_multi(live_server, page, tilelayer): page.keyboard.press("Escape") expect(add_shape).to_be_hidden() 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("link", name="Remove shape from the multi")).to_be_visible() + expect(page.get_by_role("button", name="Transform to polygon")).to_be_hidden() + 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): @@ -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 # and transfer shape 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) @@ -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 # and transfer shape 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) data = save_and_get_json(page) assert data["features"][0]["geometry"] == { @@ -259,7 +261,9 @@ def test_can_extract_shape(live_server, page, tilelayer): # Click again to finish map.click(position={"x": 100, "y": 200}) 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() page.get_by_title("Add a line to the current multi").click() 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}) expect(lines).to_have_count(1) 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) data = save_and_get_json(page) 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(polygons).to_have_count(0) 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(paths).to_have_count(1) data = save_and_get_json(page) diff --git a/umap/tests/integration/test_websocket_sync.py b/umap/tests/integration/test_websocket_sync.py index dfa86c3a..5ec39f39 100644 --- a/umap/tests/integration/test_websocket_sync.py +++ b/umap/tests/integration/test_websocket_sync.py @@ -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 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() expect(a_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 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() expect(a_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 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)