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=",
"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": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import {detect} from 'detect-browser';
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'
// 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 {
static propTypes = {
children: PropTypes.node,
@ -137,18 +143,22 @@ export default class Toolbar extends React.Component {
{
id: "filter-deuteranopia",
title: "Map (deuteranopia)",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-protanopia",
title: "Map (protanopia)",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-tritanopia",
title: "Map (tritanopia)",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-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}>
{views.map((item) => {
return (
<option key={item.id} value={item.id}>
<option key={item.id} value={item.id} disabled={item.disabled}>
{item.title}
</option>
);

View file

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

View file

@ -14,13 +14,16 @@ class StringInput extends React.Component {
constructor(props) {
super(props)
this.state = {
editing: false,
value: props.value || ''
}
}
componentDidUpdate(prevProps) {
if(this.props.value !== prevProps.value) {
this.setState({value: this.props.value})
static getDerivedStateFromProps(props, state) {
if (!state.editing) {
return {
value: props.value
};
}
}
@ -51,11 +54,15 @@ class StringInput extends React.Component {
placeholder: this.props.default,
onChange: e => {
this.setState({
editing: true,
value: e.target.value
})
},
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 PropTypes from 'prop-types'
import LayerIcon from '../icons/LayerIcon'
import {latest, expression, function as styleFunction} from '@mapbox/mapbox-gl-style-spec'
function groupFeaturesBySourceLayer(features) {
const sources = {}
@ -28,7 +29,59 @@ function groupFeaturesBySourceLayer(features) {
class FeatureLayerPopup extends React.Component {
static propTypes = {
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() {
@ -36,21 +89,31 @@ class FeatureLayerPopup extends React.Component {
const items = Object.keys(sources).map(vectorLayerId => {
const layers = sources[vectorLayerId].map((feature, idx) => {
return <label
key={idx}
className="maputnik-popup-layer"
onClick={() => {
this.props.onLayerSelect(feature.layer.id)
}}
const featureColor = this._getFeatureColor(feature, this.props.zoom);
return <div
key={idx}
className="maputnik-popup-layer"
>
<LayerIcon type={feature.layer.type} style={{
width: 14,
height: 14,
paddingRight: 3
}}/>
{feature.layer.id}
{feature.counter && <span> × {feature.counter}</span>}
</label>
<div
className="maputnik-popup-layer__swatch"
style={{background: featureColor}}
></div>
<label
className="maputnik-popup-layer__label"
onClick={() => {
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}>
<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) {
return renderPopup(<FeaturePropertyPopup features={features} />, tmpNode);
} 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.on("style.load", () => {
this.setState({ map, inspect });
this.setState({
map,
inspect,
zoom: map.getZoom()
});
if(this.props.inspectModeEnabled) {
inspect.toggleInspector();
}
@ -185,6 +189,12 @@ export default class MapboxGlMap extends React.Component {
map: this.state.map
})
})
map.on("zoom", e => {
this.setState({
zoom: map.getZoom()
});
})
}
render() {

View file

@ -56,12 +56,8 @@ export default class OpenLayersMap extends React.Component {
return <div
ref={x => this.container = x}
style={{
position: "fixed",
top: 40,
right: 0,
bottom: 0,
height: 'calc(100% - 40px)',
width: "75%",
width: "100%",
height: "100%",
backgroundColor: '#fff',
...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",
text: "Focus map"
},
{
key: "!",
text: "Debug modal"
},
]

View file

@ -1,4 +1,15 @@
.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;
color: $color-lowgray;
cursor: pointer;