From 3ba3a20848bf7e5bffda4d083c79539862dfcaea Mon Sep 17 00:00:00 2001 From: HarelM Date: Tue, 19 Dec 2023 23:44:35 +0200 Subject: [PATCH] Fix tests, types, added data-wd-key --- cypress/e2e/accessibility.cy.ts | 6 +- cypress/e2e/driver.ts | 46 +- cypress/e2e/keyboard.cy.ts | 6 +- cypress/e2e/layers.cy.ts | 24 +- cypress/e2e/map.cy.ts | 8 +- cypress/e2e/modals.cy.ts | 32 +- src/components/App.jsx | 890 ++++++++++++++----------------- src/components/AppToolbar.jsx | 355 ++++++------ src/components/LayerEditor.jsx | 456 +++++++--------- src/components/LayerList.jsx | 332 ++++++------ src/components/MapMaplibreGl.jsx | 224 ++++---- src/libs/{debug.js => debug.ts} | 24 +- src/libs/zoomcontrol.js | 21 +- vite.config.ts | 13 +- 14 files changed, 1124 insertions(+), 1313 deletions(-) rename src/libs/{debug.js => debug.ts} (54%) diff --git a/cypress/e2e/accessibility.cy.ts b/cypress/e2e/accessibility.cy.ts index 5a47f1ad..5c5ebfee 100644 --- a/cypress/e2e/accessibility.cy.ts +++ b/cypress/e2e/accessibility.cy.ts @@ -11,7 +11,7 @@ describe("accessibility", () => { it("skip link to layer list", () => { const selector = "root:skip:layer-list"; - should.isExists(selector); + should.exist(selector); when.tab(); should.beFocused(selector); when.click(selector); @@ -20,7 +20,7 @@ describe("accessibility", () => { it("skip link to layer editor", () => { const selector = "root:skip:layer-editor"; - should.isExists(selector); + should.exist(selector); when.tab().tab(); should.beFocused(selector); when.click(selector); @@ -29,7 +29,7 @@ describe("accessibility", () => { it("skip link to map view", () => { const selector = "root:skip:map-view"; - should.isExists(selector); + should.exist(selector); when.tab().tab().tab(); should.beFocused(selector); when.click(selector); diff --git a/cypress/e2e/driver.ts b/cypress/e2e/driver.ts index 16cd60e4..71762db2 100644 --- a/cypress/e2e/driver.ts +++ b/cypress/e2e/driver.ts @@ -73,7 +73,7 @@ export default class MaputnikDriver { } cy.get(".maputnik-toolbar-link").should("be.visible"); }, - fillLayersModal: (opts: any) => { + fillLayersModal: (opts: {type: string, layer?: string, id?: string}) => { var type = opts.type; var layer = opts.layer; var id; @@ -84,12 +84,12 @@ export default class MaputnikDriver { } cy.get( - this.get.getDataAttribute("add-layer.layer-type", "select") + this.get.dataAttribute("add-layer.layer-type", "select") ).select(type); - cy.get(this.get.getDataAttribute("add-layer.layer-id", "input")).type(id); + cy.get(this.get.dataAttribute("add-layer.layer-id", "input")).type(id); if (layer) { cy.get( - this.get.getDataAttribute("add-layer.layer-source-block", "input") + this.get.dataAttribute("add-layer.layer-source-block", "input") ).type(layer); } this.when.click("add-layer"); @@ -103,11 +103,20 @@ export default class MaputnikDriver { click: (selector: string) => { this.helper.when.click(selector); - // cy.get(selector).click({ force: true }); + }, + + clickZoomin: () => { + cy.get(".maplibregl-ctrl-zoom-in").click(); + }, + + selectWithin: (selector: string, value: string) => { + this.when.within(selector, () => { + cy.get("select").select(value); + }); }, select: (selector: string, value: string) => { - cy.get(selector).select(value); + this.helper.get.element(selector).select(value); }, focus: (selector: string) => { @@ -126,8 +135,8 @@ export default class MaputnikDriver { openLayersModal: () => { this.helper.when.click("layer-list:add-layer"); - cy.get(this.get.getDataAttribute("modal:add-layer")).should("exist"); - cy.get(this.get.getDataAttribute("modal:add-layer")).should("be.visible"); + cy.get(this.get.dataAttribute("modal:add-layer")).should("exist"); + cy.get(this.get.dataAttribute("modal:add-layer")).should("be.visible"); }, }; @@ -135,16 +144,16 @@ export default class MaputnikDriver { isMac: () => { return Cypress.platform === "darwin"; }, - getStyleFromWindow: (win: Window) => { + styleFromWindow: (win: Window) => { const styleId = win.localStorage.getItem("maputnik:latest_style"); const styleItem = win.localStorage.getItem(`maputnik:style:${styleId}`); const obj = JSON.parse(styleItem || ""); return obj; }, - getExampleFileUrl: () => { + exampleFileUrl: () => { return "http://localhost:8888/example-style.json"; }, - getDataAttribute: (key: string, selector?: string): string => { + dataAttribute: (key: string, selector?: string): string => { return `*[data-wd-key='${key}'] ${selector || ""}`; }, }; @@ -176,23 +185,26 @@ export default class MaputnikDriver { equalStyleStore: (getter: (obj: any) => any, styleObj: any) => { cy.window().then((win: any) => { - const obj = this.get.getStyleFromWindow(win); + const obj = this.get.styleFromWindow(win); assert.deepEqual(getter(obj), styleObj); }); }, - isStyleStoreEqualToExampleFileData: () => { + styleStoreEqualToExampleFileData: () => { cy.window().then((win: any) => { - const obj = this.get.getStyleFromWindow(win); + const obj = this.get.styleFromWindow(win); cy.fixture("example-style.json").should("deep.equal", obj); }); }, - isExists: (selector: string) => { + exist: (selector: string) => { this.helper.get.element(selector).should("exist"); }, - isSelected: (selector: string, value: string) => { - cy.get(selector).find(`option[value="${value}"]`).should("be.selected"); + beSelected: (selector: string, value: string) => { + this.helper.get.element(selector).find(`option[value="${value}"]`).should("be.selected"); }, + containText: (selector: string, text: string) => { + this.helper.get.element(selector).should("contain.text", text); + } }; } diff --git a/cypress/e2e/keyboard.cy.ts b/cypress/e2e/keyboard.cy.ts index 3d7a24bb..8cb5b1ce 100644 --- a/cypress/e2e/keyboard.cy.ts +++ b/cypress/e2e/keyboard.cy.ts @@ -1,4 +1,4 @@ -import { default as MaputnikDriver } from "./driver"; +import MaputnikDriver from "./driver"; describe("keyboard", () => { let { beforeAndAfter, given, when, get, should } = new MaputnikDriver(); @@ -45,12 +45,12 @@ describe("keyboard", () => { it("'i' should change map to inspect mode", () => { when.typeKeys("i"); - should.isSelected(get.getDataAttribute("nav:inspect"), "inspect"); + should.beSelected("nav:inspect", "inspect"); }); it("'m' should focus map", () => { when.typeKeys("m"); - should.beFocused(".maplibregl-canvas"); + should.canvasBeFocused(); }); it("'!' should show debug modal", () => { diff --git a/cypress/e2e/layers.cy.ts b/cypress/e2e/layers.cy.ts index 736a578e..41efdb43 100644 --- a/cypress/e2e/layers.cy.ts +++ b/cypress/e2e/layers.cy.ts @@ -1,4 +1,3 @@ -var assert = require("assert"); import { v1 as uuid } from "uuid"; import MaputnikDriver from "./driver"; @@ -134,12 +133,9 @@ describe("layers", () => { // Setup var id = uuid(); - when.select( - get.getDataAttribute("add-layer.layer-type", "select"), - "background" - ); + when.selectWithin("add-layer.layer-type", "background"); when.setValue( - get.getDataAttribute("add-layer.layer-id", "input"), + get.dataAttribute("add-layer.layer-id", "input"), "background:" + id ); @@ -167,7 +163,7 @@ describe("layers", () => { var id = uuid(); when.setValue( - get.getDataAttribute("layer-editor.layer-id", "input"), + get.dataAttribute("layer-editor.layer-id", "input"), "foobar:" + id ); when.click("min-zoom"); @@ -188,7 +184,7 @@ describe("layers", () => { when.click("layer-list-item:background:" + bgId); when.setValue( - get.getDataAttribute("min-zoom", 'input[type="text"]'), + get.dataAttribute("min-zoom", 'input[type="text"]'), "1" ); @@ -206,8 +202,8 @@ describe("layers", () => { ); // AND RESET! - // driver.setValue(driver.getDataAttribute("min-zoom", "input"), "") - // driver.click(driver.getDataAttribute("max-zoom", "input")); + // driver.setValue(driver.get.dataAttribute("min-zoom", "input"), "") + // driver.click(driver.get.dataAttribute("max-zoom", "input")); // driver.isStyleStoreEqual((a: any) => a.layers, [ // { @@ -222,7 +218,7 @@ describe("layers", () => { when.click("layer-list-item:background:" + bgId); when.setValue( - get.getDataAttribute("max-zoom", 'input[type="text"]'), + get.dataAttribute("max-zoom", 'input[type="text"]'), "1" ); @@ -245,7 +241,7 @@ describe("layers", () => { var id = uuid(); when.click("layer-list-item:background:" + bgId); - when.setValue(get.getDataAttribute("layer-comment", "textarea"), id); + when.setValue(get.dataAttribute("layer-comment", "textarea"), id); when.click("layer-editor.layer-id"); @@ -324,9 +320,7 @@ describe("layers", () => { when.typeKeys( "\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013 {" ); - should.isExists(errorSelector); - - when.click("layer-editor.layer-id"); + should.exist(errorSelector); }); }); }); diff --git a/cypress/e2e/map.cy.ts b/cypress/e2e/map.cy.ts index ea0b695b..afccd755 100644 --- a/cypress/e2e/map.cy.ts +++ b/cypress/e2e/map.cy.ts @@ -8,18 +8,16 @@ describe("map", () => { var zoomLevel = 12.37; when.setStyle("geojson", zoomLevel); should.beVisible("maplibre:ctrl-zoom"); - // HM TODO - //driver.getText(".maplibregl-ctrl-zoom") === "Zoom "+(zoomLevel); + should.containText("maplibre:ctrl-zoom", "Zoom: " + zoomLevel); }); it("via map controls", () => { var zoomLevel = 12.37; when.setStyle("geojson", zoomLevel); - when.click("maplibre:ctrl-zoom"); should.beVisible("maplibre:ctrl-zoom"); - // HM TODO - //driver.getText(".maplibregl-ctrl-zoom") === "Zoom "+(zoomLevel + 1); + when.clickZoomin(); + should.containText("maplibre:ctrl-zoom", "Zoom: "+(zoomLevel + 1)); }); }); }); diff --git a/cypress/e2e/modals.cy.ts b/cypress/e2e/modals.cy.ts index a910d2c4..e5fd505f 100644 --- a/cypress/e2e/modals.cy.ts +++ b/cypress/e2e/modals.cy.ts @@ -20,17 +20,17 @@ describe("modals", () => { // HM: I was not able to make the following choose file actually to select a file and close the modal... when.chooseExampleFile(); - should.isStyleStoreEqualToExampleFileData(); + should.styleStoreEqualToExampleFileData(); }); it("load from url", () => { - var styleFileUrl = get.getExampleFileUrl(); + var styleFileUrl = get.exampleFileUrl(); - when.setValue(get.getDataAttribute("modal:open.url.input"), styleFileUrl); + when.setValue(get.dataAttribute("modal:open.url.input"), styleFileUrl); when.click("modal:open.url.button"); when.waitForExampleFileRequset(); - should.isStyleStoreEqualToExampleFileData(); + should.styleStoreEqualToExampleFileData(); }); }); @@ -67,7 +67,7 @@ describe("modals", () => { it("toggle", () => { when.setStyle("geojson"); - when.select(get.getDataAttribute("nav:inspect", "select"), "inspect"); + when.selectWithin("nav:inspect", "inspect"); }); }); @@ -77,20 +77,20 @@ describe("modals", () => { }); it("name", () => { - when.setValue(get.getDataAttribute("modal:settings.name"), "foobar"); + when.setValue(get.dataAttribute("modal:settings.name"), "foobar"); when.click("modal:settings.owner"); should.equalStyleStore((obj) => obj.name, "foobar"); }); it("owner", () => { - when.setValue(get.getDataAttribute("modal:settings.owner"), "foobar"); + when.setValue(get.dataAttribute("modal:settings.owner"), "foobar"); when.click("modal:settings.name"); should.equalStyleStore((obj) => obj.owner, "foobar"); }); it("sprite url", () => { when.setValue( - get.getDataAttribute("modal:settings.sprite"), + get.dataAttribute("modal:settings.sprite"), "http://example.com" ); when.click("modal:settings.name"); @@ -99,7 +99,7 @@ describe("modals", () => { }); it("glyphs url", () => { var glyphsUrl = "http://example.com/{fontstack}/{range}.pbf"; - when.setValue(get.getDataAttribute("modal:settings.glyphs"), glyphsUrl); + when.setValue(get.dataAttribute("modal:settings.glyphs"), glyphsUrl); when.click("modal:settings.name"); should.equalStyleStore((obj) => obj.glyphs, glyphsUrl); @@ -108,7 +108,7 @@ describe("modals", () => { it("maptiler access token", () => { var apiKey = "testing123"; when.setValue( - get.getDataAttribute( + get.dataAttribute( "modal:settings.maputnik:openmaptiles_access_token" ), apiKey @@ -124,7 +124,7 @@ describe("modals", () => { it("thunderforest access token", () => { var apiKey = "testing123"; when.setValue( - get.getDataAttribute( + get.dataAttribute( "modal:settings.maputnik:thunderforest_access_token" ), apiKey @@ -139,14 +139,8 @@ describe("modals", () => { it("style renderer", () => { cy.on("uncaught:exception", () => false); // this is due to the fact that this is an invalid style for openlayers - when.select( - get.getDataAttribute("modal:settings.maputnik:renderer"), - "ol" - ); - should.isSelected( - get.getDataAttribute("modal:settings.maputnik:renderer"), - "ol" - ); + when.select("modal:settings.maputnik:renderer", "ol"); + should.beSelected("modal:settings.maputnik:renderer", "ol"); when.click("modal:settings.name"); diff --git a/src/components/App.jsx b/src/components/App.jsx index 5ef42a0d..4287f8eb 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -1,50 +1,43 @@ -import { arrayMoveMutable } from "array-move"; -import buffer from "buffer"; -import { unset } from "lodash"; -import clamp from "lodash.clamp"; -import cloneDeep from "lodash.clonedeep"; -import get from "lodash.get"; -import React from "react"; -import autoBind from "react-autobind"; +import autoBind from 'react-autobind'; +import React from 'react' +import cloneDeep from 'lodash.clonedeep' +import clamp from 'lodash.clamp' +import buffer from 'buffer' +import get from 'lodash.get' +import {unset} from 'lodash' +import {arrayMoveMutable} from 'array-move' +import url from 'url' import hash from "string-hash"; -import url from "url"; -import AppLayout from "./AppLayout"; -import MessagePanel from "./AppMessagePanel"; -import AppToolbar from "./AppToolbar"; -import LayerEditor from "./LayerEditor"; -import LayerList from "./LayerList"; -import MapMaplibreGl from "./MapMaplibreGl"; -import MapOpenLayers from "./MapOpenLayers"; +import MapMaplibreGl from './MapMaplibreGl' +import MapOpenLayers from './MapOpenLayers' +import LayerList from './LayerList' +import LayerEditor from './LayerEditor' +import AppToolbar from './AppToolbar' +import AppLayout from './AppLayout' +import MessagePanel from './AppMessagePanel' -import ModalDebug from "./ModalDebug"; -import ModalExport from "./ModalExport"; -import ModalOpen from "./ModalOpen"; -import ModalSettings from "./ModalSettings"; -import ModalShortcuts from "./ModalShortcuts"; -import ModalSources from "./ModalSources"; -import ModalSurvey from "./ModalSurvey"; +import ModalSettings from './ModalSettings' +import ModalExport from './ModalExport' +import ModalSources from './ModalSources' +import ModalOpen from './ModalOpen' +import ModalShortcuts from './ModalShortcuts' +import ModalSurvey from './ModalSurvey' +import ModalDebug from './ModalDebug' -import { latest, validate } from "@maplibre/maplibre-gl-style-spec"; -import isEqual from "lodash.isequal"; -import tokens from "../config/tokens.json"; -import { ApiStyleStore } from "../libs/apistore"; -import Debug from "../libs/debug"; -import { redoMessages, undoMessages } from "../libs/diffmessage"; -import LayerWatcher from "../libs/layerwatcher"; -import { - downloadGlyphsMetadata, - downloadSpriteMetadata, -} from "../libs/metadata"; -import { RevisionStore } from "../libs/revisions"; -import style from "../libs/style"; -import { StyleStore } from "../libs/stylestore"; -import { - initialStyleUrl, - loadStyleUrl, - removeStyleQuerystring, -} from "../libs/urlopen"; -import { formatLayerId } from "../util/format"; +import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata' +import {latest, validate} from '@maplibre/maplibre-gl-style-spec' +import style from '../libs/style' +import { initialStyleUrl, loadStyleUrl, removeStyleQuerystring } from '../libs/urlopen' +import { undoMessages, redoMessages } from '../libs/diffmessage' +import { StyleStore } from '../libs/stylestore' +import { ApiStyleStore } from '../libs/apistore' +import { RevisionStore } from '../libs/revisions' +import LayerWatcher from '../libs/layerwatcher' +import tokens from '../config/tokens.json' +import isEqual from 'lodash.isequal' +import Debug from '../libs/debug' +import {formatLayerId} from '../util/format'; // Buffer must be defined globally for @maplibre/maplibre-gl-style-spec validate() function to succeed. window.Buffer = buffer.Buffer; @@ -54,20 +47,18 @@ function setFetchAccessToken(url, mapStyle) { const matchesMaptiler = url.match(/\.maptiler\.com/); const matchesThunderforest = url.match(/\.thunderforest\.com/); if (matchesTilehosting || matchesMaptiler) { - const accessToken = style.getAccessToken("openmaptiles", mapStyle, { - allowFallback: true, - }); + const accessToken = style.getAccessToken("openmaptiles", mapStyle, {allowFallback: true}) if (accessToken) { - return url.replace("{key}", accessToken); + return url.replace('{key}', accessToken) } - } else if (matchesThunderforest) { - const accessToken = style.getAccessToken("thunderforest", mapStyle, { - allowFallback: true, - }); + } + else if (matchesThunderforest) { + const accessToken = style.getAccessToken("thunderforest", mapStyle, {allowFallback: true}) if (accessToken) { - return url.replace("{key}", accessToken); + return url.replace('{key}', accessToken) } - } else { + } + else { return url; } } @@ -79,135 +70,124 @@ function updateRootSpec(spec, fieldName, newValues) { ...spec.$root, [fieldName]: { ...spec.$root[fieldName], - values: newValues, - }, - }, - }; + values: newValues + } + } + } } export default class App extends React.Component { constructor(props) { - super(props); + super(props) autoBind(this); - this.revisionStore = new RevisionStore(); - const params = new URLSearchParams(window.location.search.substring(1)); - let port = params.get("localport"); - if ( - port == null && - window.location.port != 80 && - window.location.port != 443 - ) { - port = window.location.port; + this.revisionStore = new RevisionStore() + const params = new URLSearchParams(window.location.search.substring(1)) + let port = params.get("localport") + if (port == null && (window.location.port != 80 && window.location.port != 443)) { + port = window.location.port } this.styleStore = new ApiStyleStore({ - onLocalStyleChange: (mapStyle) => - this.onStyleChanged(mapStyle, { save: false }), + onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, {save: false}), port: port, - host: params.get("localhost"), - }); + host: params.get("localhost") + }) + const shortcuts = [ { key: "?", handler: () => { this.toggleModal("shortcuts"); - }, + } }, { key: "o", handler: () => { this.toggleModal("open"); - }, + } }, { key: "e", handler: () => { this.toggleModal("export"); - }, + } }, { key: "d", handler: () => { this.toggleModal("sources"); - }, + } }, { key: "s", handler: () => { this.toggleModal("settings"); - }, + } }, { key: "i", handler: () => { - this.setMapState(this.state.mapState === "map" ? "inspect" : "map"); - }, + this.setMapState( + this.state.mapState === "map" ? "inspect" : "map" + ); + } }, { key: "m", handler: () => { document.querySelector(".maplibregl-canvas").focus(); - }, + } }, { key: "!", handler: () => { this.toggleModal("debug"); - }, + } }, - ]; + ] document.body.addEventListener("keyup", (e) => { - if (e.key === "Escape") { + if(e.key === "Escape") { e.target.blur(); document.body.focus(); - } else if ( - this.state.isOpen.shortcuts || - document.activeElement === document.body - ) { + } + else if(this.state.isOpen.shortcuts || document.activeElement === document.body) { const shortcut = shortcuts.find((shortcut) => { - return shortcut.key === e.key; - }); + return (shortcut.key === e.key) + }) - if (shortcut) { + if(shortcut) { this.setModal("shortcuts", false); shortcut.handler(e); } } - }); + }) - const styleUrl = initialStyleUrl(); - if ( - styleUrl && - window.confirm( - "Load style from URL: " + styleUrl + " and discard current changes?" - ) - ) { - this.styleStore = new StyleStore(); - loadStyleUrl(styleUrl, (mapStyle) => this.onStyleChanged(mapStyle)); - removeStyleQuerystring(); + const styleUrl = initialStyleUrl() + if(styleUrl && window.confirm("Load style from URL: " + styleUrl + " and discard current changes?")) { + this.styleStore = new StyleStore() + loadStyleUrl(styleUrl, mapStyle => this.onStyleChanged(mapStyle)) + removeStyleQuerystring() } else { - if (styleUrl) { - removeStyleQuerystring(); + if(styleUrl) { + removeStyleQuerystring() } - this.styleStore.init((err) => { - if (err) { - console.log("Falling back to local storage for storing styles"); - this.styleStore = new StyleStore(); + this.styleStore.init(err => { + if(err) { + console.log('Falling back to local storage for storing styles') + this.styleStore = new StyleStore() } - this.styleStore.latestStyle((mapStyle) => - this.onStyleChanged(mapStyle, { initialLoad: true }) - ); + this.styleStore.latestStyle(mapStyle => this.onStyleChanged(mapStyle, {initialLoad: true})) - if (Debug.enabled()) { + if(Debug.enabled()) { Debug.set("maputnik", "styleStore", this.styleStore); Debug.set("maputnik", "revisionStore", this.revisionStore); } - }); + }) } - if (Debug.enabled()) { + if(Debug.enabled()) { Debug.set("maputnik", "revisionStore", this.revisionStore); Debug.set("maputnik", "styleStore", this.styleStore); } @@ -248,32 +228,35 @@ export default class App extends React.Component { openlayersDebugOptions: { debugToolbox: false, }, - }; + } this.layerWatcher = new LayerWatcher({ - onVectorLayersChange: (v) => this.setState({ vectorLayers: v }), - }); + onVectorLayersChange: v => this.setState({ vectorLayers: v }) + }) } handleKeyPress = (e) => { - if (navigator.platform.toUpperCase().indexOf("MAC") >= 0) { - if (e.metaKey && e.shiftKey && e.keyCode === 90) { + if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) { + if(e.metaKey && e.shiftKey && e.keyCode === 90) { e.preventDefault(); this.onRedo(e); - } else if (e.metaKey && e.keyCode === 90) { + } + else if(e.metaKey && e.keyCode === 90) { e.preventDefault(); this.onUndo(e); } - } else { - if (e.ctrlKey && e.keyCode === 90) { + } + else { + if(e.ctrlKey && e.keyCode === 90) { e.preventDefault(); this.onUndo(e); - } else if (e.ctrlKey && e.keyCode === 89) { + } + else if(e.ctrlKey && e.keyCode === 89) { e.preventDefault(); this.onRedo(e); } } - }; + } componentDidMount() { window.addEventListener("keydown", this.handleKeyPress); @@ -284,38 +267,33 @@ export default class App extends React.Component { } saveStyle(snapshotStyle) { - this.styleStore.save(snapshotStyle); + this.styleStore.save(snapshotStyle) } updateFonts(urlTemplate) { - const metadata = this.state.mapStyle.metadata || {}; - const accessToken = - metadata["maputnik:openmaptiles_access_token"] || tokens.openmaptiles; + const metadata = this.state.mapStyle.metadata || {} + const accessToken = metadata['maputnik:openmaptiles_access_token'] || tokens.openmaptiles - let glyphUrl = - typeof urlTemplate === "string" - ? urlTemplate.replace("{key}", accessToken) - : urlTemplate; - downloadGlyphsMetadata(glyphUrl, (fonts) => { - this.setState({ spec: updateRootSpec(this.state.spec, "glyphs", fonts) }); - }); + let glyphUrl = (typeof urlTemplate === 'string')? urlTemplate.replace('{key}', accessToken): urlTemplate; + downloadGlyphsMetadata(glyphUrl, fonts => { + this.setState({ spec: updateRootSpec(this.state.spec, 'glyphs', fonts)}) + }) } updateIcons(baseUrl) { - downloadSpriteMetadata(baseUrl, (icons) => { - this.setState({ spec: updateRootSpec(this.state.spec, "sprite", icons) }); - }); + downloadSpriteMetadata(baseUrl, icons => { + this.setState({ spec: updateRootSpec(this.state.spec, 'sprite', icons)}) + }) } onChangeMetadataProperty = (property, value) => { // If we're changing renderer reset the map state. if ( - property === "maputnik:renderer" && - value !== - get(this.state.mapStyle, ["metadata", "maputnik:renderer"], "mlgljs") + property === 'maputnik:renderer' && + value !== get(this.state.mapStyle, ['metadata', 'maputnik:renderer'], 'mlgljs') ) { this.setState({ - mapState: "map", + mapState: 'map' }); } @@ -323,13 +301,13 @@ export default class App extends React.Component { ...this.state.mapStyle, metadata: { ...this.state.mapStyle.metadata, - [property]: value, - }, - }; - this.onStyleChanged(changedStyle); - }; + [property]: value + } + } + this.onStyleChanged(changedStyle) + } - onStyleChanged = (newStyle, opts = {}) => { + onStyleChanged = (newStyle, opts={}) => { opts = { save: true, addRevision: true, @@ -360,11 +338,9 @@ export default class App extends React.Component { }); } - const mappedErrors = layerErrors.concat(errors).map((error) => { + const mappedErrors = layerErrors.concat(errors).map(error => { // Special case: Duplicate layer id - const dupMatch = error.message.match( - /layers\[(\d+)\]: (duplicate layer id "?(.*)"?, previously used)/ - ); + const dupMatch = error.message.match(/layers\[(\d+)\]: (duplicate layer id "?(.*)"?, previously used)/); if (dupMatch) { const [matchStr, index, message] = dupMatch; return { @@ -375,15 +351,13 @@ export default class App extends React.Component { index: parseInt(index, 10), key: "id", message, - }, - }, - }; + } + } + } } // Special case: Invalid source - const invalidSourceMatch = error.message.match( - /layers\[(\d+)\]: (source "(?:.*)" not found)/ - ); + const invalidSourceMatch = error.message.match(/layers\[(\d+)\]: (source "(?:.*)" not found)/); if (invalidSourceMatch) { const [matchStr, index, message] = invalidSourceMatch; return { @@ -394,17 +368,15 @@ export default class App extends React.Component { index: parseInt(index, 10), key: "source", message, - }, - }, - }; + } + } + } } - const layerMatch = error.message.match( - /layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/ - ); + const layerMatch = error.message.match(/layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/); if (layerMatch) { const [matchStr, index, group, property, message] = layerMatch; - const key = group && property ? [group, property].join(".") : property; + const key = (group && property) ? [group, property].join(".") : property; return { message: error.message, parsed: { @@ -412,11 +384,12 @@ export default class App extends React.Component { data: { index: parseInt(index, 10), key, - message, - }, - }, - }; - } else { + message + } + } + } + } + else { return { message: error.message, }; @@ -427,26 +400,27 @@ export default class App extends React.Component { if (errors.length > 0) { dirtyMapStyle = cloneDeep(newStyle); - errors.forEach((error) => { - const { message } = error; + errors.forEach(error => { + const {message} = error; if (message) { try { const objPath = message.split(":")[0]; // Errors can be deply nested for example 'layers[0].filter[1][1][0]' we only care upto the property 'layers[0].filter' const unsetPath = objPath.match(/^\S+?\[\d+\]\.[^\[]+/)[0]; unset(dirtyMapStyle, unsetPath); - } catch (err) { + } + catch (err) { console.warn(err); } } }); } - if (newStyle.glyphs !== this.state.mapStyle.glyphs) { - this.updateFonts(newStyle.glyphs); + if(newStyle.glyphs !== this.state.mapStyle.glyphs) { + this.updateFonts(newStyle.glyphs) } - if (newStyle.sprite !== this.state.mapStyle.sprite) { - this.updateIcons(newStyle.sprite); + if(newStyle.sprite !== this.state.mapStyle.sprite) { + this.updateIcons(newStyle.sprite) } if (opts.addRevision) { @@ -456,266 +430,251 @@ export default class App extends React.Component { this.saveStyle(newStyle); } - this.setState( - { - mapStyle: newStyle, - dirtyMapStyle: dirtyMapStyle, - errors: mappedErrors, - }, - () => { - this.fetchSources(); - this.setStateInUrl(); - } - ); - }; + this.setState({ + mapStyle: newStyle, + dirtyMapStyle: dirtyMapStyle, + errors: mappedErrors, + }, () => { + this.fetchSources(); + this.setStateInUrl(); + }) + + } onUndo = () => { - const activeStyle = this.revisionStore.undo(); + const activeStyle = this.revisionStore.undo() - const messages = undoMessages(this.state.mapStyle, activeStyle); - this.onStyleChanged(activeStyle, { addRevision: false }); + const messages = undoMessages(this.state.mapStyle, activeStyle) + this.onStyleChanged(activeStyle, {addRevision: false}); this.setState({ infos: messages, - }); - }; + }) + } onRedo = () => { - const activeStyle = this.revisionStore.redo(); - const messages = redoMessages(this.state.mapStyle, activeStyle); - this.onStyleChanged(activeStyle, { addRevision: false }); + const activeStyle = this.revisionStore.redo() + const messages = redoMessages(this.state.mapStyle, activeStyle) + this.onStyleChanged(activeStyle, {addRevision: false}); this.setState({ infos: messages, - }); - }; + }) + } onMoveLayer = (move) => { let { oldIndex, newIndex } = move; let layers = this.state.mapStyle.layers; - oldIndex = clamp(oldIndex, 0, layers.length - 1); - newIndex = clamp(newIndex, 0, layers.length - 1); - if (oldIndex === newIndex) return; + oldIndex = clamp(oldIndex, 0, layers.length-1); + newIndex = clamp(newIndex, 0, layers.length-1); + if(oldIndex === newIndex) return; if (oldIndex === this.state.selectedLayerIndex) { this.setState({ - selectedLayerIndex: newIndex, + selectedLayerIndex: newIndex }); } layers = layers.slice(0); arrayMoveMutable(layers, oldIndex, newIndex); this.onLayersChange(layers); - }; + } onLayersChange = (changedLayers) => { const changedStyle = { ...this.state.mapStyle, - layers: changedLayers, - }; - this.onStyleChanged(changedStyle); - }; + layers: changedLayers + } + this.onStyleChanged(changedStyle) + } onLayerDestroy = (index) => { let layers = this.state.mapStyle.layers; const remainingLayers = layers.slice(0); remainingLayers.splice(index, 1); this.onLayersChange(remainingLayers); - }; + } onLayerCopy = (index) => { let layers = this.state.mapStyle.layers; - const changedLayers = layers.slice(0); + const changedLayers = layers.slice(0) - const clonedLayer = cloneDeep(changedLayers[index]); - clonedLayer.id = clonedLayer.id + "-copy"; - changedLayers.splice(index, 0, clonedLayer); - this.onLayersChange(changedLayers); - }; + const clonedLayer = cloneDeep(changedLayers[index]) + clonedLayer.id = clonedLayer.id + "-copy" + changedLayers.splice(index, 0, clonedLayer) + this.onLayersChange(changedLayers) + } onLayerVisibilityToggle = (index) => { let layers = this.state.mapStyle.layers; - const changedLayers = layers.slice(0); + const changedLayers = layers.slice(0) - const layer = { ...changedLayers[index] }; - const changedLayout = "layout" in layer ? { ...layer.layout } : {}; - changedLayout.visibility = - changedLayout.visibility === "none" ? "visible" : "none"; + const layer = { ...changedLayers[index] } + const changedLayout = 'layout' in layer ? {...layer.layout} : {} + changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none' + + layer.layout = changedLayout + changedLayers[index] = layer + this.onLayersChange(changedLayers) + } - layer.layout = changedLayout; - changedLayers[index] = layer; - this.onLayersChange(changedLayers); - }; onLayerIdChange = (index, oldId, newId) => { - const changedLayers = this.state.mapStyle.layers.slice(0); + const changedLayers = this.state.mapStyle.layers.slice(0) changedLayers[index] = { ...changedLayers[index], - id: newId, - }; + id: newId + } - this.onLayersChange(changedLayers); - }; + this.onLayersChange(changedLayers) + } onLayerChanged = (index, layer) => { - const changedLayers = this.state.mapStyle.layers.slice(0); - changedLayers[index] = layer; + const changedLayers = this.state.mapStyle.layers.slice(0) + changedLayers[index] = layer - this.onLayersChange(changedLayers); - }; + this.onLayersChange(changedLayers) + } setMapState = (newState) => { - this.setState( - { - mapState: newState, - }, - this.setStateInUrl - ); - }; + this.setState({ + mapState: newState + }, this.setStateInUrl); + } setDefaultValues = (styleObj) => { - const metadata = styleObj.metadata || {}; - if (metadata["maputnik:renderer"] === undefined) { + const metadata = styleObj.metadata || {} + if(metadata['maputnik:renderer'] === undefined) { const changedStyle = { ...styleObj, metadata: { ...styleObj.metadata, - "maputnik:renderer": "mlgljs", - }, - }; - return changedStyle; + 'maputnik:renderer': 'mlgljs' + } + } + return changedStyle } else { - return styleObj; + return styleObj } - }; + } openStyle = (styleObj) => { - styleObj = this.setDefaultValues(styleObj); - this.onStyleChanged(styleObj); - }; + styleObj = this.setDefaultValues(styleObj) + this.onStyleChanged(styleObj) + } fetchSources() { const sourceList = {}; - for (let [key, val] of Object.entries(this.state.mapStyle.sources)) { - if ( + for(let [key, val] of Object.entries(this.state.mapStyle.sources)) { + if( !this.state.sources.hasOwnProperty(key) && val.type === "vector" && val.hasOwnProperty("url") ) { sourceList[key] = { type: val.type, - layers: [], + layers: [] }; let url = val.url; try { - url = setFetchAccessToken(url, this.state.mapStyle); - } catch (err) { + url = setFetchAccessToken(url, this.state.mapStyle) + } catch(err) { console.warn("Failed to setFetchAccessToken: ", err); } fetch(url, { - mode: "cors", + mode: 'cors', }) - .then((response) => response.json()) - .then((json) => { - if (!json.hasOwnProperty("vector_layers")) { - return; - } + .then(response => response.json()) + .then(json => { - // Create new objects before setState - const sources = Object.assign( - {}, - { - [key]: this.state.sources[key], - } - ); + if(!json.hasOwnProperty("vector_layers")) { + return; + } - for (let layer of json.vector_layers) { - sources[key].layers.push(layer.id); - } - - console.debug("Updating source: " + key); - this.setState({ - sources: sources, - }); - }) - .catch((err) => { - console.error("Failed to process sources for '%s'", url, err); + // Create new objects before setState + const sources = Object.assign({}, { + [key]: this.state.sources[key], }); - } else { - sourceList[key] = - this.state.sources[key] || this.state.mapStyle.sources[key]; + + for(let layer of json.vector_layers) { + sources[key].layers.push(layer.id) + } + + console.debug("Updating source: "+key); + this.setState({ + sources: sources + }); + }) + .catch(err => { + console.error("Failed to process sources for '%s'", url, err); + }); + } + else { + sourceList[key] = this.state.sources[key] || this.state.mapStyle.sources[key]; } } - if (!isEqual(this.state.sources, sourceList)) { + if(!isEqual(this.state.sources, sourceList)) { console.debug("Setting sources"); this.setState({ - sources: sourceList, - }); + sources: sourceList + }) } } - _getRenderer() { + _getRenderer () { const metadata = this.state.mapStyle.metadata || {}; - return metadata["maputnik:renderer"] || "mlgljs"; + return metadata['maputnik:renderer'] || 'mlgljs'; } onMapChange = (mapView) => { this.setState({ mapView, }); - }; + } mapRenderer() { - const { mapStyle, dirtyMapStyle } = this.state; + const {mapStyle, dirtyMapStyle} = this.state; const metadata = this.state.mapStyle.metadata || {}; const mapProps = { - mapStyle: dirtyMapStyle || mapStyle, + mapStyle: (dirtyMapStyle || mapStyle), replaceAccessTokens: (mapStyle) => { return style.replaceAccessTokens(mapStyle, { - allowFallback: true, + allowFallback: true }); }, onDataChange: (e) => { - this.layerWatcher.analyzeMap(e.map); + this.layerWatcher.analyzeMap(e.map) this.fetchSources(); }, - }; + } const renderer = this._getRenderer(); let mapElement; // Check if OL code has been loaded? - if (renderer === "ol") { - mapElement = ( - - ); + if(renderer === 'ol') { + mapElement = } else { - mapElement = ( - - ); + mapElement = } let filterName; - if (this.state.mapState.match(/^filter-/)) { + if(this.state.mapState.match(/^filter-/)) { filterName = this.state.mapState.replace(/^filter-/, ""); } const elementStyle = {}; @@ -723,42 +682,38 @@ export default class App extends React.Component { elementStyle.filter = `url('#${filterName}')`; } - return ( -
- {mapElement} -
- ); + return
+ {mapElement} +
} setStateInUrl = () => { - const { mapState, mapStyle, isOpen } = this.state; - const { selectedLayerIndex } = this.state; + const {mapState, mapStyle, isOpen} = this.state; + const {selectedLayerIndex} = this.state; const url = new URL(location.href); const hashVal = hash(JSON.stringify(mapStyle)); url.searchParams.set("layer", `${hashVal}~${selectedLayerIndex}`); const openModals = Object.entries(isOpen) - .map(([key, val]) => (val === true ? key : null)) - .filter((val) => val !== null); + .map(([key, val]) => (val === true ? key : null)) + .filter(val => val !== null); if (openModals.length > 0) { url.searchParams.set("modal", openModals.join(",")); - } else { + } + else { url.searchParams.delete("modal"); } if (mapState === "map") { url.searchParams.delete("view"); - } else if (mapState === "inspect") { + } + else if (mapState === "inspect") { url.searchParams.set("view", "inspect"); } - history.replaceState({ selectedLayerIndex }, "Maputnik", url.href); - }; + history.replaceState({selectedLayerIndex}, "Maputnik", url.href); + } getInitialStateFromUrl = (mapStyle) => { const url = new URL(location.href); @@ -766,7 +721,7 @@ export default class App extends React.Component { if (modalParam && modalParam !== "") { const modals = modalParam.split(","); const modalObj = {}; - modals.forEach((modalName) => { + modals.forEach(modalName => { modalObj[modalName] = true; }); @@ -774,7 +729,7 @@ export default class App extends React.Component { isOpen: { ...this.state.isOpen, ...modalObj, - }, + } }); } @@ -805,36 +760,31 @@ export default class App extends React.Component { selectedLayerOriginalId: mapStyle.layers[selectedLayerIndex].id, }); } - } catch (err) { + } + catch (err) { console.warn(err); } } - }; + } onLayerSelect = (index) => { - this.setState( - { - selectedLayerIndex: index, - selectedLayerOriginalId: this.state.mapStyle.layers[index].id, - }, - this.setStateInUrl - ); - }; + this.setState({ + selectedLayerIndex: index, + selectedLayerOriginalId: this.state.mapStyle.layers[index].id, + }, this.setStateInUrl); + } setModal(modalName, value) { - if (modalName === "survey" && value === false) { - localStorage.setItem("survey", ""); + if(modalName === 'survey' && value === false) { + localStorage.setItem('survey', ''); } - this.setState( - { - isOpen: { - ...this.state.isOpen, - [modalName]: value, - }, - }, - this.setStateInUrl - ); + this.setState({ + isOpen: { + ...this.state.isOpen, + [modalName]: value + } + }, this.setStateInUrl) } toggleModal(modalName) { @@ -846,147 +796,131 @@ export default class App extends React.Component { openlayersDebugOptions: { ...this.state.openlayersDebugOptions, [key]: value, - }, + } }); - }; + } onChangeMaplibreGlDebug = (key, value) => { this.setState({ maplibreGlDebugOptions: { ...this.state.maplibreGlDebugOptions, [key]: value, - }, + } }); - }; + } render() { - const layers = this.state.mapStyle.layers || []; - const selectedLayer = - layers.length > 0 ? layers[this.state.selectedLayerIndex] : null; - const metadata = this.state.mapStyle.metadata || {}; + const layers = this.state.mapStyle.layers || [] + const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null + const metadata = this.state.mapStyle.metadata || {} - const toolbar = ( - + + const layerList = + + const layerEditor = selectedLayer ? : null + + const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? : null + + + const modals =
+ + this.shortcutEl = el} + isOpen={this.state.isOpen.shortcuts} + onOpenToggle={this.toggleModal.bind(this, 'shortcuts')} + /> + - ); - - const layerList = ( - - ); - - const layerEditor = selectedLayer ? ( - - ) : null; - - const bottomPanel = - this.state.errors.length + this.state.infos.length > 0 ? ( - - ) : null; - - const modals = ( -
- - (this.shortcutEl = el)} - isOpen={this.state.isOpen.shortcuts} - onOpenToggle={this.toggleModal.bind(this, "shortcuts")} - /> - - - - - -
- ); - - return ( - - ); + +
+ + return } } diff --git a/src/components/AppToolbar.jsx b/src/components/AppToolbar.jsx index 6911889c..13d62cae 100644 --- a/src/components/AppToolbar.jsx +++ b/src/components/AppToolbar.jsx @@ -1,33 +1,24 @@ -import classnames from "classnames"; -import { detect } from "detect-browser"; -import PropTypes from "prop-types"; -import React from "react"; +import React from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import {detect} from 'detect-browser'; -import { - MdAssignmentTurnedIn, - MdFileDownload, - MdFindInPage, - MdHelpOutline, - MdLayers, - MdOpenInBrowser, - MdSettings, -} from "react-icons/md"; +import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage, MdAssignmentTurnedIn} from 'react-icons/md' +import pkgJson from '../../package.json' -import logoImage from "maputnik-design/logos/logo-color.svg"; -import pkgJson from "../../package.json"; // This is required because of , there isn't another way to detect support that I'm aware of. const browser = detect(); -const colorAccessibilityFiltersEnabled = - ["chrome", "firefox"].indexOf(browser.name) > -1; +const colorAccessibilityFiltersEnabled = ['chrome', 'firefox'].indexOf(browser.name) > -1; + class IconText extends React.Component { static propTypes = { children: PropTypes.node, - }; + } render() { - return {this.props.children}; + return {this.props.children} } } @@ -37,19 +28,17 @@ class ToolbarLink extends React.Component { children: PropTypes.node, href: PropTypes.string, onToggleModal: PropTypes.func, - }; + } render() { - return ( - - {this.props.children} - - ); + return + {this.props.children} + } } @@ -58,41 +47,36 @@ class ToolbarLinkHighlighted extends React.Component { className: PropTypes.string, children: PropTypes.node, href: PropTypes.string, - onToggleModal: PropTypes.func, - }; + onToggleModal: PropTypes.func + } render() { - return ( - - - {this.props.children} - - - ); + return + + {this.props.children} + + } } class ToolbarSelect extends React.Component { static propTypes = { children: PropTypes.node, - wdKey: PropTypes.string, - }; + wdKey: PropTypes.string + } render() { - return ( -
- {this.props.children} -
- ); + return
+ {this.props.children} +
} } @@ -100,19 +84,17 @@ class ToolbarAction extends React.Component { static propTypes = { children: PropTypes.node, onClick: PropTypes.func, - wdKey: PropTypes.string, - }; + wdKey: PropTypes.string + } render() { - return ( - - ); + return } } @@ -130,7 +112,7 @@ export default class AppToolbar extends React.Component { onSetMapState: PropTypes.func, mapState: PropTypes.string, renderer: PropTypes.string, - }; + } state = { isOpen: { @@ -139,8 +121,8 @@ export default class AppToolbar extends React.Component { open: false, add: false, export: false, - }, - }; + } + } handleSelection(val) { this.props.onSetMapState(val); @@ -149,11 +131,12 @@ export default class AppToolbar extends React.Component { onSkip = (target) => { if (target === "map") { document.querySelector(".maplibregl-canvas").focus(); - } else { - const el = document.querySelector("#skip-target-" + target); + } + else { + const el = document.querySelector("#skip-target-"+target); el.focus(); } - }; + } render() { const views = [ @@ -166,7 +149,7 @@ export default class AppToolbar extends React.Component { id: "inspect", group: "general", title: "Inspect", - disabled: this.props.renderer === "ol", + disabled: this.props.renderer === 'ol', }, { id: "filter-deuteranopia", @@ -198,137 +181,103 @@ export default class AppToolbar extends React.Component { return view.id === this.props.mapState; }); - return ( - - ); +
+ + + Open + + + + Export + + + + Data Sources + + + + Style Settings + + + + + + + + + + Help + + + + Take the Maputnik Survey + +
+ + } } diff --git a/src/components/LayerEditor.jsx b/src/components/LayerEditor.jsx index 5b0ce5b5..075681ff 100644 --- a/src/components/LayerEditor.jsx +++ b/src/components/LayerEditor.jsx @@ -1,46 +1,47 @@ -import PropTypes from "prop-types"; -import React from "react"; -import { Button, Menu, MenuItem, Wrapper } from "react-aria-menubutton"; +import React from 'react' +import PropTypes from 'prop-types' +import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton' -import { Accordion } from "react-accessible-accordion"; -import FieldComment from "./FieldComment"; -import FieldId from "./FieldId"; -import FieldJson from "./FieldJson"; -import FieldMaxZoom from "./FieldMaxZoom"; -import FieldMinZoom from "./FieldMinZoom"; -import FieldSource from "./FieldSource"; -import FieldSourceLayer from "./FieldSourceLayer"; -import FieldType from "./FieldType"; -import FilterEditor from "./FilterEditor"; -import LayerEditorGroup from "./LayerEditorGroup"; -import PropertyGroup from "./PropertyGroup"; +import FieldJson from './FieldJson' +import FilterEditor from './FilterEditor' +import PropertyGroup from './PropertyGroup' +import LayerEditorGroup from './LayerEditorGroup' +import FieldType from './FieldType' +import FieldId from './FieldId' +import FieldMinZoom from './FieldMinZoom' +import FieldMaxZoom from './FieldMaxZoom' +import FieldComment from './FieldComment' +import FieldSource from './FieldSource' +import FieldSourceLayer from './FieldSourceLayer' +import {Accordion} from 'react-accessible-accordion'; -import { MdMoreVert } from "react-icons/md"; +import {MdMoreVert} from 'react-icons/md' -import layout from "../config/layout.json"; -import { changeProperty, changeType } from "../libs/layer"; -import { formatLayerId } from "../util/format"; +import { changeType, changeProperty } from '../libs/layer' +import layout from '../config/layout.json' +import {formatLayerId} from '../util/format'; -function getLayoutForType(type) { + +function getLayoutForType (type) { return layout[type] ? layout[type] : layout.invalid; } function layoutGroups(layerType) { const layerGroup = { - title: "Layer", - type: "layer", - }; + title: 'Layer', + type: 'layer' + } const filterGroup = { - title: "Filter", - type: "filter", - }; + title: 'Filter', + type: 'filter' + } const editorGroup = { - title: "JSON Editor", - type: "jsoneditor", - }; + title: 'JSON Editor', + type: 'jsoneditor' + } return [layerGroup, filterGroup] .concat(getLayoutForType(layerType).groups) - .concat([editorGroup]); + .concat([editorGroup]) } /** Layer editor supporting multiple types of layers. */ @@ -60,320 +61,277 @@ export default class LayerEditor extends React.Component { isLastLayer: PropTypes.bool, layerIndex: PropTypes.number, errors: PropTypes.array, - }; + } static defaultProps = { onLayerChanged: () => {}, onLayerIdChange: () => {}, onLayerDestroyed: () => {}, - }; + } static childContextTypes = { - reactIconBase: PropTypes.object, - }; + reactIconBase: PropTypes.object + } constructor(props) { - super(props); + super(props) //TODO: Clean this up and refactor into function - const editorGroups = {}; - layoutGroups(this.props.layer.type).forEach((group) => { - editorGroups[group.title] = true; - }); + const editorGroups = {} + layoutGroups(this.props.layer.type).forEach(group => { + editorGroups[group.title] = true + }) - this.state = { editorGroups }; + this.state = { editorGroups } } static getDerivedStateFromProps(props, state) { - const additionalGroups = { ...state.editorGroups }; + const additionalGroups = { ...state.editorGroups } - getLayoutForType(props.layer.type).groups.forEach((group) => { - if (!(group.title in additionalGroups)) { - additionalGroups[group.title] = true; + getLayoutForType(props.layer.type).groups.forEach(group => { + if(!(group.title in additionalGroups)) { + additionalGroups[group.title] = true } - }); + }) return { - editorGroups: additionalGroups, + editorGroups: additionalGroups }; } - getChildContext() { + getChildContext () { return { reactIconBase: { size: 14, - color: "#8e8e8e", - }, - }; + color: '#8e8e8e', + } + } } changeProperty(group, property, newValue) { this.props.onLayerChanged( this.props.layerIndex, changeProperty(this.props.layer, group, property, newValue) - ); + ) } onGroupToggle(groupTitle, active) { const changedActiveGroups = { ...this.state.editorGroups, [groupTitle]: active, - }; + } this.setState({ - editorGroups: changedActiveGroups, - }); + editorGroups: changedActiveGroups + }) } renderGroupType(type, fields) { - let comment = ""; - if (this.props.layer.metadata) { - comment = this.props.layer.metadata["maputnik:comment"]; + let comment = "" + if(this.props.layer.metadata) { + comment = this.props.layer.metadata['maputnik:comment'] } - const { errors, layerIndex } = this.props; + const {errors, layerIndex} = this.props; const errorData = {}; - errors.forEach((error) => { + errors.forEach(error => { if ( error.parsed && error.parsed.type === "layer" && error.parsed.data.index == layerIndex ) { errorData[error.parsed.data.key] = { - message: error.parsed.data.message, + message: error.parsed.data.message }; } - }); + }) let sourceLayerIds; - if (this.props.sources.hasOwnProperty(this.props.layer.source)) { + if(this.props.sources.hasOwnProperty(this.props.layer.source)) { sourceLayerIds = this.props.sources[this.props.layer.source].layers; } - switch (type) { - case "layer": - return ( -
- - this.props.onLayerIdChange( - this.props.layerIndex, - this.props.layer.id, - newId - ) - } - /> - - this.props.onLayerChanged( - this.props.layerIndex, - changeType(this.props.layer, newType) - ) - } - /> - {this.props.layer.type !== "background" && ( - this.changeProperty(null, "source", v)} - /> - )} - {["background", "raster", "hillshade", "heatmap"].indexOf( - this.props.layer.type - ) < 0 && ( - this.changeProperty(null, "source-layer", v)} - /> - )} - this.changeProperty(null, "minzoom", v)} - /> - this.changeProperty(null, "maxzoom", v)} - /> - - this.changeProperty( - "metadata", - "maputnik:comment", - v == "" ? undefined : v - ) - } - /> -
- ); - case "filter": - return ( -
-
- this.changeProperty(null, "filter", f)} - /> -
-
- ); - case "properties": - return ( - + this.props.onLayerIdChange(this.props.layerIndex, this.props.layer.id, newId)} + /> + this.props.onLayerChanged( + this.props.layerIndex, + changeType(this.props.layer, newType) + )} + /> + {this.props.layer.type !== 'background' && this.changeProperty(null, 'source', v)} + /> + } + {['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.props.layer.type) < 0 && + this.changeProperty(null, 'source-layer', v)} + /> + } + this.changeProperty(null, 'minzoom', v)} + /> + this.changeProperty(null, 'maxzoom', v)} + /> + this.changeProperty('metadata', 'maputnik:comment', v == "" ? undefined : v)} + /> + + case 'filter': return
+
+ this.changeProperty(null, 'filter', f)} /> - ); - case "jsoneditor": - return ( - { - this.props.onLayerChanged(this.props.layerIndex, layer); - }} - /> - ); +
+
+ case 'properties': + return + case 'jsoneditor': + return { + this.props.onLayerChanged( + this.props.layerIndex, + layer + ); + }} + /> } } moveLayer(offset) { this.props.onMoveLayer({ oldIndex: this.props.layerIndex, - newIndex: this.props.layerIndex + offset, - }); + newIndex: this.props.layerIndex+offset + }) } render() { const groupIds = []; - const layerType = this.props.layer.type; - const groups = layoutGroups(layerType) - .filter((group) => { - return !(layerType === "background" && group.type === "source"); - }) - .map((group) => { - const groupId = group.title.replace(/ /g, "_"); - groupIds.push(groupId); - return ( - - {this.renderGroupType(group.type, group.fields)} - - ); - }); + const layerType = this.props.layer.type + const groups = layoutGroups(layerType).filter(group => { + return !(layerType === 'background' && group.type === 'source') + }).map(group => { + const groupId = group.title.replace(/ /g, "_"); + groupIds.push(groupId); + return + {this.renderGroupType(group.type, group.fields)} + + }) - const layout = this.props.layer.layout || {}; + const layout = this.props.layer.layout || {} const items = { delete: { text: "Delete", - handler: () => this.props.onLayerDestroy(this.props.layerIndex), + handler: () => this.props.onLayerDestroy(this.props.layerIndex) }, duplicate: { text: "Duplicate", - handler: () => this.props.onLayerCopy(this.props.layerIndex), + handler: () => this.props.onLayerCopy(this.props.layerIndex) }, hide: { - text: layout.visibility === "none" ? "Show" : "Hide", - handler: () => - this.props.onLayerVisibilityToggle(this.props.layerIndex), + text: (layout.visibility === "none") ? "Show" : "Hide", + handler: () => this.props.onLayerVisibilityToggle(this.props.layerIndex) }, moveLayerUp: { text: "Move layer up", // Not actually used... disabled: this.props.isFirstLayer, - handler: () => this.moveLayer(-1), + handler: () => this.moveLayer(-1) }, moveLayerDown: { text: "Move layer down", // Not actually used... disabled: this.props.isLastLayer, - handler: () => this.moveLayer(+1), - }, - }; + handler: () => this.moveLayer(+1) + } + } function handleSelection(id, event) { event.stopPropagation; items[id].handler(); } - return ( -
-
-
-

- Layer: {formatLayerId(this.props.layer.id)} -

-
- - - -
    - {Object.keys(items).map((id, idx) => { - const item = items[id]; - return ( -
  • - - {item.text} - -
  • - ); - })} -
-
-
-
+ return
+
+
+

+ Layer: {formatLayerId(this.props.layer.id)} +

+
+ + + +
    + {Object.keys(items).map((id, idx) => { + const item = items[id]; + return
  • + + {item.text} + +
  • + })} +
+
+
-
- - {groups} - -
- ); +
+ +
+ + {groups} + +
} } diff --git a/src/components/LayerList.jsx b/src/components/LayerList.jsx index 14d9b971..69dbacf8 100644 --- a/src/components/LayerList.jsx +++ b/src/components/LayerList.jsx @@ -1,13 +1,13 @@ -import classnames from "classnames"; -import lodash from "lodash"; -import PropTypes from "prop-types"; -import React from "react"; +import React from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import lodash from 'lodash'; -import LayerListGroup from "./LayerListGroup"; -import LayerListItem from "./LayerListItem"; -import ModalAdd from "./ModalAdd"; +import LayerListGroup from './LayerListGroup' +import LayerListItem from './LayerListItem' +import ModalAdd from './ModalAdd' -import { SortableContainer } from "react-sortable-hoc"; +import {SortableContainer} from 'react-sortable-hoc'; const layerListPropTypes = { layers: PropTypes.array.isRequired, @@ -15,34 +15,34 @@ const layerListPropTypes = { onLayersChange: PropTypes.func.isRequired, onLayerSelect: PropTypes.func, sources: PropTypes.object.isRequired, -}; +} function layerPrefix(name) { - return name.replace(" ", "-").replace("_", "-").split("-")[0]; + return name.replace(' ', '-').replace('_', '-').split('-')[0] } function findClosestCommonPrefix(layers, idx) { - const currentLayerPrefix = layerPrefix(layers[idx].id); - let closestIdx = idx; + const currentLayerPrefix = layerPrefix(layers[idx].id) + let closestIdx = idx for (let i = idx; i > 0; i--) { - const previousLayerPrefix = layerPrefix(layers[i - 1].id); - if (previousLayerPrefix === currentLayerPrefix) { - closestIdx = i - 1; + const previousLayerPrefix = layerPrefix(layers[i-1].id) + if(previousLayerPrefix === currentLayerPrefix) { + closestIdx = i - 1 } else { - return closestIdx; + return closestIdx } } - return closestIdx; + return closestIdx } let UID = 0; // List of collapsible layer editors class LayerListContainer extends React.Component { - static propTypes = { ...layerListPropTypes }; + static propTypes = {...layerListPropTypes} static defaultProps = { onLayerSelect: () => {}, - }; + } constructor(props) { super(props); @@ -56,8 +56,8 @@ class LayerListContainer extends React.Component { }, isOpen: { add: false, - }, - }; + } + } } toggleModal(modalName) { @@ -68,82 +68,79 @@ class LayerListContainer extends React.Component { }, isOpen: { ...this.state.isOpen, - [modalName]: !this.state.isOpen[modalName], - }, - }); + [modalName]: !this.state.isOpen[modalName] + } + }) } toggleLayers = () => { - let idx = 0; + let idx=0 - let newGroups = []; + let newGroups=[] + + this.groupedLayers().forEach(layers => { + const groupPrefix = layerPrefix(layers[0].id) + const lookupKey = [groupPrefix, idx].join('-') - this.groupedLayers().forEach((layers) => { - const groupPrefix = layerPrefix(layers[0].id); - const lookupKey = [groupPrefix, idx].join("-"); if (layers.length > 1) { - newGroups[lookupKey] = this.state.areAllGroupsExpanded; + newGroups[lookupKey] = this.state.areAllGroupsExpanded } layers.forEach((layer) => { - idx += 1; - }); + idx += 1 + }) }); this.setState({ collapsedGroups: newGroups, - areAllGroupsExpanded: !this.state.areAllGroupsExpanded, - }); - }; + areAllGroupsExpanded: !this.state.areAllGroupsExpanded + }) + } groupedLayers() { - const groups = []; + const groups = [] const layerIdCount = new Map(); for (let i = 0; i < this.props.layers.length; i++) { const origLayer = this.props.layers[i]; - const previousLayer = this.props.layers[i - 1]; - layerIdCount.set( - origLayer.id, + const previousLayer = this.props.layers[i-1] + layerIdCount.set(origLayer.id, layerIdCount.has(origLayer.id) ? layerIdCount.get(origLayer.id) + 1 : 0 ); const layer = { ...origLayer, key: `layers-list-${origLayer.id}-${layerIdCount.get(origLayer.id)}`, - }; - if ( - previousLayer && - layerPrefix(previousLayer.id) == layerPrefix(layer.id) - ) { - const lastGroup = groups[groups.length - 1]; - lastGroup.push(layer); + } + if(previousLayer && layerPrefix(previousLayer.id) == layerPrefix(layer.id)) { + const lastGroup = groups[groups.length - 1] + lastGroup.push(layer) } else { - groups.push([layer]); + groups.push([layer]) } } - return groups; + return groups } toggleLayerGroup(groupPrefix, idx) { - const lookupKey = [groupPrefix, idx].join("-"); - const newGroups = { ...this.state.collapsedGroups }; - if (lookupKey in this.state.collapsedGroups) { - newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey]; + const lookupKey = [groupPrefix, idx].join('-') + const newGroups = { ...this.state.collapsedGroups } + if(lookupKey in this.state.collapsedGroups) { + newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey] } else { - newGroups[lookupKey] = false; + newGroups[lookupKey] = false } this.setState({ - collapsedGroups: newGroups, - }); + collapsedGroups: newGroups + }) } isCollapsed(groupPrefix, idx) { - const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join("-")]; - return collapsed === undefined ? true : collapsed; + const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join('-')] + return collapsed === undefined ? true : collapsed } - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate (nextProps, nextState) { // Always update on state change if (this.state !== nextState) { return true; @@ -151,28 +148,28 @@ class LayerListContainer extends React.Component { // This component tree only requires id and visibility from the layers // objects - function getRequiredProps(layer) { + function getRequiredProps (layer) { const out = { id: layer.id, }; if (layer.layout) { out.layout = { - visibility: layer.layout.visibility, + visibility: layer.layout.visibility }; } return out; } const layersEqual = lodash.isEqual( nextProps.layers.map(getRequiredProps), - this.props.layers.map(getRequiredProps) + this.props.layers.map(getRequiredProps), ); - function withoutLayers(props) { + function withoutLayers (props) { const out = { - ...props, + ...props }; - delete out["layers"]; + delete out['layers']; return out; } @@ -187,16 +184,16 @@ class LayerListContainer extends React.Component { return propsChanged; } - componentDidUpdate(prevProps) { + componentDidUpdate (prevProps) { if (prevProps.selectedLayerIndex !== this.props.selectedLayerIndex) { const selectedItemNode = this.selectedItemRef.current; if (selectedItemNode && selectedItemNode.node) { const target = selectedItemNode.node; const options = { root: this.scrollContainerRef.current, - threshold: 1.0, - }; - const observer = new IntersectionObserver((entries) => { + threshold: 1.0 + } + const observer = new IntersectionObserver(entries => { observer.unobserve(target); if (entries.length > 0 && entries[0].intersectionRatio < 1) { target.scrollIntoView(); @@ -209,32 +206,28 @@ class LayerListContainer extends React.Component { } render() { - const listItems = []; - let idx = 0; + + const listItems = [] + let idx = 0 const layersByGroup = this.groupedLayers(); - layersByGroup.forEach((layers) => { - const groupPrefix = layerPrefix(layers[0].id); - if (layers.length > 1) { - const grp = ( - l.key).join(" ")} - key={`group-${groupPrefix}-${idx}`} - title={groupPrefix} - isActive={ - !this.isCollapsed(groupPrefix, idx) || - idx === this.props.selectedLayerIndex - } - onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)} - /> - ); - listItems.push(grp); + layersByGroup.forEach(layers => { + const groupPrefix = layerPrefix(layers[0].id) + if(layers.length > 1) { + const grp = l.key).join(" ")} + key={`group-${groupPrefix}-${idx}`} + title={groupPrefix} + isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex} + onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)} + /> + listItems.push(grp) } layers.forEach((layer, idxInGroup) => { - const groupIdx = findClosestCommonPrefix(this.props.layers, idx); + const groupIdx = findClosestCommonPrefix(this.props.layers, idx) - const layerError = this.props.errors.find((error) => { + const layerError = this.props.errors.find(error => { return ( error.parsed && error.parsed.type === "layer" && @@ -247,107 +240,94 @@ class LayerListContainer extends React.Component { additionalProps.ref = this.selectedItemRef; } - const listItem = ( - 1 && - this.isCollapsed(groupPrefix, groupIdx) && - idx !== this.props.selectedLayerIndex, - "maputnik-layer-list-item-group-last": - idxInGroup == layers.length - 1 && layers.length > 1, - "maputnik-layer-list-item--error": !!layerError, - })} - index={idx} - key={layer.key} - id={layer.key} - layerId={layer.id} - layerIndex={idx} - layerType={layer.type} - visibility={(layer.layout || {}).visibility} - isSelected={idx === this.props.selectedLayerIndex} - onLayerSelect={this.props.onLayerSelect} - onLayerDestroy={this.props.onLayerDestroy.bind(this)} - onLayerCopy={this.props.onLayerCopy.bind(this)} - onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind( - this - )} - {...additionalProps} - /> - ); - listItems.push(listItem); - idx += 1; - }); - }); + const listItem = 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex, + 'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1, + 'maputnik-layer-list-item--error': !!layerError + })} + index={idx} + key={layer.key} + id={layer.key} + layerId={layer.id} + layerIndex={idx} + layerType={layer.type} + visibility={(layer.layout || {}).visibility} + isSelected={idx === this.props.selectedLayerIndex} + onLayerSelect={this.props.onLayerSelect} + onLayerDestroy={this.props.onLayerDestroy.bind(this)} + onLayerCopy={this.props.onLayerCopy.bind(this)} + onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)} + {...additionalProps} + /> + listItems.push(listItem) + idx += 1 + }) + }) - return ( -
- + -
- Layers - -
-
- -
+ /> +
+ Layers + +
+
+
-
-
- -
-
-
-
-
    {listItems}
-
- ); +
+
+ +
+
+ +
+
    + {listItems} +
+
+ } } -const LayerListContainerSortable = SortableContainer((props) => ( - -)); +const LayerListContainerSortable = SortableContainer((props) => ) export default class LayerList extends React.Component { - static propTypes = { ...layerListPropTypes }; + static propTypes = {...layerListPropTypes} render() { - return ( - false} - /> - ); + return false} + /> } } diff --git a/src/components/MapMaplibreGl.jsx b/src/components/MapMaplibreGl.jsx index e4b29dac..61cb5b73 100644 --- a/src/components/MapMaplibreGl.jsx +++ b/src/components/MapMaplibreGl.jsx @@ -1,17 +1,19 @@ -import Color from "color"; -import MapboxInspect from "mapbox-gl-inspect"; -import colors from "mapbox-gl-inspect/lib/colors"; -import MapLibreGl from "maplibre-gl"; -import "maplibre-gl/dist/maplibre-gl.css"; -import PropTypes from "prop-types"; -import React from "react"; -import ReactDOM from "react-dom"; -import { colorHighlightedLayer } from "../libs/highlight"; -import "../libs/maplibre-rtl"; -import ZoomControl from "../libs/zoomcontrol"; -import "../maplibregl.css"; -import MapMaplibreGlFeaturePropertyPopup from "./MapMaplibreGlFeaturePropertyPopup"; -import MapMaplibreGlLayerPopup from "./MapMaplibreGlLayerPopup"; +import React from 'react' +import PropTypes from 'prop-types' +import ReactDOM from 'react-dom' +import MapLibreGl from 'maplibre-gl' +import MapboxInspect from 'mapbox-gl-inspect' +import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup' +import MapMaplibreGlFeaturePropertyPopup from './MapMaplibreGlFeaturePropertyPopup' +import tokens from '../config/tokens.json' +import colors from 'mapbox-gl-inspect/lib/colors' +import Color from 'color' +import ZoomControl from '../libs/zoomcontrol' +import { colorHighlightedLayer } from '../libs/highlight' +import 'maplibre-gl/dist/maplibre-gl.css' +import '../maplibregl.css' +import '../libs/maplibre-rtl' + const IS_SUPPORTED = MapLibreGl.supported(); @@ -22,32 +24,32 @@ function renderPopup(popup, mountNode) { function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) { const backgroundLayer = { - id: "background", - type: "background", - paint: { - "background-color": "#1c1f24", - }, - }; - - const layer = colorHighlightedLayer(highlightedLayer); - if (layer) { - coloredLayers.push(layer); + "id": "background", + "type": "background", + "paint": { + "background-color": '#1c1f24', + } } - const sources = {}; - Object.keys(originalMapStyle.sources).forEach((sourceId) => { - const source = originalMapStyle.sources[sourceId]; - if (source.type !== "raster" && source.type !== "raster-dem") { - sources[sourceId] = source; + const layer = colorHighlightedLayer(highlightedLayer) + if(layer) { + coloredLayers.push(layer) + } + + const sources = {} + Object.keys(originalMapStyle.sources).forEach(sourceId => { + const source = originalMapStyle.sources[sourceId] + if(source.type !== 'raster' && source.type !== 'raster-dem') { + sources[sourceId] = source } - }); + }) const inspectStyle = { ...originalMapStyle, sources: sources, - layers: [backgroundLayer].concat(coloredLayers), - }; - return inspectStyle; + layers: [backgroundLayer].concat(coloredLayers) + } + return inspectStyle } export default class MapMaplibreGl extends React.Component { @@ -60,7 +62,7 @@ export default class MapMaplibreGl extends React.Component { options: PropTypes.object, replaceAccessTokens: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, - }; + } static defaultProps = { onMapLoaded: () => {}, @@ -68,55 +70,51 @@ export default class MapMaplibreGl extends React.Component { onLayerSelect: () => {}, onChange: () => {}, options: {}, - }; + } constructor(props) { - super(props); + super(props) this.state = { map: null, inspect: null, - }; + } } updateMapFromProps(props) { - if (!IS_SUPPORTED) return; + if(!IS_SUPPORTED) return; - if (!this.state.map) return; + if(!this.state.map) return //Maplibre GL now does diffing natively so we don't need to calculate //the necessary operations ourselves! - this.state.map.setStyle(this.props.replaceAccessTokens(props.mapStyle), { - diff: true, - }); + this.state.map.setStyle( + this.props.replaceAccessTokens(props.mapStyle), + {diff: true} + ) } shouldComponentUpdate(nextProps, nextState) { let should = false; try { - should = - JSON.stringify(this.props) !== JSON.stringify(nextProps) || - JSON.stringify(this.state) !== JSON.stringify(nextState); - } catch (e) { + should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState); + } catch(e) { // no biggie, carry on } return should; } componentDidUpdate(prevProps, prevState, snapshot) { - if (!IS_SUPPORTED) return; + if(!IS_SUPPORTED) return; const map = this.state.map; this.updateMapFromProps(this.props); - if ( - this.state.inspect && - this.props.inspectModeEnabled !== this.state.inspect._showInspectMap - ) { + if(this.state.inspect && this.props.inspectModeEnabled !== this.state.inspect._showInspectMap) { // HACK: Fix for , while we wait for a proper fix. // eslint-disable-next-line this.state.inspect._popupBlocked = false; - this.state.inspect.toggleInspector(); + this.state.inspect.toggleInspector() } if (map) { if (this.props.inspectModeEnabled) { @@ -125,7 +123,7 @@ export default class MapMaplibreGl extends React.Component { // mapbox-gl-inspect. try { this.state.inspect.render(); - } catch (err) { + } catch(err) { console.error("FIXME: Caught error", err); } } @@ -137,40 +135,40 @@ export default class MapMaplibreGl extends React.Component { } componentDidMount() { - if (!IS_SUPPORTED) return; + if(!IS_SUPPORTED) return; const mapOpts = { ...this.props.options, container: this.container, style: this.props.mapStyle, hash: true, - maxZoom: 24, - }; + maxZoom: 24 + } const map = new MapLibreGl.Map(mapOpts); const mapViewChange = () => { const center = map.getCenter(); const zoom = map.getZoom(); - this.props.onChange({ center, zoom }); - }; + this.props.onChange({center, zoom}); + } mapViewChange(); map.showTileBoundaries = mapOpts.showTileBoundaries; map.showCollisionBoxes = mapOpts.showCollisionBoxes; map.showOverdrawInspector = mapOpts.showOverdrawInspector; - const zoomControl = new ZoomControl(); - map.addControl(zoomControl, "top-right"); + const zoomControl = new ZoomControl; + map.addControl(zoomControl, 'top-right'); - const nav = new MapLibreGl.NavigationControl({ visualizePitch: true }); - map.addControl(nav, "top-right"); + const nav = new MapLibreGl.NavigationControl({visualizePitch:true}); + map.addControl(nav, 'top-right'); - const tmpNode = document.createElement("div"); + const tmpNode = document.createElement('div'); const inspect = new MapboxInspect({ popup: new MapLibreGl.Popup({ - closeOnClick: false, + closeOnClick: false }), showMapPopup: true, showMapPopupOnHover: false, @@ -178,58 +176,41 @@ export default class MapMaplibreGl extends React.Component { showInspectButton: false, blockHoverPopupOnClick: true, assignLayerColor: (layerId, alpha) => { - return Color(colors.brightColor(layerId, alpha)) - .desaturate(0.5) - .string(); + return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string() }, - buildInspectStyle: (originalMapStyle, coloredLayers) => - buildInspectStyle( - originalMapStyle, - coloredLayers, - this.props.highlightedLayer - ), - renderPopup: (features) => { - if (this.props.inspectModeEnabled) { - return renderPopup( - , - tmpNode - ); + buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer), + renderPopup: features => { + if(this.props.inspectModeEnabled) { + return renderPopup(, tmpNode); } else { - return renderPopup( - , - tmpNode - ); + return renderPopup(, tmpNode); } - }, - }); - map.addControl(inspect); + } + }) + map.addControl(inspect) map.on("style.load", () => { this.setState({ map, inspect, - zoom: map.getZoom(), + zoom: map.getZoom() }); - }); + }) - map.on("data", (e) => { - if (e.dataType !== "tile") return; + map.on("data", e => { + if(e.dataType !== 'tile') return this.props.onDataChange({ - map: this.state.map, - }); - }); + map: this.state.map + }) + }) - map.on("error", (e) => { + map.on("error", e => { console.log("ERROR", e); - }); + }) - map.on("zoom", (e) => { + map.on("zoom", e => { this.setState({ - zoom: map.getZoom(), + zoom: map.getZoom() }); }); @@ -238,32 +219,29 @@ export default class MapMaplibreGl extends React.Component { } onLayerSelectById = (id) => { - const index = this.props.mapStyle.layers.findIndex( - (layer) => layer.id === id - ); + const index = this.props.mapStyle.layers.findIndex(layer => layer.id === id); this.props.onLayerSelect(index); - }; + } render() { - if (IS_SUPPORTED) { - return ( -
(this.container = x)} - data-wd-key="maplibre:map" - >
- ); - } else { - return ( -
-
- Error: Cannot load MaplibreGL, WebGL is either unsupported or - disabled -
+ if(IS_SUPPORTED) { + return
this.container = x} + data-wd-key="maplibre:map" + >
+ } + else { + return
+
+ Error: Cannot load MaplibreGL, WebGL is either unsupported or disabled
- ); +
} } } + diff --git a/src/libs/debug.js b/src/libs/debug.ts similarity index 54% rename from src/libs/debug.js rename to src/libs/debug.ts index db31db0b..2a5dc9fc 100644 --- a/src/libs/debug.js +++ b/src/libs/debug.ts @@ -1,12 +1,16 @@ -import querystring from 'querystring' +interface DebugStore { + [namespace: string]: { + [key: string]: any + } +} - -const debugStore = {}; +const debugStore: DebugStore = {}; function enabled() { - const qs = querystring.parse(window.location.search.slice(1)); - if(qs.hasOwnProperty("debug")) { - return !!qs.debug.match(/^(|1|true)$/); + const qs = new URL(window.location.href).searchParams; + const debugQs = qs.get("debug"); + if(debugQs) { + return !!debugQs.match(/^(|1|true)$/); } else { return false; @@ -17,7 +21,7 @@ function genErr() { return new Error("Debug not enabled, enable by appending '?debug' to your query string"); } -function set(namespace, key, value) { +function set(namespace: keyof DebugStore, key: string, value: any) { if(!enabled()) { throw genErr(); } @@ -25,7 +29,7 @@ function set(namespace, key, value) { debugStore[namespace][key] = value; } -function get(namespace, key) { +function get(namespace: keyof DebugStore, key: string) { if(!enabled()) { throw genErr(); } @@ -38,7 +42,7 @@ const mod = { enabled, get, set -} +}; -window.debug = mod; +(window as any).debug = mod; export default mod; diff --git a/src/libs/zoomcontrol.js b/src/libs/zoomcontrol.js index ed675338..34e947d6 100644 --- a/src/libs/zoomcontrol.js +++ b/src/libs/zoomcontrol.js @@ -1,28 +1,27 @@ export default class ZoomControl { onAdd(map) { this._map = map; - this._container = document.createElement("div"); - this._container.className = - "maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-zoom"; + this._container = document.createElement('div'); + this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-zoom'; this._container.setAttribute("data-wd-key", "maplibre:ctrl-zoom"); this._container.innerHTML = ` Zoom: `; this._textEl = this._container.querySelector("span"); - + this.addEventListeners(); - + return this._container; } - + updateZoomLevel() { this._textEl.innerHTML = this._map.getZoom().toFixed(2); } - - addEventListeners() { - this._map.on("render", this.updateZoomLevel.bind(this)); - this._map.on("zoomIn", this.updateZoomLevel.bind(this)); - this._map.on("zoomOut", this.updateZoomLevel.bind(this)); + + addEventListeners (){ + this._map.on('render', this.updateZoomLevel.bind(this) ); + this._map.on('zoomIn', this.updateZoomLevel.bind(this) ); + this._map.on('zoomOut', this.updateZoomLevel.bind(this) ); } onRemove() { diff --git a/vite.config.ts b/vite.config.ts index dd059fb3..d9c1bb8a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,11 +1,22 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +import replace from '@rollup/plugin-replace'; export default defineConfig({ server: { port: 8888 }, - plugins: [react()], + plugins: [ + replace({ + preventAssignment: true, + include: /\/jsonlint-lines-primitives\/lib\/jsonlint.js/, + delimiters: ['', ''], + values: { + '_token_stack:': '' + } + }) as any, + react() + ], define: { global: "window", },