Merge remote-tracking branch 'upstream/master' into feature/update-ol-plus-stability-fixes

This commit is contained in:
orangemug 2019-05-22 07:51:13 +01:00
commit c1cab38c7a
12 changed files with 231 additions and 43 deletions

5
package-lock.json generated
View file

@ -4116,6 +4116,11 @@
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
"dev": true "dev": true
}, },
"detect-browser": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-4.5.0.tgz",
"integrity": "sha512-uwyZNBwMhvRIOBIBTuDu+h4/a2bfFE/elUIvAOuKuBZcuy6yAJ/9dOe4r3wyWO/ZW2PcsP0dhEwiVwTPTTJp2Q=="
},
"detect-node": { "detect-node": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",

View file

@ -26,6 +26,7 @@
"classnames": "^2.2.6", "classnames": "^2.2.6",
"codemirror": "^5.40.2", "codemirror": "^5.40.2",
"color": "^3.0.0", "color": "^3.0.0",
"detect-browser": "^4.5.0",
"file-saver": "^1.3.8", "file-saver": "^1.3.8",
"jsonlint": "github:josdejong/jsonlint#85a19d7", "jsonlint": "github:josdejong/jsonlint#85a19d7",
"lodash.capitalize": "^4.2.1", "lodash.capitalize": "^4.2.1",

View file

@ -19,6 +19,7 @@ import SourcesModal from './modals/SourcesModal'
import OpenModal from './modals/OpenModal' import OpenModal from './modals/OpenModal'
import ShortcutsModal from './modals/ShortcutsModal' import ShortcutsModal from './modals/ShortcutsModal'
import SurveyModal from './modals/SurveyModal' import SurveyModal from './modals/SurveyModal'
import DebugModal from './modals/DebugModal'
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata' import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
import {latest, validate} from '@mapbox/mapbox-gl-style-spec' import {latest, validate} from '@mapbox/mapbox-gl-style-spec'
@ -139,6 +140,12 @@ export default class App extends React.Component {
document.querySelector(".mapboxgl-canvas").focus(); document.querySelector(".mapboxgl-canvas").focus();
} }
}, },
{
key: "!",
handler: () => {
this.toggleModal("debug");
}
},
] ]
document.body.addEventListener("keyup", (e) => { document.body.addEventListener("keyup", (e) => {
@ -203,12 +210,13 @@ export default class App extends React.Component {
open: false, open: false,
shortcuts: false, shortcuts: false,
export: false, export: false,
survey: localStorage.hasOwnProperty('survey') ? false : true survey: localStorage.hasOwnProperty('survey') ? false : true,
debug: false,
}, },
mapOptions: { mapboxGlDebugOptions: {
showTileBoundaries: queryUtil.asBool(queryObj, "show-tile-boundaries"), showTileBoundaries: false,
showCollisionBoxes: queryUtil.asBool(queryObj, "show-collision-boxes"), showCollisionBoxes: false,
showOverdrawInspector: queryUtil.asBool(queryObj, "show-overdraw-inspector") showOverdrawInspector: false,
}, },
} }
@ -217,20 +225,24 @@ export default class App extends React.Component {
}) })
} }
handleKeyPress(e) { handleKeyPress = (e) => {
if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) { if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
if(e.metaKey && e.shiftKey && e.keyCode === 90) { if(e.metaKey && e.shiftKey && e.keyCode === 90) {
e.preventDefault();
this.onRedo(e); this.onRedo(e);
} }
else if(e.metaKey && e.keyCode === 90) { else if(e.metaKey && e.keyCode === 90) {
e.preventDefault();
this.onUndo(e); this.onUndo(e);
} }
} }
else { else {
if(e.ctrlKey && e.keyCode === 90) { if(e.ctrlKey && e.keyCode === 90) {
e.preventDefault();
this.onUndo(e); this.onUndo(e);
} }
else if(e.ctrlKey && e.keyCode === 89) { else if(e.ctrlKey && e.keyCode === 89) {
e.preventDefault();
this.onRedo(e); this.onRedo(e);
} }
} }
@ -461,18 +473,22 @@ export default class App extends React.Component {
} }
} }
_getRenderer () {
const metadata = this.state.mapStyle.metadata || {};
return metadata['maputnik:renderer'] || 'mbgljs';
}
mapRenderer() { mapRenderer() {
const mapProps = { const mapProps = {
mapStyle: style.replaceAccessTokens(this.state.mapStyle, {allowFallback: true}), mapStyle: style.replaceAccessTokens(this.state.mapStyle, {allowFallback: true}),
options: this.state.mapOptions, options: this.state.mapboxGlDebugOptions,
onDataChange: (e) => { onDataChange: (e) => {
this.layerWatcher.analyzeMap(e.map) this.layerWatcher.analyzeMap(e.map)
this.fetchSources(); this.fetchSources();
}, },
} }
const metadata = this.state.mapStyle.metadata || {} const renderer = this._getRenderer();
const renderer = metadata['maputnik:renderer'] || 'mbgljs'
let mapElement; let mapElement;
@ -524,6 +540,15 @@ export default class App extends React.Component {
this.setModal(modalName, !this.state.isOpen[modalName]); this.setModal(modalName, !this.state.isOpen[modalName]);
} }
onChangeMaboxGlDebug = (key, value) => {
this.setState({
mapboxGlDebugOptions: {
...this.state.mapboxGlDebugOptions,
[key]: value,
}
});
}
render() { render() {
const layers = this.state.mapStyle.layers || [] const layers = this.state.mapStyle.layers || []
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null
@ -575,6 +600,13 @@ export default class App extends React.Component {
const modals = <div> const modals = <div>
<DebugModal
renderer={this._getRenderer()}
mapboxGlDebugOptions={this.state.mapboxGlDebugOptions}
onChangeMaboxGlDebug={this.onChangeMaboxGlDebug}
isOpen={this.state.isOpen.debug}
onOpenToggle={this.toggleModal.bind(this, 'debug')}
/>
<ShortcutsModal <ShortcutsModal
ref={(el) => this.shortcutEl = el} ref={(el) => this.shortcutEl = el}
isOpen={this.state.isOpen.shortcuts} isOpen={this.state.isOpen.shortcuts}

View file

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import {detect} from 'detect-browser';
import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage, MdAssignmentTurnedIn} from 'react-icons/md' import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage, MdAssignmentTurnedIn} from 'react-icons/md'
@ -9,6 +10,11 @@ import logoImage from 'maputnik-design/logos/logo-color.svg'
import pkgJson from '../../package.json' 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.
const browser = detect();
const colorAccessibilityFiltersEnabled = ['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,
@ -137,18 +143,22 @@ export default class Toolbar extends React.Component {
{ {
id: "filter-deuteranopia", id: "filter-deuteranopia",
title: "Map (deuteranopia)", title: "Map (deuteranopia)",
disabled: !colorAccessibilityFiltersEnabled,
}, },
{ {
id: "filter-protanopia", id: "filter-protanopia",
title: "Map (protanopia)", title: "Map (protanopia)",
disabled: !colorAccessibilityFiltersEnabled,
}, },
{ {
id: "filter-tritanopia", id: "filter-tritanopia",
title: "Map (tritanopia)", title: "Map (tritanopia)",
disabled: !colorAccessibilityFiltersEnabled,
}, },
{ {
id: "filter-achromatopsia", id: "filter-achromatopsia",
title: "Map (achromatopsia)", title: "Map (achromatopsia)",
disabled: !colorAccessibilityFiltersEnabled,
}, },
]; ];
@ -201,7 +211,7 @@ export default class Toolbar extends React.Component {
<select onChange={(e) => this.handleSelection(e.target.value)} value={currentView.id}> <select onChange={(e) => this.handleSelection(e.target.value)} value={currentView.id}>
{views.map((item) => { {views.map((item) => {
return ( return (
<option key={item.id} value={item.id}> <option key={item.id} value={item.id} disabled={item.disabled}>
{item.title} {item.title}
</option> </option>
); );

View file

@ -13,25 +13,28 @@ class NumberInput extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
value: props.value editing: false,
value: props.value,
} }
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props, state) {
return { if (!state.editing) {
value: props.value return {
}; value: props.value
};
}
} }
changeValue(newValue) { changeValue(newValue) {
this.setState({editing: true});
const value = parseFloat(newValue) const value = parseFloat(newValue)
const hasChanged = this.state.value !== value const hasChanged = this.state.value !== value
if(this.isValid(value) && hasChanged) { if(this.isValid(value) && hasChanged) {
this.props.onChange(value) this.props.onChange(value)
} else {
this.setState({ value: newValue })
} }
this.setState({ value: newValue })
} }
isValid(v) { isValid(v) {
@ -52,6 +55,7 @@ class NumberInput extends React.Component {
} }
resetValue = () => { resetValue = () => {
this.setState({editing: false});
// Reset explicitly to default value if value has been cleared // Reset explicitly to default value if value has been cleared
if(this.state.value === "") { if(this.state.value === "") {
return this.changeValue(this.props.default) return this.changeValue(this.props.default)

View file

@ -14,13 +14,16 @@ class StringInput extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
editing: false,
value: props.value || '' value: props.value || ''
} }
} }
componentDidUpdate(prevProps) { static getDerivedStateFromProps(props, state) {
if(this.props.value !== prevProps.value) { if (!state.editing) {
this.setState({value: this.props.value}) return {
value: props.value
};
} }
} }
@ -51,11 +54,15 @@ class StringInput extends React.Component {
placeholder: this.props.default, placeholder: this.props.default,
onChange: e => { onChange: e => {
this.setState({ this.setState({
editing: true,
value: e.target.value value: e.target.value
}) })
}, },
onBlur: () => { onBlur: () => {
if(this.state.value!==this.props.value) this.props.onChange(this.state.value) if(this.state.value!==this.props.value) {
this.setState({editing: false});
this.props.onChange(this.state.value);
}
} }
}); });
} }

View file

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import LayerIcon from '../icons/LayerIcon' import LayerIcon from '../icons/LayerIcon'
import {latest, expression, function as styleFunction} from '@mapbox/mapbox-gl-style-spec'
function groupFeaturesBySourceLayer(features) { function groupFeaturesBySourceLayer(features) {
const sources = {} const sources = {}
@ -28,7 +29,59 @@ function groupFeaturesBySourceLayer(features) {
class FeatureLayerPopup extends React.Component { class FeatureLayerPopup extends React.Component {
static propTypes = { static propTypes = {
onLayerSelect: PropTypes.func.isRequired, onLayerSelect: PropTypes.func.isRequired,
features: PropTypes.array features: PropTypes.array,
zoom: PropTypes.number,
}
_getFeatureColor(feature, zoom) {
try {
const paintProps = feature.layer.paint;
let propName;
if(paintProps.hasOwnProperty("text-color") && paintProps["text-color"]) {
propName = "text-color";
}
else if (paintProps.hasOwnProperty("fill-color") && paintProps["fill-color"]) {
propName = "fill-color";
}
else if (paintProps.hasOwnProperty("line-color") && paintProps["line-color"]) {
propName = "line-color";
}
else if (paintProps.hasOwnProperty("fill-extrusion-color") && paintProps["fill-extrusion-color"]) {
propName = "fill-extrusion-color";
}
if(propName) {
const propertySpec = latest["paint_"+feature.layer.type][propName];
let color = feature.layer.paint[propName];
if(typeof(color) === "object") {
if(color.stops) {
color = styleFunction.convertFunction(color, propertySpec);
}
const exprResult = expression.createExpression(color, propertySpec);
const val = exprResult.value.evaluate({
zoom: zoom
}, feature);
return val.toString();
}
else {
return color;
}
}
else {
// Default color
return "black";
}
}
// This is quite complex, just incase there's an edgecase we're missing
// always return black if we get an unexpected error.
catch (err) {
console.error("Unable to get feature color, error:", err);
return "black";
}
} }
render() { render() {
@ -36,21 +89,31 @@ class FeatureLayerPopup extends React.Component {
const items = Object.keys(sources).map(vectorLayerId => { const items = Object.keys(sources).map(vectorLayerId => {
const layers = sources[vectorLayerId].map((feature, idx) => { const layers = sources[vectorLayerId].map((feature, idx) => {
return <label const featureColor = this._getFeatureColor(feature, this.props.zoom);
key={idx}
className="maputnik-popup-layer" return <div
onClick={() => { key={idx}
this.props.onLayerSelect(feature.layer.id) className="maputnik-popup-layer"
}}
> >
<LayerIcon type={feature.layer.type} style={{ <div
width: 14, className="maputnik-popup-layer__swatch"
height: 14, style={{background: featureColor}}
paddingRight: 3 ></div>
}}/> <label
{feature.layer.id} className="maputnik-popup-layer__label"
{feature.counter && <span> × {feature.counter}</span>} onClick={() => {
</label> this.props.onLayerSelect(feature.layer.id)
}}
>
<LayerIcon type={feature.layer.type} style={{
width: 14,
height: 14,
paddingRight: 3
}}/>
{feature.layer.id}
{feature.counter && <span> × {feature.counter}</span>}
</label>
</div>
}) })
return <div key={vectorLayerId}> return <div key={vectorLayerId}>
<div className="maputnik-popup-layer-id">{vectorLayerId}</div> <div className="maputnik-popup-layer-id">{vectorLayerId}</div>

View file

@ -166,14 +166,18 @@ export default class MapboxGlMap extends React.Component {
if(this.props.inspectModeEnabled) { if(this.props.inspectModeEnabled) {
return renderPopup(<FeaturePropertyPopup features={features} />, tmpNode); return renderPopup(<FeaturePropertyPopup features={features} />, tmpNode);
} else { } else {
return renderPopup(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} />, tmpNode); return renderPopup(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} zoom={this.state.zoom} />, tmpNode);
} }
} }
}) })
map.addControl(inspect) map.addControl(inspect)
map.on("style.load", () => { map.on("style.load", () => {
this.setState({ map, inspect }); this.setState({
map,
inspect,
zoom: map.getZoom()
});
if(this.props.inspectModeEnabled) { if(this.props.inspectModeEnabled) {
inspect.toggleInspector(); inspect.toggleInspector();
} }
@ -185,6 +189,12 @@ export default class MapboxGlMap extends React.Component {
map: this.state.map map: this.state.map
}) })
}) })
map.on("zoom", e => {
this.setState({
zoom: map.getZoom()
});
})
} }
render() { render() {

View file

@ -56,12 +56,8 @@ export default class OpenLayersMap extends React.Component {
return <div return <div
ref={x => this.container = x} ref={x => this.container = x}
style={{ style={{
position: "fixed", width: "100%",
top: 40, height: "100%",
right: 0,
bottom: 0,
height: 'calc(100% - 40px)',
width: "75%",
backgroundColor: '#fff', backgroundColor: '#fff',
...this.props.style, ...this.props.style,
}}> }}>

View file

@ -0,0 +1,45 @@
import React from 'react'
import PropTypes from 'prop-types'
import Modal from './Modal'
class DebugModal extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
renderer: PropTypes.string.isRequired,
onChangeMaboxGlDebug: PropTypes.func.isRequired,
onOpenToggle: PropTypes.func.isRequired,
mapboxGlDebugOptions: PropTypes.object,
}
render() {
return <Modal
data-wd-key="debug-modal"
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title={'Debug'}
>
<div className="maputnik-modal-section maputnik-modal-shortcuts">
{this.props.renderer === 'mbgljs' &&
<ul>
{Object.entries(this.props.mapboxGlDebugOptions).map(([key, val]) => {
return <li key={key}>
<label>
<input type="checkbox" checked={val} onClick={(e) => this.props.onChangeMaboxGlDebug(key, e.target.checked)} /> {key}
</label>
</li>
})}
</ul>
}
{this.props.renderer === 'ol' &&
<div>
No debug options available for the OpenLayers renderer
</div>
}
</div>
</Modal>
}
}
export default DebugModal;

View file

@ -40,6 +40,10 @@ class ShortcutsModal extends React.Component {
key: "m", key: "m",
text: "Focus map" text: "Focus map"
}, },
{
key: "!",
text: "Debug modal"
},
] ]

View file

@ -1,4 +1,15 @@
.maputnik-popup-layer { .maputnik-popup-layer {
display: flex;
flex-direction: row;
}
.maputnik-popup-layer__swatch {
display: inline-block;
width: 5px;
align-content: stretch;
}
.maputnik-popup-layer__label {
display: block; display: block;
color: $color-lowgray; color: $color-lowgray;
cursor: pointer; cursor: pointer;