Skip to content
7 changes: 5 additions & 2 deletions src/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export class MapLayer extends HTMLElement {
// this is moved up here so that the layer control doesn't respond
// to the layer being removed with the _onLayerChange execution
// that is set up in _attached:
if(this.hasAttribute("data-moving")) return;
this._removeEvents();
if (this._layer._map) {
this._layer._map.removeLayer(this._layer);
Expand All @@ -83,6 +84,7 @@ export class MapLayer extends HTMLElement {
connectedCallback() {
//creates listener that waits for createmap event, this allows for delayed builds of maps
//this allows a safeguard for the case where loading a custom TCRS takes longer than loading mapml-viewer.js/web-map.js
if(this.hasAttribute("data-moving")) return;
this.parentNode.addEventListener('createmap', ()=>{
this._ready();
// if the map has been attached, set this layer up wrt Leaflet map
Expand Down Expand Up @@ -235,9 +237,10 @@ export class MapLayer extends HTMLElement {
for (var nodes = this.parentNode.children;i < nodes.length;i++) {
if (this.parentNode.children[i].nodeName === "LAYER-") {
if (this.parentNode.children[i] === this) {
break;
position = i + 1;
} else if (this.parentNode.children[i]._layer) {
this.parentNode.children[i]._layer.setZIndex(i+1);
}
position++;
}
}
var proj = this.parentNode.projection ? this.parentNode.projection : "OSMTILE";
Expand Down
4 changes: 4 additions & 0 deletions src/mapml.css
Original file line number Diff line number Diff line change
Expand Up @@ -354,3 +354,7 @@ summary {
.leaflet-container .leaflet-control-container {
visibility: unset!important;
}

.mapml-draggable * {
cursor: row-resize;
}
75 changes: 61 additions & 14 deletions src/mapml/layers/MapLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,19 +487,20 @@ export var MapMLLayer = L.Layer.extend({
opacity = document.createElement('input'),
opacityControl = document.createElement('details'),
opacityControlSummary = document.createElement('summary'),
opacityControlSummaryLabel = document.createElement('label');
opacityControlSummaryLabel = document.createElement('label'),
mapEl = this._layerEl.parentNode;

input.defaultChecked = this._map ? true: false;
input.type = 'checkbox';
input.className = 'leaflet-control-layers-selector';
name.draggable = true;
name.layer = this;

if (this._legendUrl) {
var legendLink = document.createElement('a');
legendLink.text = ' ' + this._title;
legendLink.href = this._legendUrl;
legendLink.target = '_blank';
legendLink.draggable = false;
name.appendChild(legendLink);
} else {
name.innerHTML = ' ' + this._title;
Expand All @@ -521,19 +522,65 @@ export var MapMLLayer = L.Layer.extend({
opacity.setAttribute('step','0.1');
opacity.value = this._container.style.opacity || '1.0';

L.DomEvent.on(opacity,'change', this._changeOpacity, this);
L.DomEvent.on(name,'dragstart', function(event) {
// will have to figure out how to drag and drop a whole element
// with its contents in the case where the <layer->content</layer->
// has no src but does have inline content.
// Should be do-able, I think.
if (this._href) {
event.dataTransfer.setData("text/uri-list",this._href);
// Why use a second .setData("text/plain"...) ? This is very important:
// See https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#link
event.dataTransfer.setData("text/plain", this._href);
fieldset.setAttribute("aria-grabbed", "false");

fieldset.onmousedown = (downEvent) => {
if(downEvent.target.tagName.toLowerCase() === "input") return;
downEvent.preventDefault();
let control = fieldset,
controls = fieldset.parentNode,
moving = false, yPos = downEvent.clientY;

document.body.onmousemove = (moveEvent) => {
moveEvent.preventDefault();

// Fixes flickering by only moving element when there is enough space
let offset = Math.abs(yPos - moveEvent.clientY);
moving = Math.abs(offset) > 5 || moving;

if(controls && !moving ||
controls.getBoundingClientRect().top > control.getBoundingClientRect().top ||
controls.getBoundingClientRect().bottom < control.getBoundingClientRect().bottom) return;

controls.classList.add("mapml-draggable");
let x = moveEvent.clientX, y = moveEvent.clientY,
root = mapEl.tagName === "MAPML-VIEWER" ? mapEl.shadowRoot : mapEl.querySelector(".web-map").shadowRoot,
elementAt = root.elementFromPoint(x, y),
swapControl = !elementAt || !elementAt.closest("fieldset") ? control : elementAt.closest("fieldset");

swapControl = offset <= swapControl.offsetHeight ? control : swapControl;

control.setAttribute("aria-grabbed", 'true');
control.setAttribute("aria-dropeffect", "move");
if(swapControl && controls === swapControl.parentNode){
swapControl = swapControl !== control.nextSibling? swapControl : swapControl.nextSibling;
if(control !== swapControl) yPos = moveEvent.clientY;
controls.insertBefore(control, swapControl);
}
}, this);
};

document.body.onmouseup = () => {
control.setAttribute("aria-grabbed", "false");
control.removeAttribute("aria-dropeffect");
let controlsElems = controls.children,
zIndex = 1;
for(let c of controlsElems){
let layerEl = c.querySelector("span").layer._layerEl;

layerEl.setAttribute("data-moving","");
mapEl.insertAdjacentElement("beforeend", layerEl);
layerEl.removeAttribute("data-moving");


layerEl._layer.setZIndex(zIndex);
zIndex++;
}
controls.classList.remove("mapml-draggable");
document.body.onmousemove = document.body.onmouseup = null;
};
};

L.DomEvent.on(opacity,'change', this._changeOpacity, this);

fieldset.appendChild(details);
details.appendChild(summary);
Expand Down
13 changes: 11 additions & 2 deletions test/e2e/core/drag.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,18 @@
</head>

<body>
<map is="web-map" projection="CBMTILE" zoom="2" lat="45.5052040" lon="-75.2202344" controls>
<map is="web-map" style="width: 500px;height: 500px;" projection="CBMTILE" zoom="2" lat="45.5052040" lon="-75.2202344"
controls>
<layer- label="CBMT" src="data/cbmt.mapml" checked></layer->

<layer- label="Static MapML with tiles" checked>
<meta name="zoom" content="min=0,max=10">
<meta name="projection" content="CBMTILE">
<link rel="license"
href="https://www.nrcan.gc.ca/earth-sciences/geography/topographic-information/free-data-geogratis/licence/17285"
title="Canada Base Map © Natural Resources Canada">
<tile zoom="0" row="3" col="3" src="data/cbmt/0/c3_r3.png"></tile>
</layer->
<layer- label="vector states" src="data/us_pop_density.mapml" checked></layer->
</map>
</body>

Expand Down
151 changes: 64 additions & 87 deletions test/e2e/core/drag.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ jest.setTimeout(30000);
beforeEach(async () => {
browser = await playwright[browserType].launch({
headless: ISHEADLESS,
slowMo: 50,
});
context = await browser.newContext();
page = await context.newPage();
Expand All @@ -32,103 +33,79 @@ jest.setTimeout(30000);
dataTransfer,
});
await page.hover(".leaflet-top.leaflet-right");
let vars = await page.$$("[draggable='true']");
expect(vars.length).toBe(1);
let vars = await page.$$("xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset");
expect(vars.length).toBe(3);
});

test("[" + browserType + "]" + " Drag and drop of layers", async () => {
const dataTransfer = await page.evaluateHandle(
() => new DataTransfer()
);
await page.hover(".leaflet-top.leaflet-right");
await page.dispatchEvent("[draggable='true']", "dragstart", {
dataTransfer,
});
await page.dispatchEvent(".leaflet-top.leaflet-right", "drop", {
dataTransfer,
});
let control = await page.$("xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(1)");
let controlBBox = await control.boundingBox();
await page.mouse.move(controlBBox.x + controlBBox.width / 2, controlBBox.y + controlBBox.height / 2);
await page.mouse.down();
await page.mouse.move(50, 50);
await page.mouse.up();
await page.hover(".leaflet-top.leaflet-right");
let vars = await page.$$("[draggable='true']");
expect(vars.length).toBe(2);
let vars = await page.$$("xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset");
expect(vars.length).toBe(3);
});

/* Comment in later on
test("drag and drop of null object", async () => {
const dataTransfer = await page.evaluateHandle(
() => new DataTransfer()
);
await page.hover(".leaflet-top.leaflet-right");
await page.dispatchEvent(".leaflet-control-zoom-in", "dragstart", {
dataTransfer,
});
await page.dispatchEvent(".leaflet-top.leaflet-right", "drop", {
dataTransfer,
test("[" + browserType + "]" + " Moving layer down one in control overlay", async () => {
await page.hover(".leaflet-top.leaflet-right");
let control = await page.$("xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(1)");
let controlBBox = await control.boundingBox();
await page.mouse.move(controlBBox.x + controlBBox.width / 2, controlBBox.y + controlBBox.height / 2);
await page.mouse.down();
await page.mouse.move(controlBBox.x + controlBBox.width / 2, (controlBBox.y + controlBBox.height / 2) + 45);
await page.mouse.up();
await page.hover(".leaflet-top.leaflet-right");

const controlText = await page.$eval(
"xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(2) > details > summary > label > span",
(span) => span.innerText
);
const layerIndex = await page.$eval(
"xpath=//html/body/map >> css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div:nth-child(1)",
(div) => div.style.zIndex
);
const domLayer = await page.$eval(
"body > map > layer-:nth-child(4)",
(div) => div.label
);

expect(controlText.toLowerCase()).toContain(domLayer.toLowerCase());
expect(layerIndex).toEqual("2");
expect(controlText).toBe(" Canada Base Map - Transportation (CBMT)");
});
await page.hover(".leaflet-top.leaflet-right");
let vars = await page.$$("[draggable='true']");
expect(vars.length).toBe(1);
});
*/

//adding layer in html can add any type of layer the user wants,
//but how should that layer get treated by the map element,
//should it be ignored or shown as undefined
/*
test("HTML - add additional MapML Layer", async () => {
const { document } = new JSDOM(`
<!doctype html>
<html>
<head>
<title>index-map.html</title>
<meta charset="UTF-8">
<script type="module" src="dist/web-map.js"></script>
<style>
html {height: 100%} body,map {height: inherit} * {margin: 0;padding: 0;}
</style>
</head>
<body>
<map is="web-map" projection="CBMTILE" zoom="2" lat="45" lon="-90" controls >
<layer- label='CBMT' src='https://geogratis.gc.ca/mapml/en/cbmtile/cbmt/' checked></layer->
<layer- label='CBMT' src='https://geogratis.gc.ca/mapml/en/cbmtile/cbmt/' checked></layer->
</map>
</body>
</html>
`).window;
const { select, update } = await domToPlaywright(page, document);
test("[" + browserType + "]" + " Moving layer up one in control overlay", async () => {
await page.hover(".leaflet-top.leaflet-right");
let control = await page.$("xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(2)");
let controlBBox = await control.boundingBox();
await page.mouse.move(controlBBox.x + controlBBox.width / 2, controlBBox.y + controlBBox.height / 2);
await page.mouse.down();
await page.mouse.move(controlBBox.x + controlBBox.width / 2, (controlBBox.y + controlBBox.height / 2) - 45);
await page.mouse.up();
await page.hover(".leaflet-top.leaflet-right");

await update(document);
await page.hover(".leaflet-top.leaflet-right");
let vars = await page.$$("[draggable='true']");
expect(vars.length).toBe(2);
});
test("HTML - add additional non-MapML Layer", async () => {
const { document } = new JSDOM(`
<!doctype html>
<html>
<head>
<title>index-map.html</title>
<meta charset="UTF-8">
<script type="module" src="dist/web-map.js"></script>
<style>
html {height: 100%} body,map {height: inherit} * {margin: 0;padding: 0;}
</style>
</head>
<body>
<map is="web-map" projection="CBMTILE" zoom="2" lat="45" lon="-90" controls >
<layer- label='CBMT' src='https://geogratis.gc.ca/mapml/en/cbmtile/cbmt/' checked></layer->
<layer- label='CBMT' src='https://example.com/' checked></layer->
</map>
</body>
</html>
`).window;
const { select, update } = await domToPlaywright(page, document);
const controlText = await page.$eval(
"xpath=//html/body/map >> css=div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(1) > details > summary > label > span",
(span) => span.innerText
);
const layerIndex = await page.$eval(
"xpath=//html/body/map >> css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div:nth-child(2)",
(div) => div.style.zIndex
);
const domLayer = await page.$eval(
"body > map > layer-:nth-child(3)",
(div) => div.label
);

expect(controlText.toLowerCase()).toContain(domLayer.toLowerCase());
expect(layerIndex).toEqual("1");
expect(controlText).toBe(" Static MapML With Tiles");
});

await update(document);
await page.hover(".leaflet-top.leaflet-right");
let vars = await page.$$("[draggable='true']");
expect(vars.length).toBe(1);
});
*/
});
}
})();
})();