Compare commits

..

9 Commits

Author SHA1 Message Date
orangemug
664125d820 v1.1.0-beta2 2018-01-19 13:56:38 +00:00
Orange Mug
9ae2f2c5af Merge pull request #219 from orangemug/fix/autocomplete-menu-issues
Autocomplete fixes
2018-01-19 13:45:38 +00:00
orangemug
721f9b36b3 Added missing shouldItemRender to <Autocomplete/>. #219 2018-01-19 12:11:34 +00:00
orangemug
a33d1b819c Autocomplete fixes #218 2018-01-19 11:58:25 +00:00
Orange Mug
3c0ebfabab Merge pull request #213 from gregorywolanski/60
Clickable layer tooltips (#60)
2018-01-18 23:03:19 +00:00
Gregory Wolanski
0ba11b94c8 Merge branch 'master' into 60 2018-01-18 23:58:06 +01:00
Orange Mug
390e90e8c2 Merge pull request #209 from gregorywolanski/41
Remove duplicated features from popups (#41)
2018-01-18 22:40:51 +00:00
Grzegorz Wolański
6a6595d971 Clickable layer tooltips (#60) 2018-01-08 22:18:30 +01:00
Grzegorz Wolański
ace6812e89 Remove duplicated features from popups (#41) 2017-12-05 23:27:17 +01:00
9 changed files with 128 additions and 97 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "maputnik",
"version": "1.1.0-beta",
"version": "1.1.0-beta2",
"description": "A MapboxGL visual style editor",
"main": "''",
"scripts": {
@@ -33,7 +33,7 @@
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"mapbox-gl": "^0.43.0",
"mapbox-gl-inspect": "^1.2.4",
"mapbox-gl-inspect": "^1.2.5",
"maputnik-design": "github:maputnik/design",
"mousetrap": "^1.6.1",
"ol-mapbox-style": "^1.0.1",

View File

@@ -249,7 +249,8 @@ export default class App extends React.Component {
} else {
return <MapboxGlMap {...mapProps}
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)} />
}
}

View File

@@ -6,11 +6,24 @@ import Autocomplete from 'react-autocomplete'
const MAX_HEIGHT = 140;
class AutocompleteMenu extends React.Component {
class AutocompleteInput extends React.Component {
static propTypes = {
keepMenuWithinWindowBounds: PropTypes.bool,
style: PropTypes.object,
children: PropTypes.node
value: PropTypes.string,
options: PropTypes.array,
onChange: PropTypes.func,
keepMenuWithinWindowBounds: PropTypes.bool
}
static defaultProps = {
onChange: () => {},
options: [],
}
constructor(props) {
super(props);
this.state = {
maxHeight: MAX_HEIGHT
};
}
calcMaxHeight() {
@@ -33,81 +46,46 @@ class AutocompleteMenu extends React.Component {
this.calcMaxHeight();
}
constructor(props) {
super(props);
this.state = {
maxHeight: MAX_HEIGHT
};
}
static defaultProps = {
style: {}
}
render() {
const maxHeight = this.state.maxHeight - this.props.style.marginBottom || 0;
const style = {
maxHeight: maxHeight+"px"
}
return (
<div
ref={(el) => {
this.autocompleteMenuEl = el;
return <div
ref={(el) => {
this.autocompleteMenuEl = el;
}}
>
<Autocomplete
menuStyle={{
position: "absolute",
overflow: "auto",
maxHeight: this.state.maxHeight
}}
className={"maputnik-autocomplete-menu"}
style={style}
>
{this.props.children}
</div>
)
}
}
class AutocompleteInput extends React.Component {
static propTypes = {
value: PropTypes.string,
options: PropTypes.array,
onChange: PropTypes.func,
keepMenuWithinWindowBounds: PropTypes.bool
}
static defaultProps = {
onChange: () => {},
options: [],
}
render() {
return <Autocomplete
wrapperProps={{
className: "maputnik-autocomplete",
style: null
}}
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>
)}
/>
wrapperProps={{
className: "maputnik-autocomplete",
style: null
}}
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)}
shouldItemRender={(item, value) => {
return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
}}
renderItem={(item, isHighlighted) => (
<div
key={item[0]}
className={classnames({
"maputnik-autocomplete-menu-item": true,
"maputnik-autocomplete-menu-item-selected": isHighlighted,
})}
>
{item[1]}
</div>
)}
/>
</div>
}
}

View File

@@ -164,7 +164,7 @@ class LayerListContainer extends React.Component {
const grp = <LayerListGroup
key={[groupPrefix, idx].join('-')}
title={groupPrefix}
isActive={!this.isCollapsed(groupPrefix, idx)}
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
/>
listItems.push(grp)
@@ -172,9 +172,10 @@ class LayerListContainer extends React.Component {
layers.forEach((layer, idxInGroup) => {
const groupIdx = findClosestCommonPrefix(this.props.layers, idx)
const listItem = <LayerListItem
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
})}
index={idx}

View File

@@ -4,18 +4,32 @@ import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import LayerIcon from '../icons/LayerIcon'
function groupFeaturesBySourceLayer(features) {
const sources = {}
let returnedFeatures = {};
features.forEach(feature => {
sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || []
sources[feature.layer['source-layer']].push(feature)
if(returnedFeatures.hasOwnProperty(feature.layer.id)) {
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
}
class FeatureLayerPopup extends React.Component {
static propTypes = {
onLayerSelect: PropTypes.func.isRequired,
features: PropTypes.array
}
@@ -27,6 +41,9 @@ class FeatureLayerPopup extends React.Component {
return <label
key={idx}
className="maputnik-popup-layer"
onClick={() => {
this.props.onLayerSelect(feature.layer.id)
}}
>
<LayerIcon type={feature.layer.type} style={{
width: 14,
@@ -34,6 +51,7 @@ class FeatureLayerPopup extends React.Component {
paddingRight: 3
}}/>
{feature.layer.id}
{feature.counter && <span> × {feature.counter}</span>}
</label>
})
return <div key={vectorLayerId}>

View File

@@ -23,7 +23,7 @@ function renderProperties(feature) {
function renderFeature(feature) {
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"}>
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
</InputBlock>
@@ -31,13 +31,36 @@ function renderFeature(feature) {
</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 {
static propTypes = {
features: PropTypes.array
}
render() {
const features = this.props.features
const features = removeDuplicatedFeatures(this.props.features)
return <div className="maputnik-feature-property-popup">
{features.map(renderFeature)}
</div>

View File

@@ -15,12 +15,6 @@ import 'mapbox-gl/dist/mapbox-gl.css'
import '../../mapboxgl.css'
import '../../libs/mapbox-rtl'
function renderLayerPopup(features) {
var mountNode = document.createElement('div');
ReactDOM.render(<FeatureLayerPopup features={features} />, mountNode)
return mountNode.innerHTML;
}
function renderPropertyPopup(features) {
var mountNode = document.createElement('div');
ReactDOM.render(<FeaturePropertyPopup features={features} />, mountNode)
@@ -60,6 +54,7 @@ function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
export default class MapboxGlMap extends React.Component {
static propTypes = {
onDataChange: PropTypes.func,
onLayerSelect: PropTypes.func.isRequired,
mapStyle: PropTypes.object.isRequired,
inspectModeEnabled: PropTypes.bool.isRequired,
highlightedLayer: PropTypes.object,
@@ -68,6 +63,7 @@ export default class MapboxGlMap extends React.Component {
static defaultProps = {
onMapLoaded: () => {},
onDataChange: () => {},
onLayerSelect: () => {},
mapboxAccessToken: tokens.mapbox,
}
@@ -133,7 +129,9 @@ export default class MapboxGlMap extends React.Component {
if(this.props.inspectModeEnabled) {
return renderPropertyPopup(features)
} else {
return renderLayerPopup(features)
var mountNode = document.createElement('div');
ReactDOM.render(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} />, mountNode)
return mountNode
}
}
})

View File

@@ -58,11 +58,22 @@ class AddModal extends React.Component {
componentWillUpdate(nextProps, nextState) {
// 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(
this.state.source !== ""
&& availableSources.indexOf(this.state.source) < 0
// Type has changed
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({
source: ""
});

View File

@@ -1,6 +1,7 @@
.maputnik-popup-layer {
display: block;
color: $color-lowgray;
cursor: pointer;
user-select: none;
line-height: 1.2;
padding: $margin-2;