Added additional menu in <LayerEditor/>

This is to make the following options accessible to keyboard users

 - reorder layers
 - duplicate layer
 - delete layer
 - hide/show layer
This commit is contained in:
orangemug
2018-05-22 21:16:46 +01:00
parent 1aed761893
commit bd9076c4ff
7 changed files with 227 additions and 50 deletions

View File

@@ -1,5 +1,8 @@
import React from 'react'
import Mousetrap from 'mousetrap'
import cloneDeep from 'lodash.clonedeep'
import clamp from 'lodash.clamp'
import {arrayMove} from 'react-sortable-hoc';
import MapboxGlMap from './map/MapboxGlMap'
import OpenLayers3Map from './map/OpenLayers3Map'
@@ -164,6 +167,24 @@ export default class App extends React.Component {
})
}
onSortEnd(move) {
let { oldIndex, newIndex } = move;
let layers = this.state.mapStyle.layers;
oldIndex = clamp(oldIndex, 0, layers.length-1);
newIndex = clamp(newIndex, 0, layers.length-1);
if(oldIndex === newIndex) return;
if (oldIndex === this.state.selectedLayerIndex) {
this.setState({
selectedLayerIndex: newIndex
});
}
layers = layers.slice(0);
layers = arrayMove(layers, oldIndex, newIndex);
this.onLayersChange(layers);
}
onLayersChange(changedLayers) {
const changedStyle = {
...this.state.mapStyle,
@@ -172,6 +193,40 @@ export default class App extends React.Component {
this.onStyleChanged(changedStyle)
}
onLayerDestroy(layerId) {
let layers = this.state.mapStyle.layers;
const remainingLayers = layers.slice(0);
const idx = style.indexOfLayer(remainingLayers, layerId)
remainingLayers.splice(idx, 1);
this.onLayersChange(remainingLayers);
}
onLayerCopy(layerId) {
let layers = this.state.mapStyle.layers;
const changedLayers = 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.onLayersChange(changedLayers)
}
onLayerVisibilityToggle(layerId) {
let layers = this.state.mapStyle.layers;
const changedLayers = 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.onLayersChange(changedLayers)
}
onLayerIdChange(oldId, newId) {
const changedLayers = this.state.mapStyle.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, oldId)
@@ -297,6 +352,10 @@ export default class App extends React.Component {
/>
const layerList = <LayerList
onSortEnd={this.onSortEnd.bind(this)}
onLayerDestroy={this.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
onLayersChange={this.onLayersChange.bind(this)}
onLayerSelect={this.onLayerSelect.bind(this)}
selectedLayerIndex={this.state.selectedLayerIndex}
@@ -306,10 +365,17 @@ export default class App extends React.Component {
const layerEditor = selectedLayer ? <LayerEditor
layer={selectedLayer}
layerIndex={this.state.selectedLayerIndex}
isFirstLayer={this.state.selectedLayerIndex < 1}
isLastLayer={this.state.selectedLayerIndex === this.state.mapStyle.layers.length-1}
sources={this.state.sources}
vectorLayers={this.state.vectorLayers}
spec={this.state.spec}
onSortEnd={this.onSortEnd.bind(this)}
onLayerChanged={this.onLayerChanged.bind(this)}
onLayerDestroy={this.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
onLayerIdChange={this.onLayerIdChange.bind(this)}
/> : null

View File

@@ -1,5 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
import classnames from 'classnames'
import JSONEditor from './JSONEditor'
import FilterEditor from '../filter/FilterEditor'
@@ -13,6 +15,8 @@ import CommentBlock from './CommentBlock'
import LayerSourceBlock from './LayerSourceBlock'
import LayerSourceLayerBlock from './LayerSourceLayerBlock'
import MoreVertIcon from 'react-icons/lib/md/more-vert'
import InputBlock from '../inputs/InputBlock'
import MultiButtonInput from '../inputs/MultiButtonInput'
@@ -176,6 +180,13 @@ export default class LayerEditor extends React.Component {
}
}
moveLayer(offset) {
this.props.onSortEnd({
oldIndex: this.props.layerIndex,
newIndex: this.props.layerIndex+offset
})
}
render() {
const layerType = this.props.layer.type
const groups = layoutGroups(layerType).filter(group => {
@@ -192,8 +203,72 @@ export default class LayerEditor extends React.Component {
</LayerEditorGroup>
})
const items = {
delete: {
text: "Delete",
handler: () => this.props.onLayerDestroy(this.props.layer.id)
},
duplicate: {
text: "Duplicate",
handler: () => this.props.onLayerCopy(this.props.layer.id)
},
hide: {
text: "Hide",
handler: () => this.props.onLayerVisibilityToggle(this.props.layer.id)
},
moveLayerUp: {
text: "Move layer up",
// Not actually used...
disabled: this.props.isFirstLayer,
handler: () => this.moveLayer(-1)
},
moveLayerDown: {
text: "Move layer down",
// Not actually used...
disabled: this.props.isLastLayer,
handler: () => this.moveLayer(+1)
}
}
function handleSelection(id, event) {
event.stopPropagation;
items[id].handler();
}
return <div className="maputnik-layer-editor"
>
<header>
<div className="layer-header">
<h2 className="layer-header__title">
Layer: {this.props.layer.id}
</h2>
<div className="layer-header__info">
<Wrapper
className='more-menu'
onSelection={handleSelection}
closeOnSelection={false}
>
<Button className='more-menu__button'>
<MoreVertIcon width="22px" height="22px" />
</Button>
<Menu>
<ul className="more-menu__menu">
{Object.keys(items).map((id, idx) => {
const item = items[id];
return <li key={id}>
<MenuItem value={id} className='more-menu__menu__item'>
{item.text}
</MenuItem>
</li>
})}
</ul>
</Menu>
</Wrapper>
</div>
</div>
</header>
{groups}
</div>
}

View File

@@ -1,7 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import cloneDeep from 'lodash.clonedeep'
import Button from '../Button'
import LayerListGroup from './LayerListGroup'
@@ -10,7 +9,7 @@ import AddIcon from 'react-icons/lib/md/add-circle-outline'
import AddModal from '../modals/AddModal'
import style from '../../libs/style.js'
import {SortableContainer, SortableHandle, arrayMove} from 'react-sortable-hoc';
import {SortableContainer, SortableHandle} from 'react-sortable-hoc';
const layerListPropTypes = {
layers: PropTypes.array.isRequired,
@@ -57,36 +56,6 @@ class LayerListContainer extends React.Component {
}
}
onLayerDestroy(layerId) {
const remainingLayers = this.props.layers.slice(0)
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)
}
toggleModal(modalName) {
this.setState({
isOpen: {
@@ -186,9 +155,9 @@ class LayerListContainer extends React.Component {
visibility={(layer.layout || {}).visibility}
isSelected={idx === this.props.selectedLayerIndex}
onLayerSelect={this.props.onLayerSelect}
onLayerDestroy={this.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
onLayerDestroy={this.props.onLayerDestroy.bind(this)}
onLayerCopy={this.props.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)}
/>
listItems.push(listItem)
idx += 1
@@ -236,18 +205,10 @@ class LayerListContainer extends React.Component {
export default class LayerList extends React.Component {
static propTypes = {...layerListPropTypes}
onSortEnd(move) {
const { oldIndex, newIndex } = move
if(oldIndex === newIndex) return
let layers = this.props.layers.slice(0)
layers = arrayMove(layers, oldIndex, newIndex)
this.props.onLayersChange(layers)
}
render() {
return <LayerListContainer
{...this.props}
onSortEnd={this.onSortEnd.bind(this)}
onSortEnd={this.props.onSortEnd.bind(this)}
useDragHandle={true}
/>
}

View File

@@ -38,7 +38,7 @@ class IconAction extends React.Component {
renderIcon() {
switch(this.props.action) {
case 'copy': return <CopyIcon />
case 'duplicate': return <CopyIcon />
case 'show': return <VisibilityIcon />
case 'hide': return <VisibilityOffIcon />
case 'delete': return <DeleteIcon />
@@ -46,13 +46,15 @@ class IconAction extends React.Component {
}
render() {
return <a
return <button
tabIndex="-1"
title={this.props.action}
className="maputnik-layer-list-icon-action"
data-wd-key={this.props.wdKey}
onClick={this.props.onClick}
>
{this.renderIcon()}
</a>
</button>
}
}
@@ -109,7 +111,7 @@ class LayerListItem extends React.Component {
/>
<IconAction
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
action={'copy'}
action={'duplicate'}
onClick={e => this.props.onLayerCopy(this.props.layerId)}
/>
<IconAction