Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keyboard accessible layer options #306

Merged
14 changes: 7 additions & 7 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ templates:
name: "Create artifacts directory"
command: mkdir /tmp/artifacts
- restore_cache:
key: v1-dependencies-{{ checksum "package.json" }}
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}

- run: npm install

- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}

- run: mkdir -p /tmp/artifacts/logs
- run: npm run build
Expand All @@ -30,14 +30,14 @@ templates:
name: "Create artifacts directory"
command: mkdir /tmp/artifacts
- restore_cache:
key: v1-dependencies-{{ checksum "package.json" }}
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}

- run: npm install

- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}

- run: mkdir -p /tmp/artifacts/logs
- run: npm run build
Expand Down Expand Up @@ -71,23 +71,23 @@ jobs:
dependencies:
override:
- brew install node@6
working_directory: ~/repo-linux-node-v6
working_directory: ~/repo-osx-node-v6
steps: *build-steps
build-osx-node-v8:
macos:
xcode: "9.0"
dependencies:
override:
- brew install node@8
working_directory: ~/repo-linux-node-v8
working_directory: ~/repo-osx-node-v8
steps: *build-steps
build-osx-node-v10:
macos:
xcode: "9.0"
dependencies:
override:
- brew install node@10
working_directory: ~/repo-linux-node-v10
working_directory: ~/repo-osx-node-v10
steps: *build-steps

workflows:
Expand Down
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
66 changes: 66 additions & 0 deletions src/components/App.jsx
Original file line number Diff line number Diff line change
@@ -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 url from 'url'

import MapboxGlMap from './map/MapboxGlMap'
Expand Down Expand Up @@ -168,6 +171,24 @@ export default class App extends React.Component {
})
}

onMoveLayer(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,
Expand All @@ -176,6 +197,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)
Expand Down Expand Up @@ -311,6 +366,10 @@ export default class App extends React.Component {
/>

const layerList = <LayerList
onMoveLayer={this.onMoveLayer.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}
Expand All @@ -320,10 +379,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}
onMoveLayer={this.onMoveLayer.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

Expand Down
82 changes: 82 additions & 0 deletions src/components/layers/LayerEditor.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'

import JSONEditor from './JSONEditor'
import FilterEditor from '../filter/FilterEditor'
Expand All @@ -13,6 +14,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'

Expand Down Expand Up @@ -45,6 +48,13 @@ export default class LayerEditor extends React.Component {
spec: PropTypes.object.isRequired,
onLayerChanged: PropTypes.func,
onLayerIdChange: PropTypes.func,
onMoveLayer: PropTypes.func,
onLayerDestroy: PropTypes.func,
onLayerCopy: PropTypes.func,
onLayerVisibilityToggle: PropTypes.func,
isFirstLayer: PropTypes.bool,
isLastLayer: PropTypes.bool,
layerIndex: PropTypes.number,
}

static defaultProps = {
Expand Down Expand Up @@ -176,6 +186,13 @@ export default class LayerEditor extends React.Component {
}
}

moveLayer(offset) {
this.props.onMoveLayer({
oldIndex: this.props.layerIndex,
newIndex: this.props.layerIndex+offset
})
}

render() {
const layerType = this.props.layer.type
const groups = layoutGroups(layerType).filter(group => {
Expand All @@ -192,8 +209,73 @@ export default class LayerEditor extends React.Component {
</LayerEditorGroup>
})

const layout = this.props.layer.layout || {}

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: (layout.visibility === "none") ? "Show" : "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 className="more-menu__button__svg" />
</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>
}
Expand Down
Loading