Fix tests, types, added data-wd-key

This commit is contained in:
HarelM
2023-12-19 23:44:35 +02:00
parent 84adbe6eb2
commit 3ba3a20848
14 changed files with 1124 additions and 1313 deletions
+3 -3
View File
@@ -11,7 +11,7 @@ describe("accessibility", () => {
it("skip link to layer list", () => { it("skip link to layer list", () => {
const selector = "root:skip:layer-list"; const selector = "root:skip:layer-list";
should.isExists(selector); should.exist(selector);
when.tab(); when.tab();
should.beFocused(selector); should.beFocused(selector);
when.click(selector); when.click(selector);
@@ -20,7 +20,7 @@ describe("accessibility", () => {
it("skip link to layer editor", () => { it("skip link to layer editor", () => {
const selector = "root:skip:layer-editor"; const selector = "root:skip:layer-editor";
should.isExists(selector); should.exist(selector);
when.tab().tab(); when.tab().tab();
should.beFocused(selector); should.beFocused(selector);
when.click(selector); when.click(selector);
@@ -29,7 +29,7 @@ describe("accessibility", () => {
it("skip link to map view", () => { it("skip link to map view", () => {
const selector = "root:skip:map-view"; const selector = "root:skip:map-view";
should.isExists(selector); should.exist(selector);
when.tab().tab().tab(); when.tab().tab().tab();
should.beFocused(selector); should.beFocused(selector);
when.click(selector); when.click(selector);
+29 -17
View File
@@ -73,7 +73,7 @@ export default class MaputnikDriver {
} }
cy.get(".maputnik-toolbar-link").should("be.visible"); cy.get(".maputnik-toolbar-link").should("be.visible");
}, },
fillLayersModal: (opts: any) => { fillLayersModal: (opts: {type: string, layer?: string, id?: string}) => {
var type = opts.type; var type = opts.type;
var layer = opts.layer; var layer = opts.layer;
var id; var id;
@@ -84,12 +84,12 @@ export default class MaputnikDriver {
} }
cy.get( cy.get(
this.get.getDataAttribute("add-layer.layer-type", "select") this.get.dataAttribute("add-layer.layer-type", "select")
).select(type); ).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) { if (layer) {
cy.get( cy.get(
this.get.getDataAttribute("add-layer.layer-source-block", "input") this.get.dataAttribute("add-layer.layer-source-block", "input")
).type(layer); ).type(layer);
} }
this.when.click("add-layer"); this.when.click("add-layer");
@@ -103,11 +103,20 @@ export default class MaputnikDriver {
click: (selector: string) => { click: (selector: string) => {
this.helper.when.click(selector); 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) => { select: (selector: string, value: string) => {
cy.get(selector).select(value); this.helper.get.element(selector).select(value);
}, },
focus: (selector: string) => { focus: (selector: string) => {
@@ -126,8 +135,8 @@ export default class MaputnikDriver {
openLayersModal: () => { openLayersModal: () => {
this.helper.when.click("layer-list:add-layer"); this.helper.when.click("layer-list:add-layer");
cy.get(this.get.getDataAttribute("modal:add-layer")).should("exist"); cy.get(this.get.dataAttribute("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("be.visible");
}, },
}; };
@@ -135,16 +144,16 @@ export default class MaputnikDriver {
isMac: () => { isMac: () => {
return Cypress.platform === "darwin"; return Cypress.platform === "darwin";
}, },
getStyleFromWindow: (win: Window) => { styleFromWindow: (win: Window) => {
const styleId = win.localStorage.getItem("maputnik:latest_style"); const styleId = win.localStorage.getItem("maputnik:latest_style");
const styleItem = win.localStorage.getItem(`maputnik:style:${styleId}`); const styleItem = win.localStorage.getItem(`maputnik:style:${styleId}`);
const obj = JSON.parse(styleItem || ""); const obj = JSON.parse(styleItem || "");
return obj; return obj;
}, },
getExampleFileUrl: () => { exampleFileUrl: () => {
return "http://localhost:8888/example-style.json"; 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 || ""}`; return `*[data-wd-key='${key}'] ${selector || ""}`;
}, },
}; };
@@ -176,23 +185,26 @@ export default class MaputnikDriver {
equalStyleStore: (getter: (obj: any) => any, styleObj: any) => { equalStyleStore: (getter: (obj: any) => any, styleObj: any) => {
cy.window().then((win: any) => { cy.window().then((win: any) => {
const obj = this.get.getStyleFromWindow(win); const obj = this.get.styleFromWindow(win);
assert.deepEqual(getter(obj), styleObj); assert.deepEqual(getter(obj), styleObj);
}); });
}, },
isStyleStoreEqualToExampleFileData: () => { styleStoreEqualToExampleFileData: () => {
cy.window().then((win: any) => { 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); cy.fixture("example-style.json").should("deep.equal", obj);
}); });
}, },
isExists: (selector: string) => { exist: (selector: string) => {
this.helper.get.element(selector).should("exist"); this.helper.get.element(selector).should("exist");
}, },
isSelected: (selector: string, value: string) => { beSelected: (selector: string, value: string) => {
cy.get(selector).find(`option[value="${value}"]`).should("be.selected"); 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);
}
}; };
} }
+3 -3
View File
@@ -1,4 +1,4 @@
import { default as MaputnikDriver } from "./driver"; import MaputnikDriver from "./driver";
describe("keyboard", () => { describe("keyboard", () => {
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver(); let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
@@ -45,12 +45,12 @@ describe("keyboard", () => {
it("'i' should change map to inspect mode", () => { it("'i' should change map to inspect mode", () => {
when.typeKeys("i"); when.typeKeys("i");
should.isSelected(get.getDataAttribute("nav:inspect"), "inspect"); should.beSelected("nav:inspect", "inspect");
}); });
it("'m' should focus map", () => { it("'m' should focus map", () => {
when.typeKeys("m"); when.typeKeys("m");
should.beFocused(".maplibregl-canvas"); should.canvasBeFocused();
}); });
it("'!' should show debug modal", () => { it("'!' should show debug modal", () => {
+9 -15
View File
@@ -1,4 +1,3 @@
var assert = require("assert");
import { v1 as uuid } from "uuid"; import { v1 as uuid } from "uuid";
import MaputnikDriver from "./driver"; import MaputnikDriver from "./driver";
@@ -134,12 +133,9 @@ describe("layers", () => {
// Setup // Setup
var id = uuid(); var id = uuid();
when.select( when.selectWithin("add-layer.layer-type", "background");
get.getDataAttribute("add-layer.layer-type", "select"),
"background"
);
when.setValue( when.setValue(
get.getDataAttribute("add-layer.layer-id", "input"), get.dataAttribute("add-layer.layer-id", "input"),
"background:" + id "background:" + id
); );
@@ -167,7 +163,7 @@ describe("layers", () => {
var id = uuid(); var id = uuid();
when.setValue( when.setValue(
get.getDataAttribute("layer-editor.layer-id", "input"), get.dataAttribute("layer-editor.layer-id", "input"),
"foobar:" + id "foobar:" + id
); );
when.click("min-zoom"); when.click("min-zoom");
@@ -188,7 +184,7 @@ describe("layers", () => {
when.click("layer-list-item:background:" + bgId); when.click("layer-list-item:background:" + bgId);
when.setValue( when.setValue(
get.getDataAttribute("min-zoom", 'input[type="text"]'), get.dataAttribute("min-zoom", 'input[type="text"]'),
"1" "1"
); );
@@ -206,8 +202,8 @@ describe("layers", () => {
); );
// AND RESET! // AND RESET!
// driver.setValue(driver.getDataAttribute("min-zoom", "input"), "") // driver.setValue(driver.get.dataAttribute("min-zoom", "input"), "")
// driver.click(driver.getDataAttribute("max-zoom", "input")); // driver.click(driver.get.dataAttribute("max-zoom", "input"));
// driver.isStyleStoreEqual((a: any) => a.layers, [ // driver.isStyleStoreEqual((a: any) => a.layers, [
// { // {
@@ -222,7 +218,7 @@ describe("layers", () => {
when.click("layer-list-item:background:" + bgId); when.click("layer-list-item:background:" + bgId);
when.setValue( when.setValue(
get.getDataAttribute("max-zoom", 'input[type="text"]'), get.dataAttribute("max-zoom", 'input[type="text"]'),
"1" "1"
); );
@@ -245,7 +241,7 @@ describe("layers", () => {
var id = uuid(); var id = uuid();
when.click("layer-list-item:background:" + bgId); 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"); when.click("layer-editor.layer-id");
@@ -324,9 +320,7 @@ describe("layers", () => {
when.typeKeys( when.typeKeys(
"\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013 {" "\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013 {"
); );
should.isExists(errorSelector); should.exist(errorSelector);
when.click("layer-editor.layer-id");
}); });
}); });
}); });
+3 -5
View File
@@ -8,18 +8,16 @@ describe("map", () => {
var zoomLevel = 12.37; var zoomLevel = 12.37;
when.setStyle("geojson", zoomLevel); when.setStyle("geojson", zoomLevel);
should.beVisible("maplibre:ctrl-zoom"); should.beVisible("maplibre:ctrl-zoom");
// HM TODO should.containText("maplibre:ctrl-zoom", "Zoom: " + zoomLevel);
//driver.getText(".maplibregl-ctrl-zoom") === "Zoom "+(zoomLevel);
}); });
it("via map controls", () => { it("via map controls", () => {
var zoomLevel = 12.37; var zoomLevel = 12.37;
when.setStyle("geojson", zoomLevel); when.setStyle("geojson", zoomLevel);
when.click("maplibre:ctrl-zoom");
should.beVisible("maplibre:ctrl-zoom"); should.beVisible("maplibre:ctrl-zoom");
// HM TODO when.clickZoomin();
//driver.getText(".maplibregl-ctrl-zoom") === "Zoom "+(zoomLevel + 1); should.containText("maplibre:ctrl-zoom", "Zoom: "+(zoomLevel + 1));
}); });
}); });
}); });
+13 -19
View File
@@ -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... // HM: I was not able to make the following choose file actually to select a file and close the modal...
when.chooseExampleFile(); when.chooseExampleFile();
should.isStyleStoreEqualToExampleFileData(); should.styleStoreEqualToExampleFileData();
}); });
it("load from url", () => { 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.click("modal:open.url.button");
when.waitForExampleFileRequset(); when.waitForExampleFileRequset();
should.isStyleStoreEqualToExampleFileData(); should.styleStoreEqualToExampleFileData();
}); });
}); });
@@ -67,7 +67,7 @@ describe("modals", () => {
it("toggle", () => { it("toggle", () => {
when.setStyle("geojson"); when.setStyle("geojson");
when.select(get.getDataAttribute("nav:inspect", "select"), "inspect"); when.selectWithin("nav:inspect", "inspect");
}); });
}); });
@@ -77,20 +77,20 @@ describe("modals", () => {
}); });
it("name", () => { it("name", () => {
when.setValue(get.getDataAttribute("modal:settings.name"), "foobar"); when.setValue(get.dataAttribute("modal:settings.name"), "foobar");
when.click("modal:settings.owner"); when.click("modal:settings.owner");
should.equalStyleStore((obj) => obj.name, "foobar"); should.equalStyleStore((obj) => obj.name, "foobar");
}); });
it("owner", () => { it("owner", () => {
when.setValue(get.getDataAttribute("modal:settings.owner"), "foobar"); when.setValue(get.dataAttribute("modal:settings.owner"), "foobar");
when.click("modal:settings.name"); when.click("modal:settings.name");
should.equalStyleStore((obj) => obj.owner, "foobar"); should.equalStyleStore((obj) => obj.owner, "foobar");
}); });
it("sprite url", () => { it("sprite url", () => {
when.setValue( when.setValue(
get.getDataAttribute("modal:settings.sprite"), get.dataAttribute("modal:settings.sprite"),
"http://example.com" "http://example.com"
); );
when.click("modal:settings.name"); when.click("modal:settings.name");
@@ -99,7 +99,7 @@ describe("modals", () => {
}); });
it("glyphs url", () => { it("glyphs url", () => {
var glyphsUrl = "http://example.com/{fontstack}/{range}.pbf"; 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"); when.click("modal:settings.name");
should.equalStyleStore((obj) => obj.glyphs, glyphsUrl); should.equalStyleStore((obj) => obj.glyphs, glyphsUrl);
@@ -108,7 +108,7 @@ describe("modals", () => {
it("maptiler access token", () => { it("maptiler access token", () => {
var apiKey = "testing123"; var apiKey = "testing123";
when.setValue( when.setValue(
get.getDataAttribute( get.dataAttribute(
"modal:settings.maputnik:openmaptiles_access_token" "modal:settings.maputnik:openmaptiles_access_token"
), ),
apiKey apiKey
@@ -124,7 +124,7 @@ describe("modals", () => {
it("thunderforest access token", () => { it("thunderforest access token", () => {
var apiKey = "testing123"; var apiKey = "testing123";
when.setValue( when.setValue(
get.getDataAttribute( get.dataAttribute(
"modal:settings.maputnik:thunderforest_access_token" "modal:settings.maputnik:thunderforest_access_token"
), ),
apiKey apiKey
@@ -139,14 +139,8 @@ describe("modals", () => {
it("style renderer", () => { it("style renderer", () => {
cy.on("uncaught:exception", () => false); // this is due to the fact that this is an invalid style for openlayers cy.on("uncaught:exception", () => false); // this is due to the fact that this is an invalid style for openlayers
when.select( when.select("modal:settings.maputnik:renderer", "ol");
get.getDataAttribute("modal:settings.maputnik:renderer"), should.beSelected("modal:settings.maputnik:renderer", "ol");
"ol"
);
should.isSelected(
get.getDataAttribute("modal:settings.maputnik:renderer"),
"ol"
);
when.click("modal:settings.name"); when.click("modal:settings.name");
+412 -478
View File
File diff suppressed because it is too large Load Diff
+152 -203
View File
@@ -1,33 +1,24 @@
import classnames from "classnames"; import React from 'react'
import { detect } from "detect-browser"; import PropTypes from 'prop-types'
import PropTypes from "prop-types"; import classnames from 'classnames'
import React from "react"; import {detect} from 'detect-browser';
import { import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage, MdAssignmentTurnedIn} from 'react-icons/md'
MdAssignmentTurnedIn, import pkgJson from '../../package.json'
MdFileDownload,
MdFindInPage,
MdHelpOutline,
MdLayers,
MdOpenInBrowser,
MdSettings,
} from "react-icons/md";
import logoImage from "maputnik-design/logos/logo-color.svg";
import pkgJson from "../../package.json";
// This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of. // This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of.
const browser = detect(); const browser = detect();
const colorAccessibilityFiltersEnabled = const colorAccessibilityFiltersEnabled = ['chrome', 'firefox'].indexOf(browser.name) > -1;
["chrome", "firefox"].indexOf(browser.name) > -1;
class IconText extends React.Component { class IconText extends React.Component {
static propTypes = { static propTypes = {
children: PropTypes.node, children: PropTypes.node,
}; }
render() { render() {
return <span className="maputnik-icon-text">{this.props.children}</span>; return <span className="maputnik-icon-text">{this.props.children}</span>
} }
} }
@@ -37,19 +28,17 @@ class ToolbarLink extends React.Component {
children: PropTypes.node, children: PropTypes.node,
href: PropTypes.string, href: PropTypes.string,
onToggleModal: PropTypes.func, onToggleModal: PropTypes.func,
}; }
render() { render() {
return ( return <a
<a className={classnames('maputnik-toolbar-link', this.props.className)}
className={classnames("maputnik-toolbar-link", this.props.className)} href={this.props.href}
href={this.props.href} rel="noopener noreferrer"
rel="noopener noreferrer" target="_blank"
target="_blank" >
> {this.props.children}
{this.props.children} </a>
</a>
);
} }
} }
@@ -58,41 +47,36 @@ class ToolbarLinkHighlighted extends React.Component {
className: PropTypes.string, className: PropTypes.string,
children: PropTypes.node, children: PropTypes.node,
href: PropTypes.string, href: PropTypes.string,
onToggleModal: PropTypes.func, onToggleModal: PropTypes.func
}; }
render() { render() {
return ( return <a
<a className={classnames('maputnik-toolbar-link', "maputnik-toolbar-link--highlighted", this.props.className)}
className={classnames( href={this.props.href}
"maputnik-toolbar-link", rel="noopener noreferrer"
"maputnik-toolbar-link--highlighted", target="_blank"
this.props.className >
)} <span className="maputnik-toolbar-link-wrapper">
href={this.props.href} {this.props.children}
rel="noopener noreferrer" </span>
target="_blank" </a>
>
<span className="maputnik-toolbar-link-wrapper">
{this.props.children}
</span>
</a>
);
} }
} }
class ToolbarSelect extends React.Component { class ToolbarSelect extends React.Component {
static propTypes = { static propTypes = {
children: PropTypes.node, children: PropTypes.node,
wdKey: PropTypes.string, wdKey: PropTypes.string
}; }
render() { render() {
return ( return <div
<div className="maputnik-toolbar-select" data-wd-key={this.props.wdKey}> className='maputnik-toolbar-select'
{this.props.children} data-wd-key={this.props.wdKey}
</div> >
); {this.props.children}
</div>
} }
} }
@@ -100,19 +84,17 @@ class ToolbarAction extends React.Component {
static propTypes = { static propTypes = {
children: PropTypes.node, children: PropTypes.node,
onClick: PropTypes.func, onClick: PropTypes.func,
wdKey: PropTypes.string, wdKey: PropTypes.string
}; }
render() { render() {
return ( return <button
<button className='maputnik-toolbar-action'
className="maputnik-toolbar-action" data-wd-key={this.props.wdKey}
data-wd-key={this.props.wdKey} onClick={this.props.onClick}
onClick={this.props.onClick} >
> {this.props.children}
{this.props.children} </button>
</button>
);
} }
} }
@@ -130,7 +112,7 @@ export default class AppToolbar extends React.Component {
onSetMapState: PropTypes.func, onSetMapState: PropTypes.func,
mapState: PropTypes.string, mapState: PropTypes.string,
renderer: PropTypes.string, renderer: PropTypes.string,
}; }
state = { state = {
isOpen: { isOpen: {
@@ -139,8 +121,8 @@ export default class AppToolbar extends React.Component {
open: false, open: false,
add: false, add: false,
export: false, export: false,
}, }
}; }
handleSelection(val) { handleSelection(val) {
this.props.onSetMapState(val); this.props.onSetMapState(val);
@@ -149,11 +131,12 @@ export default class AppToolbar extends React.Component {
onSkip = (target) => { onSkip = (target) => {
if (target === "map") { if (target === "map") {
document.querySelector(".maplibregl-canvas").focus(); document.querySelector(".maplibregl-canvas").focus();
} else { }
const el = document.querySelector("#skip-target-" + target); else {
const el = document.querySelector("#skip-target-"+target);
el.focus(); el.focus();
} }
}; }
render() { render() {
const views = [ const views = [
@@ -166,7 +149,7 @@ export default class AppToolbar extends React.Component {
id: "inspect", id: "inspect",
group: "general", group: "general",
title: "Inspect", title: "Inspect",
disabled: this.props.renderer === "ol", disabled: this.props.renderer === 'ol',
}, },
{ {
id: "filter-deuteranopia", id: "filter-deuteranopia",
@@ -198,137 +181,103 @@ export default class AppToolbar extends React.Component {
return view.id === this.props.mapState; return view.id === this.props.mapState;
}); });
return ( return <nav className='maputnik-toolbar'>
<nav className="maputnik-toolbar"> <div className="maputnik-toolbar__inner">
<div className="maputnik-toolbar__inner"> <div
<div className="maputnik-toolbar-logo-container"> className="maputnik-toolbar-logo-container"
{/* Keyboard accessible quick links */} >
<button {/* Keyboard accessible quick links */}
data-wd-key="root:skip:layer-list" <button
className="maputnik-toolbar-skip" data-wd-key="root:skip:layer-list"
onClick={(e) => this.onSkip("layer-list")} className="maputnik-toolbar-skip"
> onClick={e => this.onSkip("layer-list")}
Layers list
</button>
<button
data-wd-key="root:skip:layer-editor"
className="maputnik-toolbar-skip"
onClick={(e) => this.onSkip("layer-editor")}
>
Layer editor
</button>
<button
data-wd-key="root:skip:map-view"
className="maputnik-toolbar-skip"
onClick={(e) => this.onSkip("map")}
>
Map view
</button>
<a
className="maputnik-toolbar-logo"
target="blank"
rel="noreferrer noopener"
href="https://github.com/maputnik/editor"
>
<span dangerouslySetInnerHTML={{ __html: logoImage }} />
<h1>
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
<span className="maputnik-toolbar-version">
v{pkgJson.version}
</span>
</h1>
</a>
</div>
<div
className="maputnik-toolbar__actions"
role="navigation"
aria-label="Toolbar"
> >
<ToolbarAction Layers list
wdKey="nav:open" </button>
onClick={this.props.onToggleModal.bind(this, "open")} <button
> data-wd-key="root:skip:layer-editor"
<MdOpenInBrowser /> className="maputnik-toolbar-skip"
<IconText>Open</IconText> onClick={e => this.onSkip("layer-editor")}
</ToolbarAction> >
<ToolbarAction Layer editor
wdKey="nav:export" </button>
onClick={this.props.onToggleModal.bind(this, "export")} <button
> data-wd-key="root:skip:map-view"
<MdFileDownload /> className="maputnik-toolbar-skip"
<IconText>Export</IconText> onClick={e => this.onSkip("map")}
</ToolbarAction> >
<ToolbarAction Map view
wdKey="nav:sources" </button>
onClick={this.props.onToggleModal.bind(this, "sources")} <a
> className="maputnik-toolbar-logo"
<MdLayers /> target="blank"
<IconText>Data Sources</IconText> rel="noreferrer noopener"
</ToolbarAction> href="https://github.com/maputnik/editor"
<ToolbarAction >
wdKey="nav:settings" <img src="node_modules/maputnik-design/logos/logo-color.svg" />
onClick={this.props.onToggleModal.bind(this, "settings")} <h1>
> <span className="maputnik-toolbar-name">{pkgJson.name}</span>
<MdSettings /> <span className="maputnik-toolbar-version">v{pkgJson.version}</span>
<IconText>Style Settings</IconText> </h1>
</ToolbarAction> </a>
<ToolbarSelect wdKey="nav:inspect">
<MdFindInPage />
<label>
View
<select
className="maputnik-select"
data-wd-key="maputnik-select"
onChange={(e) => this.handleSelection(e.target.value)}
value={currentView.id}
>
{views
.filter((v) => v.group === "general")
.map((item) => {
return (
<option
key={item.id}
value={item.id}
disabled={item.disabled}
data-wd-key={item.id}
>
{item.title}
</option>
);
})}
<optgroup label="Color accessibility">
{views
.filter((v) => v.group === "color-accessibility")
.map((item) => {
return (
<option
key={item.id}
value={item.id}
disabled={item.disabled}
>
{item.title}
</option>
);
})}
</optgroup>
</select>
</label>
</ToolbarSelect>
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
<MdHelpOutline />
<IconText>Help</IconText>
</ToolbarLink>
<ToolbarLinkHighlighted
href={"https://gregorywolanski.typeform.com/to/cPgaSY"}
>
<MdAssignmentTurnedIn />
<IconText>Take the Maputnik Survey</IconText>
</ToolbarLinkHighlighted>
</div>
</div> </div>
</nav> <div className="maputnik-toolbar__actions" role="navigation" aria-label="Toolbar">
); <ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
<MdOpenInBrowser />
<IconText>Open</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
<MdFileDownload />
<IconText>Export</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
<MdLayers />
<IconText>Data Sources</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
<MdSettings />
<IconText>Style Settings</IconText>
</ToolbarAction>
<ToolbarSelect wdKey="nav:inspect">
<MdFindInPage />
<label>View
<select
className="maputnik-select"
data-wd-key="maputnik-select"
onChange={(e) => this.handleSelection(e.target.value)}
value={currentView.id}
>
{views.filter(v => v.group === "general").map((item) => {
return (
<option key={item.id} value={item.id} disabled={item.disabled} data-wd-key={item.id}>
{item.title}
</option>
);
})}
<optgroup label="Color accessibility">
{views.filter(v => v.group === "color-accessibility").map((item) => {
return (
<option key={item.id} value={item.id} disabled={item.disabled}>
{item.title}
</option>
);
})}
</optgroup>
</select>
</label>
</ToolbarSelect>
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
<MdHelpOutline />
<IconText>Help</IconText>
</ToolbarLink>
<ToolbarLinkHighlighted href={"https://gregorywolanski.typeform.com/to/cPgaSY"}>
<MdAssignmentTurnedIn />
<IconText>Take the Maputnik Survey</IconText>
</ToolbarLinkHighlighted>
</div>
</div>
</nav>
} }
} }
+207 -249
View File
@@ -1,46 +1,47 @@
import PropTypes from "prop-types"; import React from 'react'
import React from "react"; import PropTypes from 'prop-types'
import { Button, Menu, MenuItem, Wrapper } from "react-aria-menubutton"; import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
import { Accordion } from "react-accessible-accordion"; import FieldJson from './FieldJson'
import FieldComment from "./FieldComment"; import FilterEditor from './FilterEditor'
import FieldId from "./FieldId"; import PropertyGroup from './PropertyGroup'
import FieldJson from "./FieldJson"; import LayerEditorGroup from './LayerEditorGroup'
import FieldMaxZoom from "./FieldMaxZoom"; import FieldType from './FieldType'
import FieldMinZoom from "./FieldMinZoom"; import FieldId from './FieldId'
import FieldSource from "./FieldSource"; import FieldMinZoom from './FieldMinZoom'
import FieldSourceLayer from "./FieldSourceLayer"; import FieldMaxZoom from './FieldMaxZoom'
import FieldType from "./FieldType"; import FieldComment from './FieldComment'
import FilterEditor from "./FilterEditor"; import FieldSource from './FieldSource'
import LayerEditorGroup from "./LayerEditorGroup"; import FieldSourceLayer from './FieldSourceLayer'
import PropertyGroup from "./PropertyGroup"; 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 { changeType, changeProperty } from '../libs/layer'
import { changeProperty, changeType } from "../libs/layer"; import layout from '../config/layout.json'
import { formatLayerId } from "../util/format"; import {formatLayerId} from '../util/format';
function getLayoutForType(type) {
function getLayoutForType (type) {
return layout[type] ? layout[type] : layout.invalid; return layout[type] ? layout[type] : layout.invalid;
} }
function layoutGroups(layerType) { function layoutGroups(layerType) {
const layerGroup = { const layerGroup = {
title: "Layer", title: 'Layer',
type: "layer", type: 'layer'
}; }
const filterGroup = { const filterGroup = {
title: "Filter", title: 'Filter',
type: "filter", type: 'filter'
}; }
const editorGroup = { const editorGroup = {
title: "JSON Editor", title: 'JSON Editor',
type: "jsoneditor", type: 'jsoneditor'
}; }
return [layerGroup, filterGroup] return [layerGroup, filterGroup]
.concat(getLayoutForType(layerType).groups) .concat(getLayoutForType(layerType).groups)
.concat([editorGroup]); .concat([editorGroup])
} }
/** Layer editor supporting multiple types of layers. */ /** Layer editor supporting multiple types of layers. */
@@ -60,320 +61,277 @@ export default class LayerEditor extends React.Component {
isLastLayer: PropTypes.bool, isLastLayer: PropTypes.bool,
layerIndex: PropTypes.number, layerIndex: PropTypes.number,
errors: PropTypes.array, errors: PropTypes.array,
}; }
static defaultProps = { static defaultProps = {
onLayerChanged: () => {}, onLayerChanged: () => {},
onLayerIdChange: () => {}, onLayerIdChange: () => {},
onLayerDestroyed: () => {}, onLayerDestroyed: () => {},
}; }
static childContextTypes = { static childContextTypes = {
reactIconBase: PropTypes.object, reactIconBase: PropTypes.object
}; }
constructor(props) { constructor(props) {
super(props); super(props)
//TODO: Clean this up and refactor into function //TODO: Clean this up and refactor into function
const editorGroups = {}; const editorGroups = {}
layoutGroups(this.props.layer.type).forEach((group) => { layoutGroups(this.props.layer.type).forEach(group => {
editorGroups[group.title] = true; editorGroups[group.title] = true
}); })
this.state = { editorGroups }; this.state = { editorGroups }
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props, state) {
const additionalGroups = { ...state.editorGroups }; const additionalGroups = { ...state.editorGroups }
getLayoutForType(props.layer.type).groups.forEach((group) => { getLayoutForType(props.layer.type).groups.forEach(group => {
if (!(group.title in additionalGroups)) { if(!(group.title in additionalGroups)) {
additionalGroups[group.title] = true; additionalGroups[group.title] = true
} }
}); })
return { return {
editorGroups: additionalGroups, editorGroups: additionalGroups
}; };
} }
getChildContext() { getChildContext () {
return { return {
reactIconBase: { reactIconBase: {
size: 14, size: 14,
color: "#8e8e8e", color: '#8e8e8e',
}, }
}; }
} }
changeProperty(group, property, newValue) { changeProperty(group, property, newValue) {
this.props.onLayerChanged( this.props.onLayerChanged(
this.props.layerIndex, this.props.layerIndex,
changeProperty(this.props.layer, group, property, newValue) changeProperty(this.props.layer, group, property, newValue)
); )
} }
onGroupToggle(groupTitle, active) { onGroupToggle(groupTitle, active) {
const changedActiveGroups = { const changedActiveGroups = {
...this.state.editorGroups, ...this.state.editorGroups,
[groupTitle]: active, [groupTitle]: active,
}; }
this.setState({ this.setState({
editorGroups: changedActiveGroups, editorGroups: changedActiveGroups
}); })
} }
renderGroupType(type, fields) { renderGroupType(type, fields) {
let comment = ""; let comment = ""
if (this.props.layer.metadata) { if(this.props.layer.metadata) {
comment = this.props.layer.metadata["maputnik:comment"]; comment = this.props.layer.metadata['maputnik:comment']
} }
const { errors, layerIndex } = this.props; const {errors, layerIndex} = this.props;
const errorData = {}; const errorData = {};
errors.forEach((error) => { errors.forEach(error => {
if ( if (
error.parsed && error.parsed &&
error.parsed.type === "layer" && error.parsed.type === "layer" &&
error.parsed.data.index == layerIndex error.parsed.data.index == layerIndex
) { ) {
errorData[error.parsed.data.key] = { errorData[error.parsed.data.key] = {
message: error.parsed.data.message, message: error.parsed.data.message
}; };
} }
}); })
let sourceLayerIds; 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; sourceLayerIds = this.props.sources[this.props.layer.source].layers;
} }
switch (type) { switch(type) {
case "layer": case 'layer': return <div>
return ( <FieldId
<div> value={this.props.layer.id}
<FieldId wdKey="layer-editor.layer-id"
value={this.props.layer.id} error={errorData.id}
wdKey="layer-editor.layer-id" onChange={newId => this.props.onLayerIdChange(this.props.layerIndex, this.props.layer.id, newId)}
error={errorData.id} />
onChange={(newId) => <FieldType
this.props.onLayerIdChange( disabled={true}
this.props.layerIndex, error={errorData.type}
this.props.layer.id, value={this.props.layer.type}
newId onChange={newType => this.props.onLayerChanged(
) this.props.layerIndex,
} changeType(this.props.layer, newType)
/> )}
<FieldType />
disabled={true} {this.props.layer.type !== 'background' && <FieldSource
error={errorData.type} error={errorData.source}
value={this.props.layer.type} sourceIds={Object.keys(this.props.sources)}
onChange={(newType) => value={this.props.layer.source}
this.props.onLayerChanged( onChange={v => this.changeProperty(null, 'source', v)}
this.props.layerIndex, />
changeType(this.props.layer, newType) }
) {['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.props.layer.type) < 0 &&
} <FieldSourceLayer
/> error={errorData['source-layer']}
{this.props.layer.type !== "background" && ( sourceLayerIds={sourceLayerIds}
<FieldSource value={this.props.layer['source-layer']}
error={errorData.source} onChange={v => this.changeProperty(null, 'source-layer', v)}
sourceIds={Object.keys(this.props.sources)} />
value={this.props.layer.source} }
onChange={(v) => this.changeProperty(null, "source", v)} <FieldMinZoom
/> error={errorData.minzoom}
)} value={this.props.layer.minzoom}
{["background", "raster", "hillshade", "heatmap"].indexOf( onChange={v => this.changeProperty(null, 'minzoom', v)}
this.props.layer.type />
) < 0 && ( <FieldMaxZoom
<FieldSourceLayer error={errorData.maxzoom}
error={errorData["source-layer"]} value={this.props.layer.maxzoom}
sourceLayerIds={sourceLayerIds} onChange={v => this.changeProperty(null, 'maxzoom', v)}
value={this.props.layer["source-layer"]} />
onChange={(v) => this.changeProperty(null, "source-layer", v)} <FieldComment
/> error={errorData.comment}
)} value={comment}
<FieldMinZoom onChange={v => this.changeProperty('metadata', 'maputnik:comment', v == "" ? undefined : v)}
error={errorData.minzoom} />
value={this.props.layer.minzoom} </div>
onChange={(v) => this.changeProperty(null, "minzoom", v)} case 'filter': return <div>
/> <div className="maputnik-filter-editor-wrapper">
<FieldMaxZoom <FilterEditor
error={errorData.maxzoom}
value={this.props.layer.maxzoom}
onChange={(v) => this.changeProperty(null, "maxzoom", v)}
/>
<FieldComment
error={errorData.comment}
value={comment}
onChange={(v) =>
this.changeProperty(
"metadata",
"maputnik:comment",
v == "" ? undefined : v
)
}
/>
</div>
);
case "filter":
return (
<div>
<div className="maputnik-filter-editor-wrapper">
<FilterEditor
errors={errorData}
filter={this.props.layer.filter}
properties={
this.props.vectorLayers[this.props.layer["source-layer"]]
}
onChange={(f) => this.changeProperty(null, "filter", f)}
/>
</div>
</div>
);
case "properties":
return (
<PropertyGroup
errors={errorData} errors={errorData}
layer={this.props.layer} filter={this.props.layer.filter}
groupFields={fields} properties={this.props.vectorLayers[this.props.layer['source-layer']]}
spec={this.props.spec} onChange={f => this.changeProperty(null, 'filter', f)}
onChange={this.changeProperty.bind(this)}
/> />
); </div>
case "jsoneditor": </div>
return ( case 'properties':
<FieldJson return <PropertyGroup
layer={this.props.layer} errors={errorData}
onChange={(layer) => { layer={this.props.layer}
this.props.onLayerChanged(this.props.layerIndex, layer); groupFields={fields}
}} spec={this.props.spec}
/> onChange={this.changeProperty.bind(this)}
); />
case 'jsoneditor':
return <FieldJson
layer={this.props.layer}
onChange={(layer) => {
this.props.onLayerChanged(
this.props.layerIndex,
layer
);
}}
/>
} }
} }
moveLayer(offset) { moveLayer(offset) {
this.props.onMoveLayer({ this.props.onMoveLayer({
oldIndex: this.props.layerIndex, oldIndex: this.props.layerIndex,
newIndex: this.props.layerIndex + offset, newIndex: this.props.layerIndex+offset
}); })
} }
render() { render() {
const groupIds = []; const groupIds = [];
const layerType = this.props.layer.type; const layerType = this.props.layer.type
const groups = layoutGroups(layerType) const groups = layoutGroups(layerType).filter(group => {
.filter((group) => { return !(layerType === 'background' && group.type === 'source')
return !(layerType === "background" && group.type === "source"); }).map(group => {
}) const groupId = group.title.replace(/ /g, "_");
.map((group) => { groupIds.push(groupId);
const groupId = group.title.replace(/ /g, "_"); return <LayerEditorGroup
groupIds.push(groupId); data-wd-key={group.title}
return ( id={groupId}
<LayerEditorGroup key={group.title}
data-wd-key={group.title} title={group.title}
id={groupId} isActive={this.state.editorGroups[group.title]}
key={group.title} onActiveToggle={this.onGroupToggle.bind(this, group.title)}
title={group.title} >
isActive={this.state.editorGroups[group.title]} {this.renderGroupType(group.type, group.fields)}
onActiveToggle={this.onGroupToggle.bind(this, group.title)} </LayerEditorGroup>
> })
{this.renderGroupType(group.type, group.fields)}
</LayerEditorGroup>
);
});
const layout = this.props.layer.layout || {}; const layout = this.props.layer.layout || {}
const items = { const items = {
delete: { delete: {
text: "Delete", text: "Delete",
handler: () => this.props.onLayerDestroy(this.props.layerIndex), handler: () => this.props.onLayerDestroy(this.props.layerIndex)
}, },
duplicate: { duplicate: {
text: "Duplicate", text: "Duplicate",
handler: () => this.props.onLayerCopy(this.props.layerIndex), handler: () => this.props.onLayerCopy(this.props.layerIndex)
}, },
hide: { hide: {
text: layout.visibility === "none" ? "Show" : "Hide", text: (layout.visibility === "none") ? "Show" : "Hide",
handler: () => handler: () => this.props.onLayerVisibilityToggle(this.props.layerIndex)
this.props.onLayerVisibilityToggle(this.props.layerIndex),
}, },
moveLayerUp: { moveLayerUp: {
text: "Move layer up", text: "Move layer up",
// Not actually used... // Not actually used...
disabled: this.props.isFirstLayer, disabled: this.props.isFirstLayer,
handler: () => this.moveLayer(-1), handler: () => this.moveLayer(-1)
}, },
moveLayerDown: { moveLayerDown: {
text: "Move layer down", text: "Move layer down",
// Not actually used... // Not actually used...
disabled: this.props.isLastLayer, disabled: this.props.isLastLayer,
handler: () => this.moveLayer(+1), handler: () => this.moveLayer(+1)
}, }
}; }
function handleSelection(id, event) { function handleSelection(id, event) {
event.stopPropagation; event.stopPropagation;
items[id].handler(); items[id].handler();
} }
return ( return <section className="maputnik-layer-editor"
<section role="main"
className="maputnik-layer-editor" aria-label="Layer editor"
role="main" >
aria-label="Layer editor" <header>
> <div className="layer-header">
<header> <h2 className="layer-header__title">
<div className="layer-header"> Layer: {formatLayerId(this.props.layer.id)}
<h2 className="layer-header__title"> </h2>
Layer: {formatLayerId(this.props.layer.id)} <div className="layer-header__info">
</h2> <Wrapper
<div className="layer-header__info"> className='more-menu'
<Wrapper onSelection={handleSelection}
className="more-menu" closeOnSelection={false}
onSelection={handleSelection} >
closeOnSelection={false} <Button id="skip-target-layer-editor" data-wd-key="skip-target-layer-editor" className='more-menu__button' title="Layer options">
> <MdMoreVert className="more-menu__button__svg" />
<Button </Button>
data-wd-key="skip-target-layer-editor" <Menu>
id="skip-target-layer-editor" <ul className="more-menu__menu">
className="more-menu__button" {Object.keys(items).map((id, idx) => {
title="Layer options" const item = items[id];
> return <li key={id}>
<MdMoreVert className="more-menu__button__svg" /> <MenuItem value={id} className='more-menu__menu__item'>
</Button> {item.text}
<Menu> </MenuItem>
<ul className="more-menu__menu"> </li>
{Object.keys(items).map((id, idx) => { })}
const item = items[id]; </ul>
return ( </Menu>
<li key={id}> </Wrapper>
<MenuItem
value={id}
className="more-menu__menu__item"
>
{item.text}
</MenuItem>
</li>
);
})}
</ul>
</Menu>
</Wrapper>
</div>
</div> </div>
</header> </div>
<Accordion
allowMultipleExpanded={true} </header>
allowZeroExpanded={true} <Accordion
preExpanded={groupIds} allowMultipleExpanded={true}
> allowZeroExpanded={true}
{groups} preExpanded={groupIds}
</Accordion> >
</section> {groups}
); </Accordion>
</section>
} }
} }
+156 -176
View File
@@ -1,13 +1,13 @@
import classnames from "classnames"; import React from 'react'
import lodash from "lodash"; import PropTypes from 'prop-types'
import PropTypes from "prop-types"; import classnames from 'classnames'
import React from "react"; import lodash from 'lodash';
import LayerListGroup from "./LayerListGroup"; import LayerListGroup from './LayerListGroup'
import LayerListItem from "./LayerListItem"; import LayerListItem from './LayerListItem'
import ModalAdd from "./ModalAdd"; import ModalAdd from './ModalAdd'
import { SortableContainer } from "react-sortable-hoc"; import {SortableContainer} from 'react-sortable-hoc';
const layerListPropTypes = { const layerListPropTypes = {
layers: PropTypes.array.isRequired, layers: PropTypes.array.isRequired,
@@ -15,34 +15,34 @@ const layerListPropTypes = {
onLayersChange: PropTypes.func.isRequired, onLayersChange: PropTypes.func.isRequired,
onLayerSelect: PropTypes.func, onLayerSelect: PropTypes.func,
sources: PropTypes.object.isRequired, sources: PropTypes.object.isRequired,
}; }
function layerPrefix(name) { function layerPrefix(name) {
return name.replace(" ", "-").replace("_", "-").split("-")[0]; return name.replace(' ', '-').replace('_', '-').split('-')[0]
} }
function findClosestCommonPrefix(layers, idx) { function findClosestCommonPrefix(layers, idx) {
const currentLayerPrefix = layerPrefix(layers[idx].id); const currentLayerPrefix = layerPrefix(layers[idx].id)
let closestIdx = idx; let closestIdx = idx
for (let i = idx; i > 0; i--) { for (let i = idx; i > 0; i--) {
const previousLayerPrefix = layerPrefix(layers[i - 1].id); const previousLayerPrefix = layerPrefix(layers[i-1].id)
if (previousLayerPrefix === currentLayerPrefix) { if(previousLayerPrefix === currentLayerPrefix) {
closestIdx = i - 1; closestIdx = i - 1
} else { } else {
return closestIdx; return closestIdx
} }
} }
return closestIdx; return closestIdx
} }
let UID = 0; let UID = 0;
// List of collapsible layer editors // List of collapsible layer editors
class LayerListContainer extends React.Component { class LayerListContainer extends React.Component {
static propTypes = { ...layerListPropTypes }; static propTypes = {...layerListPropTypes}
static defaultProps = { static defaultProps = {
onLayerSelect: () => {}, onLayerSelect: () => {},
}; }
constructor(props) { constructor(props) {
super(props); super(props);
@@ -56,8 +56,8 @@ class LayerListContainer extends React.Component {
}, },
isOpen: { isOpen: {
add: false, add: false,
}, }
}; }
} }
toggleModal(modalName) { toggleModal(modalName) {
@@ -68,82 +68,79 @@ class LayerListContainer extends React.Component {
}, },
isOpen: { isOpen: {
...this.state.isOpen, ...this.state.isOpen,
[modalName]: !this.state.isOpen[modalName], [modalName]: !this.state.isOpen[modalName]
}, }
}); })
} }
toggleLayers = () => { 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) { if (layers.length > 1) {
newGroups[lookupKey] = this.state.areAllGroupsExpanded; newGroups[lookupKey] = this.state.areAllGroupsExpanded
} }
layers.forEach((layer) => { layers.forEach((layer) => {
idx += 1; idx += 1
}); })
}); });
this.setState({ this.setState({
collapsedGroups: newGroups, collapsedGroups: newGroups,
areAllGroupsExpanded: !this.state.areAllGroupsExpanded, areAllGroupsExpanded: !this.state.areAllGroupsExpanded
}); })
}; }
groupedLayers() { groupedLayers() {
const groups = []; const groups = []
const layerIdCount = new Map(); const layerIdCount = new Map();
for (let i = 0; i < this.props.layers.length; i++) { for (let i = 0; i < this.props.layers.length; i++) {
const origLayer = this.props.layers[i]; const origLayer = this.props.layers[i];
const previousLayer = this.props.layers[i - 1]; const previousLayer = this.props.layers[i-1]
layerIdCount.set( layerIdCount.set(origLayer.id,
origLayer.id,
layerIdCount.has(origLayer.id) ? layerIdCount.get(origLayer.id) + 1 : 0 layerIdCount.has(origLayer.id) ? layerIdCount.get(origLayer.id) + 1 : 0
); );
const layer = { const layer = {
...origLayer, ...origLayer,
key: `layers-list-${origLayer.id}-${layerIdCount.get(origLayer.id)}`, key: `layers-list-${origLayer.id}-${layerIdCount.get(origLayer.id)}`,
}; }
if ( if(previousLayer && layerPrefix(previousLayer.id) == layerPrefix(layer.id)) {
previousLayer && const lastGroup = groups[groups.length - 1]
layerPrefix(previousLayer.id) == layerPrefix(layer.id) lastGroup.push(layer)
) {
const lastGroup = groups[groups.length - 1];
lastGroup.push(layer);
} else { } else {
groups.push([layer]); groups.push([layer])
} }
} }
return groups; return groups
} }
toggleLayerGroup(groupPrefix, idx) { toggleLayerGroup(groupPrefix, idx) {
const lookupKey = [groupPrefix, idx].join("-"); const lookupKey = [groupPrefix, idx].join('-')
const newGroups = { ...this.state.collapsedGroups }; const newGroups = { ...this.state.collapsedGroups }
if (lookupKey in this.state.collapsedGroups) { if(lookupKey in this.state.collapsedGroups) {
newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey]; newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey]
} else { } else {
newGroups[lookupKey] = false; newGroups[lookupKey] = false
} }
this.setState({ this.setState({
collapsedGroups: newGroups, collapsedGroups: newGroups
}); })
} }
isCollapsed(groupPrefix, idx) { isCollapsed(groupPrefix, idx) {
const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join("-")]; const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join('-')]
return collapsed === undefined ? true : collapsed; return collapsed === undefined ? true : collapsed
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate (nextProps, nextState) {
// Always update on state change // Always update on state change
if (this.state !== nextState) { if (this.state !== nextState) {
return true; return true;
@@ -151,28 +148,28 @@ class LayerListContainer extends React.Component {
// This component tree only requires id and visibility from the layers // This component tree only requires id and visibility from the layers
// objects // objects
function getRequiredProps(layer) { function getRequiredProps (layer) {
const out = { const out = {
id: layer.id, id: layer.id,
}; };
if (layer.layout) { if (layer.layout) {
out.layout = { out.layout = {
visibility: layer.layout.visibility, visibility: layer.layout.visibility
}; };
} }
return out; return out;
} }
const layersEqual = lodash.isEqual( const layersEqual = lodash.isEqual(
nextProps.layers.map(getRequiredProps), nextProps.layers.map(getRequiredProps),
this.props.layers.map(getRequiredProps) this.props.layers.map(getRequiredProps),
); );
function withoutLayers(props) { function withoutLayers (props) {
const out = { const out = {
...props, ...props
}; };
delete out["layers"]; delete out['layers'];
return out; return out;
} }
@@ -187,16 +184,16 @@ class LayerListContainer extends React.Component {
return propsChanged; return propsChanged;
} }
componentDidUpdate(prevProps) { componentDidUpdate (prevProps) {
if (prevProps.selectedLayerIndex !== this.props.selectedLayerIndex) { if (prevProps.selectedLayerIndex !== this.props.selectedLayerIndex) {
const selectedItemNode = this.selectedItemRef.current; const selectedItemNode = this.selectedItemRef.current;
if (selectedItemNode && selectedItemNode.node) { if (selectedItemNode && selectedItemNode.node) {
const target = selectedItemNode.node; const target = selectedItemNode.node;
const options = { const options = {
root: this.scrollContainerRef.current, root: this.scrollContainerRef.current,
threshold: 1.0, threshold: 1.0
}; }
const observer = new IntersectionObserver((entries) => { const observer = new IntersectionObserver(entries => {
observer.unobserve(target); observer.unobserve(target);
if (entries.length > 0 && entries[0].intersectionRatio < 1) { if (entries.length > 0 && entries[0].intersectionRatio < 1) {
target.scrollIntoView(); target.scrollIntoView();
@@ -209,32 +206,28 @@ class LayerListContainer extends React.Component {
} }
render() { render() {
const listItems = [];
let idx = 0; const listItems = []
let idx = 0
const layersByGroup = this.groupedLayers(); const layersByGroup = this.groupedLayers();
layersByGroup.forEach((layers) => { layersByGroup.forEach(layers => {
const groupPrefix = layerPrefix(layers[0].id); const groupPrefix = layerPrefix(layers[0].id)
if (layers.length > 1) { if(layers.length > 1) {
const grp = ( const grp = <LayerListGroup
<LayerListGroup data-wd-key={[groupPrefix, idx].join('-')}
data-wd-key={[groupPrefix, idx].join("-")} aria-controls={layers.map(l => l.key).join(" ")}
aria-controls={layers.map((l) => l.key).join(" ")} key={`group-${groupPrefix}-${idx}`}
key={`group-${groupPrefix}-${idx}`} title={groupPrefix}
title={groupPrefix} isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
isActive={ onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
!this.isCollapsed(groupPrefix, idx) || />
idx === this.props.selectedLayerIndex listItems.push(grp)
}
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
/>
);
listItems.push(grp);
} }
layers.forEach((layer, idxInGroup) => { 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 ( return (
error.parsed && error.parsed &&
error.parsed.type === "layer" && error.parsed.type === "layer" &&
@@ -247,107 +240,94 @@ class LayerListContainer extends React.Component {
additionalProps.ref = this.selectedItemRef; additionalProps.ref = this.selectedItemRef;
} }
const listItem = ( const listItem = <LayerListItem
<LayerListItem className={classnames({
className={classnames({ 'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
"maputnik-layer-list-item-collapsed": 'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1,
layers.length > 1 && 'maputnik-layer-list-item--error': !!layerError
this.isCollapsed(groupPrefix, groupIdx) && })}
idx !== this.props.selectedLayerIndex, index={idx}
"maputnik-layer-list-item-group-last": key={layer.key}
idxInGroup == layers.length - 1 && layers.length > 1, id={layer.key}
"maputnik-layer-list-item--error": !!layerError, layerId={layer.id}
})} layerIndex={idx}
index={idx} layerType={layer.type}
key={layer.key} visibility={(layer.layout || {}).visibility}
id={layer.key} isSelected={idx === this.props.selectedLayerIndex}
layerId={layer.id} onLayerSelect={this.props.onLayerSelect}
layerIndex={idx} onLayerDestroy={this.props.onLayerDestroy.bind(this)}
layerType={layer.type} onLayerCopy={this.props.onLayerCopy.bind(this)}
visibility={(layer.layout || {}).visibility} onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)}
isSelected={idx === this.props.selectedLayerIndex} {...additionalProps}
onLayerSelect={this.props.onLayerSelect} />
onLayerDestroy={this.props.onLayerDestroy.bind(this)} listItems.push(listItem)
onLayerCopy={this.props.onLayerCopy.bind(this)} idx += 1
onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind( })
this })
)}
{...additionalProps}
/>
);
listItems.push(listItem);
idx += 1;
});
});
return ( return <section
<section className="maputnik-layer-list"
className="maputnik-layer-list" role="complementary"
role="complementary" aria-label="Layers list"
aria-label="Layers list" ref={this.scrollContainerRef}
ref={this.scrollContainerRef} >
> <ModalAdd
<ModalAdd
key={this.state.keys.add} key={this.state.keys.add}
layers={this.props.layers} layers={this.props.layers}
sources={this.props.sources} sources={this.props.sources}
isOpen={this.state.isOpen.add} isOpen={this.state.isOpen.add}
onOpenToggle={this.toggleModal.bind(this, "add")} onOpenToggle={this.toggleModal.bind(this, 'add')}
onLayersChange={this.props.onLayersChange} onLayersChange={this.props.onLayersChange}
/> />
<header className="maputnik-layer-list-header"> <header className="maputnik-layer-list-header">
<span className="maputnik-layer-list-header-title">Layers</span> <span className="maputnik-layer-list-header-title">Layers</span>
<span className="maputnik-space" /> <span className="maputnik-space" />
<div className="maputnik-default-property"> <div className="maputnik-default-property">
<div className="maputnik-multibutton"> <div className="maputnik-multibutton">
<button <button
id="skip-target-layer-list" id="skip-target-layer-list"
data-wd-key="skip-target-layer-list" data-wd-key="skip-target-layer-list"
onClick={this.toggleLayers} onClick={this.toggleLayers}
className="maputnik-button" className="maputnik-button">
> {this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
{this.state.areAllGroupsExpanded === true </button>
? "Collapse"
: "Expand"}
</button>
</div>
</div> </div>
<div className="maputnik-default-property">
<div className="maputnik-multibutton">
<button
onClick={this.toggleModal.bind(this, "add")}
data-wd-key="layer-list:add-layer"
className="maputnik-button maputnik-button-selected"
>
Add Layer
</button>
</div>
</div>
</header>
<div role="navigation" aria-label="Layers list">
<ul className="maputnik-layer-list-container">{listItems}</ul>
</div> </div>
</section> <div className="maputnik-default-property">
); <div className="maputnik-multibutton">
<button
onClick={this.toggleModal.bind(this, 'add')}
data-wd-key="layer-list:add-layer"
className="maputnik-button maputnik-button-selected">
Add Layer
</button>
</div>
</div>
</header>
<div
role="navigation"
aria-label="Layers list"
>
<ul className="maputnik-layer-list-container">
{listItems}
</ul>
</div>
</section>
} }
} }
const LayerListContainerSortable = SortableContainer((props) => ( const LayerListContainerSortable = SortableContainer((props) => <LayerListContainer {...props} />)
<LayerListContainer {...props} />
));
export default class LayerList extends React.Component { export default class LayerList extends React.Component {
static propTypes = { ...layerListPropTypes }; static propTypes = {...layerListPropTypes}
render() { render() {
return ( return <LayerListContainerSortable
<LayerListContainerSortable {...this.props}
{...this.props} helperClass='sortableHelper'
helperClass="sortableHelper" onSortEnd={this.props.onMoveLayer.bind(this)}
onSortEnd={this.props.onMoveLayer.bind(this)} useDragHandle={true}
useDragHandle={true} shouldCancelStart={() => false}
shouldCancelStart={() => false} />
/>
);
} }
} }
+101 -123
View File
@@ -1,17 +1,19 @@
import Color from "color"; import React from 'react'
import MapboxInspect from "mapbox-gl-inspect"; import PropTypes from 'prop-types'
import colors from "mapbox-gl-inspect/lib/colors"; import ReactDOM from 'react-dom'
import MapLibreGl from "maplibre-gl"; import MapLibreGl from 'maplibre-gl'
import "maplibre-gl/dist/maplibre-gl.css"; import MapboxInspect from 'mapbox-gl-inspect'
import PropTypes from "prop-types"; import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup'
import React from "react"; import MapMaplibreGlFeaturePropertyPopup from './MapMaplibreGlFeaturePropertyPopup'
import ReactDOM from "react-dom"; import tokens from '../config/tokens.json'
import { colorHighlightedLayer } from "../libs/highlight"; import colors from 'mapbox-gl-inspect/lib/colors'
import "../libs/maplibre-rtl"; import Color from 'color'
import ZoomControl from "../libs/zoomcontrol"; import ZoomControl from '../libs/zoomcontrol'
import "../maplibregl.css"; import { colorHighlightedLayer } from '../libs/highlight'
import MapMaplibreGlFeaturePropertyPopup from "./MapMaplibreGlFeaturePropertyPopup"; import 'maplibre-gl/dist/maplibre-gl.css'
import MapMaplibreGlLayerPopup from "./MapMaplibreGlLayerPopup"; import '../maplibregl.css'
import '../libs/maplibre-rtl'
const IS_SUPPORTED = MapLibreGl.supported(); const IS_SUPPORTED = MapLibreGl.supported();
@@ -22,32 +24,32 @@ function renderPopup(popup, mountNode) {
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) { function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
const backgroundLayer = { const backgroundLayer = {
id: "background", "id": "background",
type: "background", "type": "background",
paint: { "paint": {
"background-color": "#1c1f24", "background-color": '#1c1f24',
}, }
};
const layer = colorHighlightedLayer(highlightedLayer);
if (layer) {
coloredLayers.push(layer);
} }
const sources = {}; const layer = colorHighlightedLayer(highlightedLayer)
Object.keys(originalMapStyle.sources).forEach((sourceId) => { if(layer) {
const source = originalMapStyle.sources[sourceId]; coloredLayers.push(layer)
if (source.type !== "raster" && source.type !== "raster-dem") { }
sources[sourceId] = source;
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 = { const inspectStyle = {
...originalMapStyle, ...originalMapStyle,
sources: sources, sources: sources,
layers: [backgroundLayer].concat(coloredLayers), layers: [backgroundLayer].concat(coloredLayers)
}; }
return inspectStyle; return inspectStyle
} }
export default class MapMaplibreGl extends React.Component { export default class MapMaplibreGl extends React.Component {
@@ -60,7 +62,7 @@ export default class MapMaplibreGl extends React.Component {
options: PropTypes.object, options: PropTypes.object,
replaceAccessTokens: PropTypes.func.isRequired, replaceAccessTokens: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
}; }
static defaultProps = { static defaultProps = {
onMapLoaded: () => {}, onMapLoaded: () => {},
@@ -68,55 +70,51 @@ export default class MapMaplibreGl extends React.Component {
onLayerSelect: () => {}, onLayerSelect: () => {},
onChange: () => {}, onChange: () => {},
options: {}, options: {},
}; }
constructor(props) { constructor(props) {
super(props); super(props)
this.state = { this.state = {
map: null, map: null,
inspect: null, inspect: null,
}; }
} }
updateMapFromProps(props) { 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 //Maplibre GL now does diffing natively so we don't need to calculate
//the necessary operations ourselves! //the necessary operations ourselves!
this.state.map.setStyle(this.props.replaceAccessTokens(props.mapStyle), { this.state.map.setStyle(
diff: true, this.props.replaceAccessTokens(props.mapStyle),
}); {diff: true}
)
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
let should = false; let should = false;
try { try {
should = should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState);
JSON.stringify(this.props) !== JSON.stringify(nextProps) || } catch(e) {
JSON.stringify(this.state) !== JSON.stringify(nextState);
} catch (e) {
// no biggie, carry on // no biggie, carry on
} }
return should; return should;
} }
componentDidUpdate(prevProps, prevState, snapshot) { componentDidUpdate(prevProps, prevState, snapshot) {
if (!IS_SUPPORTED) return; if(!IS_SUPPORTED) return;
const map = this.state.map; const map = this.state.map;
this.updateMapFromProps(this.props); this.updateMapFromProps(this.props);
if ( if(this.state.inspect && this.props.inspectModeEnabled !== this.state.inspect._showInspectMap) {
this.state.inspect &&
this.props.inspectModeEnabled !== this.state.inspect._showInspectMap
) {
// HACK: Fix for <https://github.com/maputnik/editor/issues/576>, while we wait for a proper fix. // HACK: Fix for <https://github.com/maputnik/editor/issues/576>, while we wait for a proper fix.
// eslint-disable-next-line // eslint-disable-next-line
this.state.inspect._popupBlocked = false; this.state.inspect._popupBlocked = false;
this.state.inspect.toggleInspector(); this.state.inspect.toggleInspector()
} }
if (map) { if (map) {
if (this.props.inspectModeEnabled) { if (this.props.inspectModeEnabled) {
@@ -125,7 +123,7 @@ export default class MapMaplibreGl extends React.Component {
// mapbox-gl-inspect. // mapbox-gl-inspect.
try { try {
this.state.inspect.render(); this.state.inspect.render();
} catch (err) { } catch(err) {
console.error("FIXME: Caught error", err); console.error("FIXME: Caught error", err);
} }
} }
@@ -137,40 +135,40 @@ export default class MapMaplibreGl extends React.Component {
} }
componentDidMount() { componentDidMount() {
if (!IS_SUPPORTED) return; if(!IS_SUPPORTED) return;
const mapOpts = { const mapOpts = {
...this.props.options, ...this.props.options,
container: this.container, container: this.container,
style: this.props.mapStyle, style: this.props.mapStyle,
hash: true, hash: true,
maxZoom: 24, maxZoom: 24
}; }
const map = new MapLibreGl.Map(mapOpts); const map = new MapLibreGl.Map(mapOpts);
const mapViewChange = () => { const mapViewChange = () => {
const center = map.getCenter(); const center = map.getCenter();
const zoom = map.getZoom(); const zoom = map.getZoom();
this.props.onChange({ center, zoom }); this.props.onChange({center, zoom});
}; }
mapViewChange(); mapViewChange();
map.showTileBoundaries = mapOpts.showTileBoundaries; map.showTileBoundaries = mapOpts.showTileBoundaries;
map.showCollisionBoxes = mapOpts.showCollisionBoxes; map.showCollisionBoxes = mapOpts.showCollisionBoxes;
map.showOverdrawInspector = mapOpts.showOverdrawInspector; map.showOverdrawInspector = mapOpts.showOverdrawInspector;
const zoomControl = new ZoomControl(); const zoomControl = new ZoomControl;
map.addControl(zoomControl, "top-right"); map.addControl(zoomControl, 'top-right');
const nav = new MapLibreGl.NavigationControl({ visualizePitch: true }); const nav = new MapLibreGl.NavigationControl({visualizePitch:true});
map.addControl(nav, "top-right"); map.addControl(nav, 'top-right');
const tmpNode = document.createElement("div"); const tmpNode = document.createElement('div');
const inspect = new MapboxInspect({ const inspect = new MapboxInspect({
popup: new MapLibreGl.Popup({ popup: new MapLibreGl.Popup({
closeOnClick: false, closeOnClick: false
}), }),
showMapPopup: true, showMapPopup: true,
showMapPopupOnHover: false, showMapPopupOnHover: false,
@@ -178,58 +176,41 @@ export default class MapMaplibreGl extends React.Component {
showInspectButton: false, showInspectButton: false,
blockHoverPopupOnClick: true, blockHoverPopupOnClick: true,
assignLayerColor: (layerId, alpha) => { assignLayerColor: (layerId, alpha) => {
return Color(colors.brightColor(layerId, alpha)) return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string()
.desaturate(0.5)
.string();
}, },
buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
buildInspectStyle( renderPopup: features => {
originalMapStyle, if(this.props.inspectModeEnabled) {
coloredLayers, return renderPopup(<MapMaplibreGlFeaturePropertyPopup features={features} />, tmpNode);
this.props.highlightedLayer
),
renderPopup: (features) => {
if (this.props.inspectModeEnabled) {
return renderPopup(
<MapMaplibreGlFeaturePropertyPopup features={features} />,
tmpNode
);
} else { } else {
return renderPopup( return renderPopup(<MapMaplibreGlLayerPopup features={features} onLayerSelect={this.onLayerSelectById} zoom={this.state.zoom} />, tmpNode);
<MapMaplibreGlLayerPopup
features={features}
onLayerSelect={this.onLayerSelectById}
zoom={this.state.zoom}
/>,
tmpNode
);
} }
}, }
}); })
map.addControl(inspect); map.addControl(inspect)
map.on("style.load", () => { map.on("style.load", () => {
this.setState({ this.setState({
map, map,
inspect, inspect,
zoom: map.getZoom(), zoom: map.getZoom()
}); });
}); })
map.on("data", (e) => { map.on("data", e => {
if (e.dataType !== "tile") return; if(e.dataType !== 'tile') return
this.props.onDataChange({ this.props.onDataChange({
map: this.state.map, map: this.state.map
}); })
}); })
map.on("error", (e) => { map.on("error", e => {
console.log("ERROR", e); console.log("ERROR", e);
}); })
map.on("zoom", (e) => { map.on("zoom", e => {
this.setState({ this.setState({
zoom: map.getZoom(), zoom: map.getZoom()
}); });
}); });
@@ -238,32 +219,29 @@ export default class MapMaplibreGl extends React.Component {
} }
onLayerSelectById = (id) => { onLayerSelectById = (id) => {
const index = this.props.mapStyle.layers.findIndex( const index = this.props.mapStyle.layers.findIndex(layer => layer.id === id);
(layer) => layer.id === id
);
this.props.onLayerSelect(index); this.props.onLayerSelect(index);
}; }
render() { render() {
if (IS_SUPPORTED) { if(IS_SUPPORTED) {
return ( return <div
<div className="maputnik-map__map"
className="maputnik-map__map" role="region"
role="region" aria-label="Map view"
aria-label="Map view" ref={x => this.container = x}
ref={(x) => (this.container = x)} data-wd-key="maplibre:map"
data-wd-key="maplibre:map" ></div>
></div> }
); else {
} else { return <div
return ( className="maputnik-map maputnik-map--error"
<div className="maputnik-map maputnik-map--error"> >
<div className="maputnik-map__error-message"> <div className="maputnik-map__error-message">
Error: Cannot load MaplibreGL, WebGL is either unsupported or Error: Cannot load MaplibreGL, WebGL is either unsupported or disabled
disabled
</div>
</div> </div>
); </div>
} }
} }
} }
+14 -10
View File
@@ -1,12 +1,16 @@
import querystring from 'querystring' interface DebugStore {
[namespace: string]: {
[key: string]: any
}
}
const debugStore: DebugStore = {};
const debugStore = {};
function enabled() { function enabled() {
const qs = querystring.parse(window.location.search.slice(1)); const qs = new URL(window.location.href).searchParams;
if(qs.hasOwnProperty("debug")) { const debugQs = qs.get("debug");
return !!qs.debug.match(/^(|1|true)$/); if(debugQs) {
return !!debugQs.match(/^(|1|true)$/);
} }
else { else {
return false; return false;
@@ -17,7 +21,7 @@ function genErr() {
return new Error("Debug not enabled, enable by appending '?debug' to your query string"); 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()) { if(!enabled()) {
throw genErr(); throw genErr();
} }
@@ -25,7 +29,7 @@ function set(namespace, key, value) {
debugStore[namespace][key] = value; debugStore[namespace][key] = value;
} }
function get(namespace, key) { function get(namespace: keyof DebugStore, key: string) {
if(!enabled()) { if(!enabled()) {
throw genErr(); throw genErr();
} }
@@ -38,7 +42,7 @@ const mod = {
enabled, enabled,
get, get,
set set
} };
window.debug = mod; (window as any).debug = mod;
export default mod; export default mod;
+6 -7
View File
@@ -1,9 +1,8 @@
export default class ZoomControl { export default class ZoomControl {
onAdd(map) { onAdd(map) {
this._map = map; this._map = map;
this._container = document.createElement("div"); this._container = document.createElement('div');
this._container.className = this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-zoom';
"maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-zoom";
this._container.setAttribute("data-wd-key", "maplibre:ctrl-zoom"); this._container.setAttribute("data-wd-key", "maplibre:ctrl-zoom");
this._container.innerHTML = ` this._container.innerHTML = `
Zoom: <span></span> Zoom: <span></span>
@@ -19,10 +18,10 @@ export default class ZoomControl {
this._textEl.innerHTML = this._map.getZoom().toFixed(2); this._textEl.innerHTML = this._map.getZoom().toFixed(2);
} }
addEventListeners() { addEventListeners (){
this._map.on("render", this.updateZoomLevel.bind(this)); this._map.on('render', this.updateZoomLevel.bind(this) );
this._map.on("zoomIn", this.updateZoomLevel.bind(this)); this._map.on('zoomIn', this.updateZoomLevel.bind(this) );
this._map.on("zoomOut", this.updateZoomLevel.bind(this)); this._map.on('zoomOut', this.updateZoomLevel.bind(this) );
} }
onRemove() { onRemove() {
+12 -1
View File
@@ -1,11 +1,22 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import replace from '@rollup/plugin-replace';
export default defineConfig({ export default defineConfig({
server: { server: {
port: 8888 port: 8888
}, },
plugins: [react()], plugins: [
replace({
preventAssignment: true,
include: /\/jsonlint-lines-primitives\/lib\/jsonlint.js/,
delimiters: ['', ''],
values: {
'_token_stack:': ''
}
}) as any,
react()
],
define: { define: {
global: "window", global: "window",
}, },