mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-28 18:31:17 +01:00
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:
parent
1aed761893
commit
bd9076c4ff
7 changed files with 227 additions and 50 deletions
25
package-lock.json
generated
25
package-lock.json
generated
|
@ -5523,6 +5523,11 @@
|
|||
"readable-stream": "2.3.5"
|
||||
}
|
||||
},
|
||||
"focus-group": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/focus-group/-/focus-group-0.3.1.tgz",
|
||||
"integrity": "sha1-4PMu2GsNq91v/OvfiY7LMuR/7c4="
|
||||
},
|
||||
"focus-trap": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-2.4.4.tgz",
|
||||
|
@ -8365,6 +8370,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz",
|
||||
"integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk="
|
||||
},
|
||||
"lodash.clamp": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clamp/-/lodash.clamp-4.0.3.tgz",
|
||||
"integrity": "sha1-XCS+3u7vB1NWDcK0y0Zx+Qpt36o="
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
|
@ -11479,6 +11489,16 @@
|
|||
"object-assign": "4.1.1"
|
||||
}
|
||||
},
|
||||
"react-aria-menubutton": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-aria-menubutton/-/react-aria-menubutton-5.1.1.tgz",
|
||||
"integrity": "sha512-ceBjPvuqwM2jnRFsfMlpfPdyqQ5cz4STNH7NlKpxStr2uETB/zQ2sHiIUMTuqSuOszU1kgUB2vm3aVn3xdjhcA==",
|
||||
"requires": {
|
||||
"focus-group": "0.3.1",
|
||||
"prop-types": "15.6.1",
|
||||
"teeny-tap": "0.2.0"
|
||||
}
|
||||
},
|
||||
"react-aria-modal": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/react-aria-modal/-/react-aria-modal-2.12.1.tgz",
|
||||
|
@ -14298,6 +14318,11 @@
|
|||
"xtend": "4.0.1"
|
||||
}
|
||||
},
|
||||
"teeny-tap": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/teeny-tap/-/teeny-tap-0.2.0.tgz",
|
||||
"integrity": "sha1-Fn5kUYLQasIi1iuyq2eUenClimg="
|
||||
},
|
||||
"text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"github-api": "^3.0.0",
|
||||
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
||||
"lodash.capitalize": "^4.2.1",
|
||||
"lodash.clamp": "^4.0.3",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
|
@ -42,6 +43,7 @@
|
|||
"prop-types": "^15.6.0",
|
||||
"react": "^16.3.2",
|
||||
"react-addons-pure-render-mixin": "^15.6.2",
|
||||
"react-aria-menubutton": "^5.1.1",
|
||||
"react-aria-modal": "^2.12.1",
|
||||
"react-autocomplete": "^1.7.2",
|
||||
"react-codemirror2": "^4.2.1",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -50,14 +50,25 @@
|
|||
@include flex-row;
|
||||
}
|
||||
|
||||
&-icon-action svg {
|
||||
&-icon-action {
|
||||
display: none;
|
||||
|
||||
svg {
|
||||
fill: $color-black;
|
||||
}
|
||||
}
|
||||
|
||||
.maputnik-layer-list-item:hover,
|
||||
.maputnik-layer-list-item-selected {
|
||||
background-color: lighten($color-black, 2);
|
||||
|
||||
.maputnik-layer-list-icon-action {
|
||||
display: block;
|
||||
background: initial;
|
||||
border: none;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.maputnik-layer-list-icon-action svg {
|
||||
fill: darken($color-lowgray, 0.5);
|
||||
|
||||
|
@ -168,3 +179,38 @@
|
|||
color: $color-lowgray;
|
||||
}
|
||||
}
|
||||
|
||||
.more-menu {
|
||||
position: relative;
|
||||
|
||||
&__button {
|
||||
}
|
||||
|
||||
&__menu {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
background: $color-black;
|
||||
border: solid 1px $color-midgray;
|
||||
right: 0;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
&__menu__item {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.layer-header {
|
||||
display: flex;
|
||||
padding: 6px;
|
||||
background: $color-black;
|
||||
border-bottom: solid 1px $color-midgray;
|
||||
|
||||
&__title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__info {
|
||||
min-width: 28px;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue