mirror of
https://github.com/maputnik/editor.git
synced 2025-12-27 08:30:02 +00:00
Merge remote-tracking branch 'upstream/master' into feature/issue-433
This commit is contained in:
@@ -23,7 +23,7 @@ import SurveyModal from './modals/SurveyModal'
|
||||
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
|
||||
import {latest, validate} from '@mapbox/mapbox-gl-style-spec'
|
||||
import style from '../libs/style'
|
||||
import { initialStyleUrl, loadStyleUrl } from '../libs/urlopen'
|
||||
import { initialStyleUrl, loadStyleUrl, removeStyleQuerystring } from '../libs/urlopen'
|
||||
import { undoMessages, redoMessages } from '../libs/diffmessage'
|
||||
import { StyleStore } from '../libs/stylestore'
|
||||
import { ApiStyleStore } from '../libs/apistore'
|
||||
@@ -49,6 +49,26 @@ function normalizeSourceURL (url, apiToken="") {
|
||||
}
|
||||
}
|
||||
|
||||
function setFetchAccessToken(url, mapStyle) {
|
||||
const matchesTilehosting = url.match(/\.tilehosting\.com/);
|
||||
const matchesMaptiler = url.match(/\.maptiler\.com/);
|
||||
const matchesThunderforest = url.match(/\.thunderforest\.com/);
|
||||
if (matchesTilehosting || matchesMaptiler) {
|
||||
const accessToken = style.getAccessToken("openmaptiles", mapStyle, {allowFallback: true})
|
||||
if (accessToken) {
|
||||
return url.replace('{key}', accessToken)
|
||||
}
|
||||
}
|
||||
else if (matchesThunderforest) {
|
||||
const accessToken = style.getAccessToken("thunderforest", mapStyle, {allowFallback: true})
|
||||
if (accessToken) {
|
||||
return url.replace('{key}', accessToken)
|
||||
}
|
||||
}
|
||||
else {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
function updateRootSpec(spec, fieldName, newValues) {
|
||||
return {
|
||||
@@ -73,57 +93,48 @@ export default class App extends React.Component {
|
||||
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false)
|
||||
})
|
||||
|
||||
|
||||
const keyCodes = {
|
||||
"esc": 27,
|
||||
"?": 191,
|
||||
"o": 79,
|
||||
"e": 69,
|
||||
"s": 83,
|
||||
"d": 68,
|
||||
"i": 73,
|
||||
"m": 77,
|
||||
}
|
||||
|
||||
const shortcuts = [
|
||||
{
|
||||
keyCode: keyCodes["?"],
|
||||
key: "?",
|
||||
handler: () => {
|
||||
this.toggleModal("shortcuts");
|
||||
}
|
||||
},
|
||||
{
|
||||
keyCode: keyCodes["o"],
|
||||
key: "o",
|
||||
handler: () => {
|
||||
this.toggleModal("open");
|
||||
}
|
||||
},
|
||||
{
|
||||
keyCode: keyCodes["e"],
|
||||
key: "e",
|
||||
handler: () => {
|
||||
this.toggleModal("export");
|
||||
}
|
||||
},
|
||||
{
|
||||
keyCode: keyCodes["d"],
|
||||
key: "d",
|
||||
handler: () => {
|
||||
this.toggleModal("sources");
|
||||
}
|
||||
},
|
||||
{
|
||||
keyCode: keyCodes["s"],
|
||||
key: "s",
|
||||
handler: () => {
|
||||
this.toggleModal("settings");
|
||||
}
|
||||
},
|
||||
{
|
||||
keyCode: keyCodes["i"],
|
||||
key: "i",
|
||||
handler: () => {
|
||||
this.setMapState("inspect");
|
||||
this.setMapState(
|
||||
this.state.mapState === "map" ? "inspect" : "map"
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
keyCode: keyCodes["m"],
|
||||
key: "m",
|
||||
handler: () => {
|
||||
document.querySelector(".mapboxgl-canvas").focus();
|
||||
}
|
||||
@@ -131,26 +142,31 @@ export default class App extends React.Component {
|
||||
]
|
||||
|
||||
document.body.addEventListener("keyup", (e) => {
|
||||
if(e.keyCode === keyCodes["esc"]) {
|
||||
if(e.key === "Escape") {
|
||||
e.target.blur();
|
||||
document.body.focus();
|
||||
}
|
||||
else if(document.activeElement === document.body) {
|
||||
else if(this.state.isOpen.shortcuts || document.activeElement === document.body) {
|
||||
const shortcut = shortcuts.find((shortcut) => {
|
||||
return (shortcut.keyCode === e.keyCode)
|
||||
return (shortcut.key === e.key)
|
||||
})
|
||||
|
||||
if(shortcut) {
|
||||
this.setModal("shortcuts", false);
|
||||
shortcut.handler(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const styleUrl = initialStyleUrl()
|
||||
if(styleUrl) {
|
||||
if(styleUrl && window.confirm("Load style from URL: " + styleUrl + " and discard current changes?")) {
|
||||
this.styleStore = new StyleStore()
|
||||
loadStyleUrl(styleUrl, mapStyle => this.onStyleChanged(mapStyle))
|
||||
removeStyleQuerystring()
|
||||
} else {
|
||||
if(styleUrl) {
|
||||
removeStyleQuerystring()
|
||||
}
|
||||
this.styleStore.init(err => {
|
||||
if(err) {
|
||||
console.log('Falling back to local storage for storing styles')
|
||||
@@ -191,7 +207,8 @@ export default class App extends React.Component {
|
||||
},
|
||||
mapOptions: {
|
||||
showTileBoundaries: queryUtil.asBool(queryObj, "show-tile-boundaries"),
|
||||
showCollisionBoxes: queryUtil.asBool(queryObj, "show-collision-boxes")
|
||||
showCollisionBoxes: queryUtil.asBool(queryObj, "show-collision-boxes"),
|
||||
showOverdrawInspector: queryUtil.asBool(queryObj, "show-overdraw-inspector")
|
||||
},
|
||||
}
|
||||
|
||||
@@ -401,6 +418,12 @@ export default class App extends React.Component {
|
||||
console.warn("Failed to normalizeSourceURL: ", err);
|
||||
}
|
||||
|
||||
try {
|
||||
url = setFetchAccessToken(url, this.state.mapStyle)
|
||||
} catch(err) {
|
||||
console.warn("Failed to setFetchAccessToken: ", err);
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
mode: 'cors',
|
||||
})
|
||||
@@ -484,17 +507,21 @@ export default class App extends React.Component {
|
||||
this.setState({ selectedLayerIndex: idx })
|
||||
}
|
||||
|
||||
toggleModal(modalName) {
|
||||
setModal(modalName, value) {
|
||||
if(modalName === 'survey' && value === false) {
|
||||
localStorage.setItem('survey', '');
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isOpen: {
|
||||
...this.state.isOpen,
|
||||
[modalName]: !this.state.isOpen[modalName]
|
||||
[modalName]: value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(modalName === 'survey') {
|
||||
localStorage.setItem('survey', '');
|
||||
}
|
||||
toggleModal(modalName) {
|
||||
this.setModal(modalName, !this.state.isOpen[modalName]);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -549,6 +576,7 @@ export default class App extends React.Component {
|
||||
|
||||
const modals = <div>
|
||||
<ShortcutsModal
|
||||
ref={(el) => this.shortcutEl = el}
|
||||
isOpen={this.state.isOpen.shortcuts}
|
||||
onOpenToggle={this.toggleModal.bind(this, 'shortcuts')}
|
||||
/>
|
||||
|
||||
@@ -198,7 +198,7 @@ export default class Toolbar extends React.Component {
|
||||
<ToolbarSelect wdKey="nav:inspect">
|
||||
<MdFindInPage />
|
||||
<IconText>View </IconText>
|
||||
<select onChange={(e) => this.handleSelection(e.target.value)}>
|
||||
<select onChange={(e) => this.handleSelection(e.target.value)} value={currentView.id}>
|
||||
{views.map((item) => {
|
||||
return (
|
||||
<option key={item.id} value={item.id}>
|
||||
|
||||
@@ -81,6 +81,7 @@ export default class SpecField extends React.Component {
|
||||
options={options}
|
||||
/>
|
||||
}
|
||||
case 'formatted':
|
||||
case 'string':
|
||||
if(iconProperties.indexOf(this.props.fieldName) >= 0) {
|
||||
return <IconInput
|
||||
|
||||
@@ -54,7 +54,8 @@ class AutocompleteInput extends React.Component {
|
||||
menuStyle={{
|
||||
position: "fixed",
|
||||
overflow: "auto",
|
||||
maxHeight: this.state.maxHeight
|
||||
maxHeight: this.state.maxHeight,
|
||||
zIndex: '998'
|
||||
}}
|
||||
wrapperProps={{
|
||||
className: "maputnik-autocomplete",
|
||||
|
||||
@@ -5,7 +5,7 @@ import AutocompleteInput from './AutocompleteInput'
|
||||
|
||||
class IconInput extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.array,
|
||||
value: PropTypes.string,
|
||||
icons: PropTypes.array,
|
||||
style: PropTypes.object,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
|
||||
@@ -29,20 +29,11 @@ class JSONEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
return {
|
||||
code: JSON.stringify(props.layer, null, 2)
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
try {
|
||||
const parsedLayer = JSON.parse(this.state.code)
|
||||
// If the structure is still the same do not update
|
||||
// because it affects editing experience by reformatting all the time
|
||||
return nextState.code !== JSON.stringify(parsedLayer, null, 2)
|
||||
} catch(err) {
|
||||
return true
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.layer !== this.props.layer) {
|
||||
this.setState({
|
||||
code: JSON.stringify(this.props.layer, null, 2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class LayerListContainer extends React.Component {
|
||||
areAllGroupsExpanded: false,
|
||||
isOpen: {
|
||||
add: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleModal(modalName) {
|
||||
@@ -66,12 +66,12 @@ class LayerListContainer extends React.Component {
|
||||
this.groupedLayers().forEach(layers => {
|
||||
const groupPrefix = layerPrefix(layers[0].id)
|
||||
const lookupKey = [groupPrefix, idx].join('-')
|
||||
|
||||
|
||||
|
||||
if (layers.length > 1) {
|
||||
newGroups[lookupKey] = this.state.areAllGroupsExpanded
|
||||
}
|
||||
|
||||
|
||||
layers.forEach((layer) => {
|
||||
idx += 1
|
||||
})
|
||||
@@ -204,6 +204,7 @@ export default class LayerList extends React.Component {
|
||||
render() {
|
||||
return <LayerListContainerSortable
|
||||
{...this.props}
|
||||
helperClass='sortableHelper'
|
||||
onSortEnd={this.props.onMoveLayer.bind(this)}
|
||||
useDragHandle={true}
|
||||
/>
|
||||
|
||||
@@ -7,23 +7,16 @@ import {MdContentCopy, MdVisibility, MdVisibilityOff, MdDelete} from 'react-icon
|
||||
import LayerIcon from '../icons/LayerIcon'
|
||||
import {SortableElement, SortableHandle} from 'react-sortable-hoc'
|
||||
|
||||
class LayerTypeDragHandle extends React.Component {
|
||||
static propTypes = LayerIcon.propTypes
|
||||
|
||||
render() {
|
||||
return <LayerIcon
|
||||
{...this.props}
|
||||
style={{
|
||||
cursor: 'move',
|
||||
width: 14,
|
||||
height: 14,
|
||||
paddingRight: 3,
|
||||
}}
|
||||
const DraggableLabel = SortableHandle((props) => {
|
||||
return <div className="maputnik-layer-list-item-handle">
|
||||
<LayerIcon
|
||||
className="layer-handle__icon"
|
||||
type={props.layerType}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
const LayerTypeDragHandleSortable = SortableHandle((props) => <LayerTypeDragHandle {...props} />)
|
||||
<span className="maputnik-layer-list-item-id">{props.layerId}</span>
|
||||
</div>
|
||||
});
|
||||
|
||||
class IconAction extends React.Component {
|
||||
static propTypes = {
|
||||
@@ -111,8 +104,7 @@ class LayerListItem extends React.Component {
|
||||
"maputnik-layer-list-item-selected": this.props.isSelected,
|
||||
[this.props.className]: true,
|
||||
})}>
|
||||
<LayerTypeDragHandleSortable type={this.props.layerType} />
|
||||
<span className="maputnik-layer-list-item-id">{this.props.layerId}</span>
|
||||
<DraggableLabel {...this.props} />
|
||||
<span style={{flexGrow: 1}} />
|
||||
<IconAction
|
||||
wdKey={"layer-list-item:"+this.props.layerId+":delete"}
|
||||
|
||||
@@ -22,7 +22,7 @@ function renderProperties(feature) {
|
||||
}
|
||||
|
||||
function renderFeature(feature) {
|
||||
return <div key={feature.id}>
|
||||
return <div key={`${feature.sourceLayer}-${feature.id}`}>
|
||||
<div className="maputnik-popup-layer-id">{feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div>
|
||||
<InputBlock key={"property-type"} label={"$type"}>
|
||||
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
|
||||
@@ -43,7 +43,7 @@ function removeDuplicatedFeatures(features) {
|
||||
if(featureIndex === -1) {
|
||||
uniqueFeatures.push(feature)
|
||||
} else {
|
||||
if(uniqueFeatures[featureIndex].hasOwnProperty('counter')) {
|
||||
if(uniqueFeatures[featureIndex].hasOwnProperty('inspectModeCounter')) {
|
||||
uniqueFeatures[featureIndex].inspectModeCounter++
|
||||
} else {
|
||||
uniqueFeatures[featureIndex].inspectModeCounter = 2
|
||||
|
||||
@@ -17,10 +17,10 @@ import '../../libs/mapbox-rtl'
|
||||
|
||||
const IS_SUPPORTED = MapboxGl.supported();
|
||||
|
||||
function renderPropertyPopup(features) {
|
||||
var mountNode = document.createElement('div');
|
||||
ReactDOM.render(<FeaturePropertyPopup features={features} />, mountNode)
|
||||
return mountNode.innerHTML;
|
||||
function renderPopup(popup, mountNode) {
|
||||
ReactDOM.render(popup, mountNode);
|
||||
var content = mountNode.innerHTML;
|
||||
return content;
|
||||
}
|
||||
|
||||
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
|
||||
@@ -77,9 +77,6 @@ export default class MapboxGlMap extends React.Component {
|
||||
this.state = {
|
||||
map: null,
|
||||
inspect: null,
|
||||
isPopupOpen: false,
|
||||
popupX: 0,
|
||||
popupY: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +94,16 @@ export default class MapboxGlMap extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
let should = false;
|
||||
try {
|
||||
should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState);
|
||||
} catch(e) {
|
||||
// no biggie, carry on
|
||||
}
|
||||
return should;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if(!IS_SUPPORTED) return;
|
||||
|
||||
@@ -114,6 +121,7 @@ export default class MapboxGlMap extends React.Component {
|
||||
if (map) {
|
||||
map.showTileBoundaries = this.props.options.showTileBoundaries;
|
||||
map.showCollisionBoxes = this.props.options.showCollisionBoxes;
|
||||
map.showOverdrawInspector = this.props.options.showOverdrawInspector;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +139,7 @@ export default class MapboxGlMap extends React.Component {
|
||||
|
||||
map.showTileBoundaries = mapOpts.showTileBoundaries;
|
||||
map.showCollisionBoxes = mapOpts.showCollisionBoxes;
|
||||
map.showOverdrawInspector = mapOpts.showOverdrawInspector;
|
||||
|
||||
const zoom = new ZoomControl;
|
||||
map.addControl(zoom, 'top-right');
|
||||
@@ -138,6 +147,8 @@ export default class MapboxGlMap extends React.Component {
|
||||
const nav = new MapboxGl.NavigationControl();
|
||||
map.addControl(nav, 'top-right');
|
||||
|
||||
const tmpNode = document.createElement('div');
|
||||
|
||||
const inspect = new MapboxInspect({
|
||||
popup: new MapboxGl.Popup({
|
||||
closeOnClick: false
|
||||
@@ -153,11 +164,9 @@ export default class MapboxGlMap extends React.Component {
|
||||
buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
|
||||
renderPopup: features => {
|
||||
if(this.props.inspectModeEnabled) {
|
||||
return renderPropertyPopup(features)
|
||||
return renderPopup(<FeaturePropertyPopup features={features} />, tmpNode);
|
||||
} else {
|
||||
var mountNode = document.createElement('div');
|
||||
ReactDOM.render(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} zoom={this.state.zoom} />, mountNode)
|
||||
return mountNode
|
||||
return renderPopup(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} zoom={this.state.zoom} />, tmpNode);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -169,6 +178,9 @@ export default class MapboxGlMap extends React.Component {
|
||||
inspect,
|
||||
zoom: map.getZoom()
|
||||
});
|
||||
if(this.props.inspectModeEnabled) {
|
||||
inspect.toggleInspector();
|
||||
}
|
||||
})
|
||||
|
||||
map.on("data", e => {
|
||||
|
||||
Reference in New Issue
Block a user