Support copy, delete and toggle visibility

This commit is contained in:
Lukas Martinelli 2016-12-20 20:21:35 +01:00
parent 6d9484ec5e
commit dc097e9f9a
6 changed files with 87 additions and 50 deletions

View file

@ -21,6 +21,7 @@
"dependencies": { "dependencies": {
"color": "^1.0.3", "color": "^1.0.3",
"file-saver": "^1.3.2", "file-saver": "^1.3.2",
"lodash.clonedeep": "^4.5.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"lodash.topairs": "^4.3.0", "lodash.topairs": "^4.3.0",
"mapbox-gl": "mapbox/mapbox-gl-js#6c24b9621d2aa770eda67fb5638b4d78087b5624", "mapbox-gl": "mapbox/mapbox-gl-js#6c24b9621d2aa770eda67fb5638b4d78087b5624",

View file

@ -75,12 +75,12 @@ export default class App extends React.Component {
this.setState({ accessToken: newToken }) this.setState({ accessToken: newToken })
} }
onLayersChanged(changedLayers) { onLayersChange(changedLayers) {
const changedStyle = { const changedStyle = {
...this.state.mapStyle, ...this.state.mapStyle,
layers: [changedLayers] layers: changedLayers
} }
this.setState({ mapStyle: newStyle }) this.setState({ mapStyle: changedStyle })
} }
onLayerChanged(layer) { onLayerChanged(layer) {
@ -112,14 +112,9 @@ export default class App extends React.Component {
} }
} }
onLayerSelected(layerId) { onLayerSelect(layerId) {
const layers = this.state.mapStyle.layers const idx = style.indexOfLayer(this.state.mapStyle.layers, layerId)
for (let i = 0; i < layers.length; i++) { this.setState({ selectedLayerIndex: idx })
if(layers[i].id === layerId) {
this.setState({ selectedLayerIndex: i })
break
}
}
} }
render() { render() {
@ -135,17 +130,17 @@ export default class App extends React.Component {
/> />
const layerList = <LayerList const layerList = <LayerList
onLayersChanged={this.onLayersChanged.bind(this)} onLayersChange={this.onLayersChange.bind(this)}
onLayerSelected={this.onLayerSelected.bind(this)} onLayerSelect={this.onLayerSelect.bind(this)}
selectedLayerIndex={this.state.selectedLayerIndex} selectedLayerIndex={this.state.selectedLayerIndex}
layers={layers} layers={layers}
/> />
const layerEditor = selectedLayer ? <LayerEditor const layerEditor = selectedLayer ? <LayerEditor
layer={selectedLayer} layer={selectedLayer}
onLayerChanged={this.onLayerChanged.bind(this)}
sources={this.layerWatcher.sources} sources={this.layerWatcher.sources}
vectorLayers={this.layerWatcher.vectorLayers} vectorLayers={this.layerWatcher.vectorLayers}
onLayerChanged={this.onLayerChanged.bind(this)}
/> : null /> : null
return <Layout return <Layout

View file

@ -9,10 +9,6 @@ import SourceEditor from './SourceEditor'
import FilterEditor from '../filter/FilterEditor' import FilterEditor from '../filter/FilterEditor'
import PropertyGroup from '../fields/PropertyGroup' import PropertyGroup from '../fields/PropertyGroup'
import MdVisibility from 'react-icons/lib/md/visibility'
import MdVisibilityOff from 'react-icons/lib/md/visibility-off'
import MdDelete from 'react-icons/lib/md/delete'
import ScrollContainer from '../ScrollContainer' import ScrollContainer from '../ScrollContainer'
import layout from '../../config/layout.json' import layout from '../../config/layout.json'
@ -77,14 +73,6 @@ export default class LayerEditor extends React.Component {
this.props.onLayerChanged(changedLayer) this.props.onLayerChanged(changedLayer)
} }
toggleVisibility() {
if(this.props.layer.has('layout') && this.props.layer.layout.visibility === 'none') {
this.onLayoutChanged('visibility', 'visible')
} else {
this.onLayoutChanged('visibility', 'none')
}
}
render() { render() {
const layerType = this.props.layer.type const layerType = this.props.layer.type
const groups = layout[layerType].groups const groups = layout[layerType].groups

View file

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import PureRenderMixin from 'react-addons-pure-render-mixin'; import PureRenderMixin from 'react-addons-pure-render-mixin';
import cloneDeep from 'lodash.clonedeep'
import Heading from 'rebass/dist/Heading' import Heading from 'rebass/dist/Heading'
import Toolbar from 'rebass/dist/Toolbar' import Toolbar from 'rebass/dist/Toolbar'
@ -9,6 +10,7 @@ import Space from 'rebass/dist/Space'
import LayerListItem from './LayerListItem' import LayerListItem from './LayerListItem'
import ScrollContainer from '../ScrollContainer' import ScrollContainer from '../ScrollContainer'
import style from '../../libs/style.js'
import { margins } from '../../config/scales.js' import { margins } from '../../config/scales.js'
import {SortableContainer, SortableHandle, arrayMove} from 'react-sortable-hoc'; import {SortableContainer, SortableHandle, arrayMove} from 'react-sortable-hoc';
@ -16,8 +18,8 @@ import {SortableContainer, SortableHandle, arrayMove} from 'react-sortable-hoc';
const layerListPropTypes = { const layerListPropTypes = {
layers: React.PropTypes.array.isRequired, layers: React.PropTypes.array.isRequired,
selectedLayerIndex: React.PropTypes.number.isRequired, selectedLayerIndex: React.PropTypes.number.isRequired,
onLayersChanged: React.PropTypes.func.isRequired, onLayersChange: React.PropTypes.func.isRequired,
onLayerSelected: React.PropTypes.func, onLayerSelect: React.PropTypes.func,
} }
// List of collapsible layer editors // List of collapsible layer editors
@ -25,7 +27,7 @@ const layerListPropTypes = {
class LayerListContainer extends React.Component { class LayerListContainer extends React.Component {
static propTypes = {...layerListPropTypes} static propTypes = {...layerListPropTypes}
static defaultProps = { static defaultProps = {
onLayerSelected: () => {}, onLayerSelect: () => {},
} }
constructor(props) { constructor(props) {
@ -33,9 +35,34 @@ class LayerListContainer extends React.Component {
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
} }
onLayerDestroyed(deletedLayer) { onLayerDestroy(layerId) {
const remainingLayers = this.props.layers.delete(deletedLayer.id) const remainingLayers = this.props.layers.slice(0)
this.props.onLayersChanged(remainingLayers) const idx = style.indexOfLayer(remainingLayers, layerId)
remainingLayers.splice(idx, 1);
this.props.onLayersChange(remainingLayers)
}
onLayerCopy(layerId) {
const changedLayers = this.props.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
const clonedLayer = cloneDeep(changedLayers[idx])
clonedLayer.id = clonedLayer.id + "-copy"
changedLayers.splice(idx, 0, clonedLayer)
this.props.onLayersChange(changedLayers)
}
onLayerVisibilityToggle(layerId) {
const changedLayers = this.props.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
const layer = { ...changedLayers[idx] }
const changedLayout = 'layout' in layer ? {...layer.layout} : {}
changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none'
layer.layout = changedLayout
changedLayers[idx] = layer
this.props.onLayersChange(changedLayers)
} }
render() { render() {
@ -46,8 +73,12 @@ class LayerListContainer extends React.Component {
key={layerId} key={layerId}
layerId={layerId} layerId={layerId}
layerType={layer.type} layerType={layer.type}
visibility={(layer.layout || {}).visibility}
isSelected={index === this.props.selectedLayerIndex} isSelected={index === this.props.selectedLayerIndex}
onLayerSelected={this.props.onLayerSelected} onLayerSelect={this.props.onLayerSelect}
onLayerDestroy={this.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
/> />
}) })
return <ScrollContainer> return <ScrollContainer>
@ -71,7 +102,7 @@ export default class LayerList extends React.Component {
if(oldIndex === newIndex) return if(oldIndex === newIndex) return
let layers = this.props.layers.slice(0) let layers = this.props.layers.slice(0)
layers = arrayMove(layers, oldIndex, newIndex) layers = arrayMove(layers, oldIndex, newIndex)
this.props.onLayersChanged(layers) this.props.onLayersChange(layers)
} }
render() { render() {

View file

@ -35,6 +35,7 @@ class IconAction extends React.Component {
static propTypes = { static propTypes = {
action: React.PropTypes.string.isRequired, action: React.PropTypes.string.isRequired,
active: React.PropTypes.bool, active: React.PropTypes.bool,
onClick: React.PropTypes.func.isRequired,
} }
constructor(props) { constructor(props) {
@ -44,12 +45,19 @@ class IconAction extends React.Component {
renderIcon() { renderIcon() {
const iconStyle = { const iconStyle = {
fill: this.props.active ? (this.state.hover ? colors.lowgray : colors.midgray) : colors.gray, fill: colors.gray
}
if(this.props.active) {
iconStyle.fill = colors.midgray
}
if(this.state.hover) {
iconStyle.fill = colors.lowgray
} }
switch(this.props.action) { switch(this.props.action) {
case 'copy': return <CopyIcon style={iconStyle} /> case 'copy': return <CopyIcon style={iconStyle} />
case 'show': return <VisibilityOnIcon style={iconStyle} /> case 'show': return <VisibilityIcon style={iconStyle} />
case 'hide': return <VisibilityOffIcon style={iconStyle} /> case 'hide': return <VisibilityOffIcon style={iconStyle} />
case 'delete': return <DeleteIcon style={iconStyle} /> case 'delete': return <DeleteIcon style={iconStyle} />
default: return null default: return null
@ -57,17 +65,18 @@ class IconAction extends React.Component {
} }
render() { render() {
return <div return <a
style={{ style={{
display: "inline", display: "inline",
marginLeft: margins[0], marginLeft: margins[0],
...this.props.style ...this.props.style
}} }}
onClick={this.props.onClick}
onMouseOver={e => this.setState({hover: true})} onMouseOver={e => this.setState({hover: true})}
onMouseOut={e => this.setState({hover: false})} onMouseOut={e => this.setState({hover: false})}
> >
{this.renderIcon()} {this.renderIcon()}
</div> </a>
} }
} }
@ -77,18 +86,20 @@ class LayerListItem extends React.Component {
layerId: React.PropTypes.string.isRequired, layerId: React.PropTypes.string.isRequired,
layerType: React.PropTypes.string.isRequired, layerType: React.PropTypes.string.isRequired,
isSelected: React.PropTypes.bool, isSelected: React.PropTypes.bool,
visibility: React.PropTypes.bool, visibility: React.PropTypes.string,
onLayerSelected: React.PropTypes.func.isRequired, onLayerSelect: React.PropTypes.func.isRequired,
onLayerDestroyed: React.PropTypes.func, onLayerCopy: React.PropTypes.func,
onLayerVisibilityToggled: React.PropTypes.func, onLayerDestroy: React.PropTypes.func,
onLayerVisibilityToggle: React.PropTypes.func,
} }
static defaultProps = { static defaultProps = {
isSelected: false, isSelected: false,
visibility: true, visibility: 'visible',
onLayerDestroyed: () => {}, onLayerCopy: () => {},
onLayerVisibilityToggled: () => {}, onLayerDestroy: () => {},
onLayerVisibilityToggle: () => {},
} }
static childContextTypes = { static childContextTypes = {
@ -143,7 +154,7 @@ class LayerListItem extends React.Component {
return <li return <li
key={this.props.layerId} key={this.props.layerId}
onClick={e => this.props.onLayerSelected(this.props.layerId)} onClick={e => this.props.onLayerSelect(this.props.layerId)}
onMouseOver={e => this.setState({hover: true})} onMouseOver={e => this.setState({hover: true})}
onMouseOut={e => this.setState({hover: false})} onMouseOut={e => this.setState({hover: false})}
style={itemStyle}> style={itemStyle}>
@ -161,15 +172,16 @@ class LayerListItem extends React.Component {
<span style={{flexGrow: 1}} /> <span style={{flexGrow: 1}} />
<IconAction {...iconProps} <IconAction {...iconProps}
action={'delete'} action={'delete'}
onClick={e => this.props.onLayerDestroyed(this.props.layerId)} onClick={e => this.props.onLayerDestroy(this.props.layerId)}
/> />
<IconAction {...iconProps} <IconAction {...iconProps}
action={'copy'} action={'copy'}
onClick={e => this.props.onLayerVisibilityToggled(this.props.layerId)} onClick={e => this.props.onLayerCopy(this.props.layerId)}
/> />
<IconAction {...iconProps} <IconAction {...iconProps}
action={this.props.visibility ? 'hide' : 'show'} active={this.state.hover || this.props.isSelected || this.props.visibility === 'none'}
onClick={e => this.props.onLayerVisibilityToggled(this.props.layerId)} action={this.props.visibility === 'visible' ? 'hide' : 'show'}
onClick={e => this.props.onLayerVisibilityToggle(this.props.layerId)}
/> />
</div> </div>
</li> </li>

View file

@ -24,7 +24,17 @@ function ensureMetadataExists(style) {
return ensureHasId(ensureHasTimestamp(style)) return ensureHasId(ensureHasTimestamp(style))
} }
function indexOfLayer(layers, layerId) {
for (let i = 0; i < layers.length; i++) {
if(layers[i].id === layerId) {
return i
}
}
return null
}
export default { export default {
ensureMetadataExists, ensureMetadataExists,
emptyStyle, emptyStyle,
indexOfLayer,
} }