mirror of
https://github.com/maputnik/editor.git
synced 2026-04-29 12:50:00 +00:00
Compare commits
9 Commits
v1.1.0-bet
...
v1.1.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
664125d820 | ||
|
|
9ae2f2c5af | ||
|
|
721f9b36b3 | ||
|
|
a33d1b819c | ||
|
|
3c0ebfabab | ||
|
|
0ba11b94c8 | ||
|
|
390e90e8c2 | ||
|
|
6a6595d971 | ||
|
|
ace6812e89 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "maputnik",
|
"name": "maputnik",
|
||||||
"version": "1.1.0-beta",
|
"version": "1.1.0-beta2",
|
||||||
"description": "A MapboxGL visual style editor",
|
"description": "A MapboxGL visual style editor",
|
||||||
"main": "''",
|
"main": "''",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"mapbox-gl": "^0.43.0",
|
"mapbox-gl": "^0.43.0",
|
||||||
"mapbox-gl-inspect": "^1.2.4",
|
"mapbox-gl-inspect": "^1.2.5",
|
||||||
"maputnik-design": "github:maputnik/design",
|
"maputnik-design": "github:maputnik/design",
|
||||||
"mousetrap": "^1.6.1",
|
"mousetrap": "^1.6.1",
|
||||||
"ol-mapbox-style": "^1.0.1",
|
"ol-mapbox-style": "^1.0.1",
|
||||||
|
|||||||
@@ -249,7 +249,8 @@ export default class App extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
return <MapboxGlMap {...mapProps}
|
return <MapboxGlMap {...mapProps}
|
||||||
inspectModeEnabled={this.state.inspectModeEnabled}
|
inspectModeEnabled={this.state.inspectModeEnabled}
|
||||||
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]} />
|
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
|
||||||
|
onLayerSelect={this.onLayerSelect.bind(this)} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,24 @@ import Autocomplete from 'react-autocomplete'
|
|||||||
|
|
||||||
const MAX_HEIGHT = 140;
|
const MAX_HEIGHT = 140;
|
||||||
|
|
||||||
class AutocompleteMenu extends React.Component {
|
class AutocompleteInput extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
keepMenuWithinWindowBounds: PropTypes.bool,
|
value: PropTypes.string,
|
||||||
style: PropTypes.object,
|
options: PropTypes.array,
|
||||||
children: PropTypes.node
|
onChange: PropTypes.func,
|
||||||
|
keepMenuWithinWindowBounds: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
onChange: () => {},
|
||||||
|
options: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
maxHeight: MAX_HEIGHT
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
calcMaxHeight() {
|
calcMaxHeight() {
|
||||||
@@ -33,81 +46,46 @@ class AutocompleteMenu extends React.Component {
|
|||||||
this.calcMaxHeight();
|
this.calcMaxHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
maxHeight: MAX_HEIGHT
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
style: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const maxHeight = this.state.maxHeight - this.props.style.marginBottom || 0;
|
return <div
|
||||||
const style = {
|
ref={(el) => {
|
||||||
maxHeight: maxHeight+"px"
|
this.autocompleteMenuEl = el;
|
||||||
}
|
}}
|
||||||
|
>
|
||||||
return (
|
<Autocomplete
|
||||||
<div
|
menuStyle={{
|
||||||
ref={(el) => {
|
position: "absolute",
|
||||||
this.autocompleteMenuEl = el;
|
overflow: "auto",
|
||||||
|
maxHeight: this.state.maxHeight
|
||||||
}}
|
}}
|
||||||
className={"maputnik-autocomplete-menu"}
|
wrapperProps={{
|
||||||
style={style}
|
className: "maputnik-autocomplete",
|
||||||
>
|
style: null
|
||||||
{this.props.children}
|
}}
|
||||||
</div>
|
inputProps={{
|
||||||
)
|
className: "maputnik-string"
|
||||||
}
|
}}
|
||||||
}
|
value={this.props.value}
|
||||||
|
items={this.props.options}
|
||||||
class AutocompleteInput extends React.Component {
|
getItemValue={(item) => item[0]}
|
||||||
static propTypes = {
|
onSelect={v => this.props.onChange(v)}
|
||||||
value: PropTypes.string,
|
onChange={(e, v) => this.props.onChange(v)}
|
||||||
options: PropTypes.array,
|
shouldItemRender={(item, value) => {
|
||||||
onChange: PropTypes.func,
|
return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
|
||||||
keepMenuWithinWindowBounds: PropTypes.bool
|
}}
|
||||||
}
|
renderItem={(item, isHighlighted) => (
|
||||||
|
<div
|
||||||
static defaultProps = {
|
key={item[0]}
|
||||||
onChange: () => {},
|
className={classnames({
|
||||||
options: [],
|
"maputnik-autocomplete-menu-item": true,
|
||||||
}
|
"maputnik-autocomplete-menu-item-selected": isHighlighted,
|
||||||
|
})}
|
||||||
render() {
|
>
|
||||||
return <Autocomplete
|
{item[1]}
|
||||||
wrapperProps={{
|
</div>
|
||||||
className: "maputnik-autocomplete",
|
)}
|
||||||
style: null
|
/>
|
||||||
}}
|
</div>
|
||||||
renderMenu={(items) => {
|
|
||||||
return <AutocompleteMenu keepMenuWithinWindowBounds={this.props.keepMenuWithinWindowBounds} style={{marginBottom: 4}}>
|
|
||||||
{items}
|
|
||||||
</AutocompleteMenu>
|
|
||||||
}}
|
|
||||||
inputProps={{
|
|
||||||
className: "maputnik-string"
|
|
||||||
}}
|
|
||||||
value={this.props.value}
|
|
||||||
items={this.props.options}
|
|
||||||
getItemValue={(item) => item[0]}
|
|
||||||
onSelect={v => this.props.onChange(v)}
|
|
||||||
onChange={(e, v) => this.props.onChange(v)}
|
|
||||||
renderItem={(item, isHighlighted) => (
|
|
||||||
<div
|
|
||||||
key={item[0]}
|
|
||||||
className={classnames({
|
|
||||||
"maputnik-autocomplete-menu-item": true,
|
|
||||||
"maputnik-autocomplete-menu-item-selected": isHighlighted,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{item[1]}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class LayerListContainer extends React.Component {
|
|||||||
const grp = <LayerListGroup
|
const grp = <LayerListGroup
|
||||||
key={[groupPrefix, idx].join('-')}
|
key={[groupPrefix, idx].join('-')}
|
||||||
title={groupPrefix}
|
title={groupPrefix}
|
||||||
isActive={!this.isCollapsed(groupPrefix, idx)}
|
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
|
||||||
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
|
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
|
||||||
/>
|
/>
|
||||||
listItems.push(grp)
|
listItems.push(grp)
|
||||||
@@ -172,9 +172,10 @@ class LayerListContainer extends React.Component {
|
|||||||
|
|
||||||
layers.forEach((layer, idxInGroup) => {
|
layers.forEach((layer, idxInGroup) => {
|
||||||
const groupIdx = findClosestCommonPrefix(this.props.layers, idx)
|
const groupIdx = findClosestCommonPrefix(this.props.layers, idx)
|
||||||
|
|
||||||
const listItem = <LayerListItem
|
const listItem = <LayerListItem
|
||||||
className={classnames({
|
className={classnames({
|
||||||
'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx),
|
'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
|
||||||
'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1
|
'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1
|
||||||
})}
|
})}
|
||||||
index={idx}
|
index={idx}
|
||||||
|
|||||||
@@ -4,18 +4,32 @@ import InputBlock from '../inputs/InputBlock'
|
|||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
import LayerIcon from '../icons/LayerIcon'
|
import LayerIcon from '../icons/LayerIcon'
|
||||||
|
|
||||||
|
|
||||||
function groupFeaturesBySourceLayer(features) {
|
function groupFeaturesBySourceLayer(features) {
|
||||||
const sources = {}
|
const sources = {}
|
||||||
|
|
||||||
|
let returnedFeatures = {};
|
||||||
|
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || []
|
if(returnedFeatures.hasOwnProperty(feature.layer.id)) {
|
||||||
sources[feature.layer['source-layer']].push(feature)
|
returnedFeatures[feature.layer.id]++
|
||||||
|
|
||||||
|
const featureObject = sources[feature.layer['source-layer']].find(f => f.layer.id === feature.layer.id)
|
||||||
|
|
||||||
|
featureObject.counter = returnedFeatures[feature.layer.id]
|
||||||
|
} else {
|
||||||
|
sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || []
|
||||||
|
sources[feature.layer['source-layer']].push(feature)
|
||||||
|
|
||||||
|
returnedFeatures[feature.layer.id] = 1
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return sources
|
return sources
|
||||||
}
|
}
|
||||||
|
|
||||||
class FeatureLayerPopup extends React.Component {
|
class FeatureLayerPopup extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
onLayerSelect: PropTypes.func.isRequired,
|
||||||
features: PropTypes.array
|
features: PropTypes.array
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +41,9 @@ class FeatureLayerPopup extends React.Component {
|
|||||||
return <label
|
return <label
|
||||||
key={idx}
|
key={idx}
|
||||||
className="maputnik-popup-layer"
|
className="maputnik-popup-layer"
|
||||||
|
onClick={() => {
|
||||||
|
this.props.onLayerSelect(feature.layer.id)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<LayerIcon type={feature.layer.type} style={{
|
<LayerIcon type={feature.layer.type} style={{
|
||||||
width: 14,
|
width: 14,
|
||||||
@@ -34,6 +51,7 @@ class FeatureLayerPopup extends React.Component {
|
|||||||
paddingRight: 3
|
paddingRight: 3
|
||||||
}}/>
|
}}/>
|
||||||
{feature.layer.id}
|
{feature.layer.id}
|
||||||
|
{feature.counter && <span> × {feature.counter}</span>}
|
||||||
</label>
|
</label>
|
||||||
})
|
})
|
||||||
return <div key={vectorLayerId}>
|
return <div key={vectorLayerId}>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function renderProperties(feature) {
|
|||||||
|
|
||||||
function renderFeature(feature) {
|
function renderFeature(feature) {
|
||||||
return <div key={feature.id}>
|
return <div key={feature.id}>
|
||||||
<div className="maputnik-popup-layer-id">{feature.layer['source-layer']}</div>
|
<div className="maputnik-popup-layer-id">{feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div>
|
||||||
<InputBlock key={"property-type"} label={"$type"}>
|
<InputBlock key={"property-type"} label={"$type"}>
|
||||||
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
|
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
@@ -31,13 +31,36 @@ function renderFeature(feature) {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeDuplicatedFeatures(features) {
|
||||||
|
let uniqueFeatures = [];
|
||||||
|
|
||||||
|
features.forEach(feature => {
|
||||||
|
const featureIndex = uniqueFeatures.findIndex(feature2 => {
|
||||||
|
return feature.layer['source-layer'] === feature2.layer['source-layer']
|
||||||
|
&& JSON.stringify(feature.properties) === JSON.stringify(feature2.properties)
|
||||||
|
})
|
||||||
|
|
||||||
|
if(featureIndex === -1) {
|
||||||
|
uniqueFeatures.push(feature)
|
||||||
|
} else {
|
||||||
|
if(uniqueFeatures[featureIndex].hasOwnProperty('counter')) {
|
||||||
|
uniqueFeatures[featureIndex].inspectModeCounter++
|
||||||
|
} else {
|
||||||
|
uniqueFeatures[featureIndex].inspectModeCounter = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return uniqueFeatures
|
||||||
|
}
|
||||||
|
|
||||||
class FeaturePropertyPopup extends React.Component {
|
class FeaturePropertyPopup extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
features: PropTypes.array
|
features: PropTypes.array
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const features = this.props.features
|
const features = removeDuplicatedFeatures(this.props.features)
|
||||||
return <div className="maputnik-feature-property-popup">
|
return <div className="maputnik-feature-property-popup">
|
||||||
{features.map(renderFeature)}
|
{features.map(renderFeature)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ import 'mapbox-gl/dist/mapbox-gl.css'
|
|||||||
import '../../mapboxgl.css'
|
import '../../mapboxgl.css'
|
||||||
import '../../libs/mapbox-rtl'
|
import '../../libs/mapbox-rtl'
|
||||||
|
|
||||||
function renderLayerPopup(features) {
|
|
||||||
var mountNode = document.createElement('div');
|
|
||||||
ReactDOM.render(<FeatureLayerPopup features={features} />, mountNode)
|
|
||||||
return mountNode.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPropertyPopup(features) {
|
function renderPropertyPopup(features) {
|
||||||
var mountNode = document.createElement('div');
|
var mountNode = document.createElement('div');
|
||||||
ReactDOM.render(<FeaturePropertyPopup features={features} />, mountNode)
|
ReactDOM.render(<FeaturePropertyPopup features={features} />, mountNode)
|
||||||
@@ -60,6 +54,7 @@ function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
|
|||||||
export default class MapboxGlMap extends React.Component {
|
export default class MapboxGlMap extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onDataChange: PropTypes.func,
|
onDataChange: PropTypes.func,
|
||||||
|
onLayerSelect: PropTypes.func.isRequired,
|
||||||
mapStyle: PropTypes.object.isRequired,
|
mapStyle: PropTypes.object.isRequired,
|
||||||
inspectModeEnabled: PropTypes.bool.isRequired,
|
inspectModeEnabled: PropTypes.bool.isRequired,
|
||||||
highlightedLayer: PropTypes.object,
|
highlightedLayer: PropTypes.object,
|
||||||
@@ -68,6 +63,7 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onMapLoaded: () => {},
|
onMapLoaded: () => {},
|
||||||
onDataChange: () => {},
|
onDataChange: () => {},
|
||||||
|
onLayerSelect: () => {},
|
||||||
mapboxAccessToken: tokens.mapbox,
|
mapboxAccessToken: tokens.mapbox,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +129,9 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
if(this.props.inspectModeEnabled) {
|
if(this.props.inspectModeEnabled) {
|
||||||
return renderPropertyPopup(features)
|
return renderPropertyPopup(features)
|
||||||
} else {
|
} else {
|
||||||
return renderLayerPopup(features)
|
var mountNode = document.createElement('div');
|
||||||
|
ReactDOM.render(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} />, mountNode)
|
||||||
|
return mountNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -58,11 +58,22 @@ class AddModal extends React.Component {
|
|||||||
|
|
||||||
componentWillUpdate(nextProps, nextState) {
|
componentWillUpdate(nextProps, nextState) {
|
||||||
// Check if source is valid for new type
|
// Check if source is valid for new type
|
||||||
const availableSources = this.getSources(nextState.type);
|
const oldType = this.state.type;
|
||||||
|
const newType = nextState.type;
|
||||||
|
|
||||||
|
const availableSourcesOld = this.getSources(oldType);
|
||||||
|
const availableSourcesNew = this.getSources(newType);
|
||||||
|
|
||||||
if(
|
if(
|
||||||
this.state.source !== ""
|
// Type has changed
|
||||||
&& availableSources.indexOf(this.state.source) < 0
|
oldType !== newType
|
||||||
|
&& this.state.source !== ""
|
||||||
|
// Was a valid source previously
|
||||||
|
&& availableSourcesOld.indexOf(this.state.source) > -1
|
||||||
|
// And is not a valid source now
|
||||||
|
&& availableSourcesNew.indexOf(nextState.source) < 0
|
||||||
) {
|
) {
|
||||||
|
// Clear the source
|
||||||
this.setState({
|
this.setState({
|
||||||
source: ""
|
source: ""
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
.maputnik-popup-layer {
|
.maputnik-popup-layer {
|
||||||
display: block;
|
display: block;
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
padding: $margin-2;
|
padding: $margin-2;
|
||||||
|
|||||||
Reference in New Issue
Block a user