From 602ff1d24c75b81697be2259e4a659ee208bb340 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Wed, 26 Jul 2023 17:11:11 -0400 Subject: [PATCH] More WIP --- src/layer.js | 115 ++++++++++--------- src/mapml/layers/MapMLLayer.js | 28 ++++- src/web-map.js | 44 +++---- test/e2e/api/domApi-HTMLLayerElement.test.js | 4 +- test/e2e/api/domApi-mapml-viewer.test.js | 8 +- test/e2e/api/domApi-web-map.test.js | 8 +- test/e2e/core/drag.test.js | 8 +- test/e2e/mapml-viewer/mapml-viewer.test.js | 13 +-- 8 files changed, 122 insertions(+), 106 deletions(-) diff --git a/src/layer.js b/src/layer.js index e7ffbfbc2..2b9756e3f 100644 --- a/src/layer.js +++ b/src/layer.js @@ -98,22 +98,59 @@ export class MapLayer extends HTMLElement { this.attachShadow({ mode: 'open' }); } new Promise((resolve, reject) => { - this.addEventListener('extentload', () => { - // need to detect / handle layer.error here - resolve(); + this.addEventListener( + 'extentload', + (event) => { + event.stopPropagation(); + if (event.detail.error) { + reject(); + } else { + resolve(); + } + }, + { once: true } + ); + this.addEventListener( + 'changestyle', + function (e) { + e.stopPropagation(); + this.src = e.detail.src; + }, + { once: true } + ); + this.addEventListener( + 'changeprojection', + function (e) { + e.stopPropagation(); + this.src = e.detail.href; + }, + { once: true } + ); + let base = this.baseURI ? this.baseURI : document.baseURI; + let opacity_value = this.hasAttribute('opacity') + ? this.getAttribute('opacity') + : '1.0'; + this._layer = M.mapMLLayer( + this.src ? new URL(this.src, base).href : null, + this, + { + mapprojection: this.parentElement._map.options.projection, + opacity: opacity_value + } + ); + }) + .then(() => { + this._onLayerExtentLoad(); + this._attachedToMap(); + if (this._layerControl && !this.hidden) { + this._layerControl.addOrUpdateOverlay(this._layer, this.label); + } + }) + .catch((e) => { + this.dispatchEvent( + new CustomEvent('error', { detail: { target: this } }) + ); }); - // this._layer.on('extentload', this._onLayerExtentLoad, this); - this._setUpEvents(); - // add other event listeners possibly - this._ready(); // rename _ready - }).then(() => { - this._onLayerExtentLoad(); - // refactor _attachedToMap - this._attachedToMap(); - if (this._layerControl && !this.hidden) { - this._layerControl.addOrUpdateOverlay(this._layer, this.label); - } - }); } adoptedCallback() { @@ -202,18 +239,12 @@ export class MapLayer extends HTMLElement { if (this._layerControl) { this._layerControl.addOrUpdateOverlay(this._layer, this.label); } - if (!this._layer.error) { - // re-use 'loadedmetadata' event from HTMLMediaElement inteface, applied - // to MapML extent as metadata - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadedmetadata_event - this.dispatchEvent( - new CustomEvent('loadedmetadata', { detail: { target: this } }) - ); - } else { - this.dispatchEvent( - new CustomEvent('error', { detail: { target: this } }) - ); - } + // re-use 'loadedmetadata' event from HTMLMediaElement inteface, applied + // to MapML extent as metadata + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadedmetadata_event + this.dispatchEvent( + new CustomEvent('loadedmetadata', { detail: { target: this } }) + ); } _validateDisabled() { setTimeout(() => { @@ -337,26 +368,6 @@ export class MapLayer extends HTMLElement { this.checked = this._layer._map.hasLayer(this._layer); } } - _ready() { - // the layer might not be attached to a map - // so we need a way for non-src based layers to establish what their - // zoom range, extent and projection are. meta elements in content to - // allow the author to provide this explicitly are one way, they will - // be parsed from the second parameter here - // IE 11 did not have a value for this.baseURI for some reason - var base = this.baseURI ? this.baseURI : document.baseURI; - let opacity_value = this.hasAttribute('opacity') - ? this.getAttribute('opacity') - : '1.0'; - this._layer = M.mapMLLayer( - this.src ? new URL(this.src, base).href : null, - this, - { - mapprojection: this.parentElement._map.options.projection, - opacity: opacity_value - } - ); - } _attachedToMap() { // set i to the position of this layer element in the set of layers var i = 0, @@ -414,16 +425,6 @@ export class MapLayer extends HTMLElement { this._layer.off(); } } - _setUpEvents() { - this.addEventListener('changestyle', function (e) { - e.stopPropagation(); - this.src = e.detail.src; - }); - this.addEventListener('changeprojection', function (e) { - e.stopPropagation(); - this.src = e.detail.href; - }); - } zoomTo() { if (!this.extent) return; let map = this._layer._map, diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js index 56c843b8c..70d0ebee6 100644 --- a/src/mapml/layers/MapMLLayer.js +++ b/src/mapml/layers/MapMLLayer.js @@ -1183,8 +1183,6 @@ export var MapMLLayer = L.Layer.extend({ fetch(url, { headers: headers }) .then((response) => { if (!response.ok) { - layer.error = true; - layer.fire('extentload', layer, true); throw new Error(`HTTP error! Status: ${response.status}`); } return response.text(); @@ -1193,7 +1191,11 @@ export var MapMLLayer = L.Layer.extend({ fCallback(response, false); }) .catch((response) => { - console.log('wut'); + layer.error = true; + layer._layerEl.dispatchEvent( + new CustomEvent('extentload', { detail: layer }) + ); + console.log(`HTTP error! Status: ${response.message}`); }); } function transcribe(element) { @@ -1417,11 +1419,23 @@ export var MapMLLayer = L.Layer.extend({ var mapml = !local ? new DOMParser().parseFromString(content, 'text/xml') : content; + if ( + !local && + (mapml.querySelector('parsererror') || !mapml.querySelector('mapml-')) + ) { + layer.error = true; + layer._layerEl.dispatchEvent( + new CustomEvent('extentload', { detail: layer }) + ); + throw new Error('Parser error'); + } var base = new URL( mapml.querySelector('map-base') ? mapml.querySelector('map-base').getAttribute('href') - : mapml.baseURI, - mapml.baseURI + : local + ? mapml.baseURI + : layer._href, + layer._href ).href; layer._properties = {}; if (mapml.querySelector && mapml.querySelector('map-feature')) @@ -1442,8 +1456,10 @@ export var MapMLLayer = L.Layer.extend({ layer._layerEl.parentElement._toggleControls(); } layer.fire('extentload', layer, false); + // need this to enable processing by the element connectedCallback + // processing layer._layerEl.dispatchEvent( - new CustomEvent('extentload', { detail: layer, bubbles: true }) + new CustomEvent('extentload', { detail: layer }) ); // local functions function thinkOfAGoodName() { diff --git a/src/web-map.js b/src/web-map.js index ff03f80ae..ac72b1cbe 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -75,23 +75,13 @@ export class WebMap extends HTMLMapElement { } } get projection() { - return this.hasAttribute('projection') + return this.hasAttribute('projection') && M[this.getAttribute('projection')] ? this.getAttribute('projection') : 'OSMTILE'; } set projection(val) { if (val && M[val]) { this.setAttribute('projection', val); - if (this._map && this._map.options.projection !== val) { - this._map.options.crs = M[val]; - this._map.options.projection = val; - for (let layer of this.querySelectorAll('layer-')) { - layer.removeAttribute('disabled'); - let reAttach = this.removeChild(layer); - this.appendChild(reAttach); - } - if (this._debug) for (let i = 0; i < 2; i++) this.toggleDebug(); - } else this.dispatchEvent(new CustomEvent('createmap')); } else throw new Error('Undefined Projection'); } get zoom() { @@ -180,18 +170,16 @@ export class WebMap extends HTMLMapElement { // is because the mapml-viewer element has / can have a size of 0 up until after // something that happens between this point and the event handler executing // perhaps a browser rendering cycle?? - this.addEventListener('createmap', this._createMap); let custom = !['CBMTILE', 'APSTILE', 'OSMTILE', 'WGS84'].includes( this.projection ); - if (!custom) { - // this is worth a read, because dispatchEvent is synchronous - // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent - // In particular: - // "All applicable event handlers are called and return before dispatchEvent() returns." - this.dispatchEvent(new CustomEvent('createmap')); - } + // this is worth a read, because dispatchEvent is synchronous + // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent + // In particular: + // "All applicable event handlers are called and return before dispatchEvent() returns." + this._createMap(); + this._toggleStatic(); /* @@ -407,6 +395,22 @@ export class WebMap extends HTMLMapElement { case 'static': this._toggleStatic(); break; + case 'projection': + if (newValue && M[newValue]) { + if (this._map && this._map.options.projection !== newValue) { + this._map.options.crs = M[newValue]; + this._map.options.projection = newValue; + for (let layer of this.querySelectorAll('layer-')) { + layer.removeAttribute('disabled'); + let reAttach = this.removeChild(layer); + this.appendChild(reAttach); + } + if (this._debug) for (let i = 0; i < 2; i++) this.toggleDebug(); + this.zoomTo(this.lat, this.lon, this.zoom); + //this.dispatchEvent(new CustomEvent('projectionchange')); + } + } + break; } } @@ -860,7 +864,7 @@ export class WebMap extends HTMLMapElement { this._updateMapCenter(); this._addToHistory(); this.dispatchEvent( - new CustomEvent('moveend', { detail: { target: this } }) + new CustomEvent('map-moveend', { detail: { target: this } }) ); }, this diff --git a/test/e2e/api/domApi-HTMLLayerElement.test.js b/test/e2e/api/domApi-HTMLLayerElement.test.js index 26f24c923..eab6c8be7 100644 --- a/test/e2e/api/domApi-HTMLLayerElement.test.js +++ b/test/e2e/api/domApi-HTMLLayerElement.test.js @@ -43,7 +43,9 @@ test.describe('HTMLLayerElement DOM API Tests', () => { let localWithTitleLabel = await page.evaluate(() => { return document.querySelector('#local-with-title').label; }); - expect(localWithTitleLabel).toEqual('Layer name set via local map-title element - unsettable via HTMLLayerelement.label'); + expect(localWithTitleLabel).toEqual( + 'Layer name set via local map-title element - unsettable via HTMLLayerelement.label' + ); let localWithTitleName = await page.evaluate(() => { let layer = document.querySelector('#local-with-title'); return layer._layer.getName(); diff --git a/test/e2e/api/domApi-mapml-viewer.test.js b/test/e2e/api/domApi-mapml-viewer.test.js index 27b68abcb..2ebf9fc77 100644 --- a/test/e2e/api/domApi-mapml-viewer.test.js +++ b/test/e2e/api/domApi-mapml-viewer.test.js @@ -140,10 +140,10 @@ test.describe('mapml-viewer DOM API Tests', () => { // locators avoid flaky tests, allegedly const viewer = await page.locator('mapml-viewer'); await viewer.evaluate(() => { - let m = document.querySelector('mapml-viewer'); - document.body.removeChild(m); - document.body.appendChild(m); - }); + let m = document.querySelector('mapml-viewer'); + document.body.removeChild(m); + document.body.appendChild(m); + }); await page.waitForTimeout(200); expect( await viewer.evaluate(() => { diff --git a/test/e2e/api/domApi-web-map.test.js b/test/e2e/api/domApi-web-map.test.js index 27235531d..6d5c40877 100644 --- a/test/e2e/api/domApi-web-map.test.js +++ b/test/e2e/api/domApi-web-map.test.js @@ -133,10 +133,10 @@ test.describe('web-map DOM API Tests', () => { // locators avoid flaky tests, allegedly const viewer = await page.locator('map'); await viewer.evaluate(() => { - let m = document.querySelector('map'); - document.body.removeChild(m); - document.body.appendChild(m); - }); + let m = document.querySelector('map'); + document.body.removeChild(m); + document.body.appendChild(m); + }); await page.waitForTimeout(200); expect( await viewer.evaluate(() => { diff --git a/test/e2e/core/drag.test.js b/test/e2e/core/drag.test.js index 2c5f0f2ed..8a94878fd 100644 --- a/test/e2e/core/drag.test.js +++ b/test/e2e/core/drag.test.js @@ -73,8 +73,8 @@ test.describe('UI Drag&Drop Test', () => { (span) => span.innerText ); const layerIndex = await page.$eval( - '.leaflet-pane.leaflet-overlay-pane > div:nth-child(1)', - (div) => div.style.zIndex + '.leaflet-pane.leaflet-overlay-pane .mapml-templated-tile-container', + (div) => div.parentElement.parentElement.style.zIndex ); const domLayer = await page.$eval( 'body > map > layer-:nth-child(4)', @@ -109,8 +109,8 @@ test.describe('UI Drag&Drop Test', () => { (span) => span.innerText ); const layerIndex = await page.$eval( - '.leaflet-overlay-pane > div:nth-child(2)', - (div) => div.style.zIndex + '.leaflet-overlay-pane .mapml-static-tile-layer', + (div) => div.parentElement.style.zIndex ); const domLayer = await page.$eval( 'map > layer-:nth-child(3)', diff --git a/test/e2e/mapml-viewer/mapml-viewer.test.js b/test/e2e/mapml-viewer/mapml-viewer.test.js index 7c2b40f39..49adbf28c 100644 --- a/test/e2e/mapml-viewer/mapml-viewer.test.js +++ b/test/e2e/mapml-viewer/mapml-viewer.test.js @@ -120,12 +120,9 @@ test.describe('Playwright mapml-viewer Element Tests', () => { await page.$eval('body > mapml-viewer', (layer) => layer.setAttribute('controlslist', 'nolayer') ); + let layerControl = await page.locator('.leaflet-control-layers'); + await expect(layerControl).toBeHidden(); - let layerControlHidden = await page.$eval( - '.leaflet-top.leaflet-right', - (div) => div.firstChild.hidden - ); - expect(layerControlHidden).toEqual(true); await page.click('body > mapml-viewer', { button: 'right' }); // toggle controls await page.click('.mapml-contextmenu > button:nth-of-type(6)'); @@ -133,11 +130,7 @@ test.describe('Playwright mapml-viewer Element Tests', () => { // toggle controls await page.click('.mapml-contextmenu > button:nth-of-type(6)'); - layerControlHidden = await page.$eval( - '.leaflet-top.leaflet-right', - (div) => div.firstChild.hidden - ); - expect(layerControlHidden).toEqual(true); + await expect(layerControl).toBeHidden(); }); }); });