Skip to content

Ability to reorder layer in control panel, reflect changes in DOM and map #249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 21, 2021
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);
});
*/
});
}
})();
})();