diff --git a/src/mapml.css b/src/mapml.css index cd3857d12..0fc5cbcb2 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -81,7 +81,6 @@ .mapml-contextmenu { display: none; - overflow-y: auto; max-height: 100%; max-width: 100%; box-shadow: 0 1px 7px rgba(0,0,0,0.4); @@ -91,7 +90,7 @@ background-color: #fff; cursor: default; -webkit-user-select: none; - -moz-user-select: none; + -moz-user-select: none; -ms-user-select: none; user-select: none; } @@ -113,16 +112,8 @@ border-top: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0; } - -.mapml-contextmenu-icon { - margin: 2px 8px 0 0; - width: 16px; - height: 16px; - float: left; - border: 0; -} - + .mapml-contextmenu-separator { border-bottom: 1px solid #ccc; margin: 5px 0; -} +} \ No newline at end of file diff --git a/src/mapml.js b/src/mapml.js index ae7ef65f0..521c726a6 100644 --- a/src/mapml.js +++ b/src/mapml.js @@ -1035,6 +1035,17 @@ M.QueryHandler = L.Handler.extend({ } }); + +/* +MIT License related to portions of M.ContextMenu +Copyright (c) 2017 adam.ratcliffe@gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), +to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +*/ M.ContextMenu = L.Handler.extend({ _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', @@ -1044,41 +1055,83 @@ M.ContextMenu = L.Handler.extend({ //setting the items in the context menu and their callback functions this._items = [ { - text:"Back (B)", + text:"Back (B)", callback:this._goBack, }, { - text:"Forward (F)", + text:"Forward (F)", callback:this._goForward, }, { - text:"Reload (R)", + text:"Reload (R)", callback:this._reload, }, '-', { - text:"Toggle Controls (T)", + text:"Toggle Controls (T)", callback:this._toggleControls, }, { - text:"Copy Coordinates (C)", + text:"Copy Coordinates (C) >", callback:this._copyCoords, + hideOnSelect:false, + submenu:[ + { + text:"tile", + callback:this._copyTile, + }, + { + text:"tilematrix", + callback:this._copyTileMatrix, + }, + '-', + { + text:"map", + callback:this._copyMap, + }, + '-', + { + text:"tcrs", + callback:this._copyTCRS, + }, + { + text:"pcrs", + callback:this._copyPCRS, + }, + { + text:"gcrs", + callback:this._copyGCRS, + }, + ] }, { - text:"View Map Source (V)", + text:"View Map Source (V)", callback:this._viewSource, }, ]; this._visible = false; + this._keyboardEvent = false; this._container = L.DomUtil.create("div", "mapml-contextmenu", map._container); this._container.style.zIndex = 10000; this._container.style.position = "absolute"; this._container.style.width = "150px"; - + this._createItems(); + this._coordMenu = L.DomUtil.create("div", "mapml-contextmenu mapml-submenu", this._container); + this._coordMenu.style.zIndex = 10000; + this._coordMenu.style.position = "absolute"; + + this._coordMenu.style.width = "80px"; + + this._clickEvent = null; + + for(let i =0;i mapSize.x) { + menu.style.left = 'auto'; + menu.style.right = 150 + 'px'; + } else { + menu.style.left = 150 + 'px'; + menu.style.right = 'auto'; + } + + if (click.containerPoint.y + 150 > mapSize.y) { + menu.style.top = 'auto'; + menu.style.bottom = 20 + 'px'; + } else { + menu.style.top = 100 + 'px'; + menu.style.bottom = 'auto'; + } + if(this._keyboardEvent)menu.firstChild.focus(); + }, + + _hideCoordMenu: function(e){ + if(e.srcElement.parentElement.classList.contains("mapml-submenu") || + e.srcElement.innerText === "Copy Coordinates (C) >")return; + let menu = this._coordMenu; + menu.style.display = "none"; + }, + _onItemMouseOver: function (e) { - L.DomUtil.addClass(e.target || e.srcElement, 'over'); + L.DomUtil.addClass(e.target || e.srcElement, 'over'); + if(e.srcElement.innerText === "Copy Coordinates (C) >") this._showCoordMenu(e); }, _onItemMouseOut: function (e) { - L.DomUtil.removeClass(e.target || e.srcElement, 'over'); + L.DomUtil.removeClass(e.target || e.srcElement, 'over'); + this._hideCoordMenu(e); } }); diff --git a/src/mm-mapp.js b/src/mm-mapp.js index b045883be..df1f3326c 100644 --- a/src/mm-mapp.js +++ b/src/mm-mapp.js @@ -69,10 +69,28 @@ export class MmMapp extends HTMLElement { get layers() { return this.getElementsByTagName('layer-'); } + + get extent(){ + let map = this._map, + pcrsBounds = M.pixelToPCRSBounds( + map.getPixelBounds(), + map.getZoom(), + map.options.projection); + let formattedExtent = M.convertAndFormatPCRS(pcrsBounds, map); + if(map.getMaxZoom() !== Infinity){ + formattedExtent.zoom = { + minZoom:map.getMinZoom(), + maxZoom:map.getMaxZoom() + }; + } + return (formattedExtent); + } + constructor() { // Always call super first in constructor super(); + this._source = this.outerHTML; let tmpl = document.createElement('template'); tmpl.innerHTML = `` + @@ -135,12 +153,21 @@ export class MmMapp extends HTMLElement { } else { this._container.style.height = this.height+"px"; } + + // create an array to track the history of the map and the current index + if(!this._history){ + this._history = []; + this._historyIndex = -1; + this._traversalCall = false; + } + // create the Leaflet map if this is the first time attached is called if (!this._map) { this._map = L.map(this._container, { center: new L.LatLng(this.lat, this.lon), projection: this.projection, query: true, + contextMenu: true, mapEl: this, crs: M[this.projection], zoom: this.zoom, @@ -320,6 +347,7 @@ export class MmMapp extends HTMLElement { this._map.on('moveend', function () { this._updateMapCenter(); + this._addToHistory(); this.dispatchEvent(new CustomEvent('moveend', {detail: {target: this}})); }, this); @@ -384,7 +412,7 @@ export class MmMapp extends HTMLElement { } } zoomTo(lat, lon, zoom) { - zoom = zoom||this.zoom; + zoom = Number.isInteger(zoom)? zoom:this.zoom; var location = new L.LatLng(lat,lon); this._map.setView(location, zoom); this.zoom = zoom; @@ -398,6 +426,60 @@ export class MmMapp extends HTMLElement { this.lon = this._map.getCenter().lng; this.zoom = this._map.getZoom(); } + + _addToHistory(){ + if(this._traversalCall){ + this._traversalCall = false; + return; + } + let mapLocation = this._map.getCenter(); + let location ={ + zoom:this._map.getZoom(), + lat:mapLocation.lat, + lng:mapLocation.lng, + }; + this._historyIndex++; + this._history.push(location); + } + + back(){ + let mapEl = this, + history = mapEl._history; + if(mapEl._historyIndex > 0){ + mapEl._historyIndex--; + } + let prev = history[mapEl._historyIndex]; + mapEl._traversalCall = true; + mapEl.zoomTo(prev.lat,prev.lng,prev.zoom); + } + + forward(){ + let mapEl = this, + history = this._history; + if(mapEl._historyIndex < history.length -1){ + mapEl._historyIndex++; + } + let next = history[this._historyIndex]; + mapEl._traversalCall = true; + mapEl.zoomTo(next.lat,next.lng,next.zoom); + } + + reload(){ + let mapEl = this, + initialLocation = mapEl._history.shift(); + mapEl._history = [initialLocation]; + mapEl._historyIndex = -1; + mapEl._traversalCall = true; + mapEl.zoomTo(initialLocation.lat,initialLocation.lng,initialLocation.zoom); + } + + viewSource(){ + let blob = new Blob([this._source],{type:"text/plain"}), + url = URL.createObjectURL(blob); + window.open(url); + URL.revokeObjectURL(url); + } + _ready() { // when used in a custom element, the leaflet script element is hidden inside // the import's shadow dom. diff --git a/src/web-map.js b/src/web-map.js index e70d5ef35..cf4ee6656 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -94,6 +94,7 @@ export class WebMap extends HTMLMapElement { // Always call super first in constructor super(); + this._source = this.outerHTML; let tmpl = document.createElement('template'); tmpl.innerHTML = `` + @@ -499,17 +500,13 @@ export class WebMap extends HTMLMapElement { let mapEl = this, initialLocation = mapEl._history.shift(); mapEl._history = [initialLocation]; + mapEl._historyIndex = -1; mapEl._traversalCall = true; mapEl.zoomTo(initialLocation.lat,initialLocation.lng,initialLocation.zoom); } viewSource(){ - let mapHTML = this.outerHTML, - divIndex = mapHTML.indexOf(" { + await page.click("body > map"); + await page.keyboard.press("Shift+F10"); + const aHandle = await page.evaluateHandle(() => document.querySelector(".web-map")); + const nextHandle = await page.evaluateHandle(doc => doc.shadowRoot, aHandle); + const resultHandle = await page.evaluateHandle(root => root.activeElement, nextHandle); + const nameHandle = await page.evaluateHandle(name => name.outerText, resultHandle); + let name = await nameHandle.jsonValue(); + await nameHandle.dispose(); + expect(name).toEqual("Back (B)"); + }); + + test("[" + browserType + "]" + " Context menu tab goes to next item", async () => { + await page.keyboard.press("Tab"); + const aHandle = await page.evaluateHandle(() => document.querySelector(".web-map")); + const nextHandle = await page.evaluateHandle(doc => doc.shadowRoot, aHandle); + const resultHandle = await page.evaluateHandle(root => root.activeElement, nextHandle); + const nameHandle = await page.evaluateHandle(name => name.outerText, resultHandle); + let name = await nameHandle.jsonValue(); + await nameHandle.dispose(); + expect(name).toEqual("Forward (F)"); + }); + + test("[" + browserType + "]" + " Submenu opens on C with focus on first item", async () => { + await page.keyboard.press("c"); + await page.keyboard.press("Tab"); + const aHandle = await page.evaluateHandle(() => document.querySelector(".web-map")); + const nextHandle = await page.evaluateHandle(doc => doc.shadowRoot, aHandle); + const resultHandle = await page.evaluateHandle(root => root.activeElement, nextHandle); + const nameHandle = await page.evaluateHandle(name => name.outerText, resultHandle); + let name = await nameHandle.jsonValue(); + await nameHandle.dispose(); + expect(name).toEqual("tile"); + }); + test("[" + browserType + "]" + " Context menu displaying on map", async () => { await page.click("body > map", { button: "right" }); const contextMenu = await page.$eval(