From bd9076c4ffecd6397993e516d14f9c9674a86de6 Mon Sep 17 00:00:00 2001 From: orangemug Date: Tue, 22 May 2018 21:16:46 +0100 Subject: [PATCH] Added additional menu in This is to make the following options accessible to keyboard users - reorder layers - duplicate layer - delete layer - hide/show layer --- package-lock.json | 25 +++++++++ package.json | 2 + src/components/App.jsx | 66 ++++++++++++++++++++++ src/components/layers/LayerEditor.jsx | 75 +++++++++++++++++++++++++ src/components/layers/LayerList.jsx | 49 ++-------------- src/components/layers/LayerListItem.jsx | 10 ++-- src/styles/_layer.scss | 50 ++++++++++++++++- 7 files changed, 227 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 35c46c9..4667ddc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 0184727..575c9ab 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/App.jsx b/src/components/App.jsx index 61b2ea7..857931f 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -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 = : null diff --git a/src/components/layers/LayerEditor.jsx b/src/components/layers/LayerEditor.jsx index d205640..16c8d28 100644 --- a/src/components/layers/LayerEditor.jsx +++ b/src/components/layers/LayerEditor.jsx @@ -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 { }) + + 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
+
+
+

+ Layer: {this.props.layer.id} +

+
+ + + +
    + {Object.keys(items).map((id, idx) => { + const item = items[id]; + return
  • + + {item.text} + +
  • + })} +
+
+
+
+
+ +
{groups}
} diff --git a/src/components/layers/LayerList.jsx b/src/components/layers/LayerList.jsx index a9b144f..25312f6 100644 --- a/src/components/layers/LayerList.jsx +++ b/src/components/layers/LayerList.jsx @@ -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 } diff --git a/src/components/layers/LayerListItem.jsx b/src/components/layers/LayerListItem.jsx index 91c9217..5c65188 100644 --- a/src/components/layers/LayerListItem.jsx +++ b/src/components/layers/LayerListItem.jsx @@ -38,7 +38,7 @@ class IconAction extends React.Component { renderIcon() { switch(this.props.action) { - case 'copy': return + case 'duplicate': return case 'show': return case 'hide': return case 'delete': return @@ -46,13 +46,15 @@ class IconAction extends React.Component { } render() { - return {this.renderIcon()} - + } } @@ -109,7 +111,7 @@ class LayerListItem extends React.Component { /> this.props.onLayerCopy(this.props.layerId)} />