mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-29 10:50:29 +01:00
Merge pull request #313 from orangemug/feature/shortcuts
Keyboard shortcuts
This commit is contained in:
commit
cae6cffb7b
7 changed files with 235 additions and 46 deletions
|
@ -13,6 +13,12 @@ import Toolbar from './Toolbar'
|
||||||
import AppLayout from './AppLayout'
|
import AppLayout from './AppLayout'
|
||||||
import MessagePanel from './MessagePanel'
|
import MessagePanel from './MessagePanel'
|
||||||
|
|
||||||
|
import SettingsModal from './modals/SettingsModal'
|
||||||
|
import ExportModal from './modals/ExportModal'
|
||||||
|
import SourcesModal from './modals/SourcesModal'
|
||||||
|
import OpenModal from './modals/OpenModal'
|
||||||
|
import ShortcutsModal from './modals/ShortcutsModal'
|
||||||
|
|
||||||
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
|
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
||||||
import style from '../libs/style.js'
|
import style from '../libs/style.js'
|
||||||
|
@ -51,6 +57,79 @@ export default class App extends React.Component {
|
||||||
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false)
|
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const keyCodes = {
|
||||||
|
"esc": 27,
|
||||||
|
"?": 191,
|
||||||
|
"o": 79,
|
||||||
|
"e": 69,
|
||||||
|
"s": 83,
|
||||||
|
"d": 68,
|
||||||
|
"i": 73,
|
||||||
|
"m": 77,
|
||||||
|
}
|
||||||
|
|
||||||
|
const shortcuts = [
|
||||||
|
{
|
||||||
|
keyCode: keyCodes["?"],
|
||||||
|
handler: () => {
|
||||||
|
this.toggleModal("shortcuts");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keyCode: keyCodes["o"],
|
||||||
|
handler: () => {
|
||||||
|
this.toggleModal("open");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keyCode: keyCodes["e"],
|
||||||
|
handler: () => {
|
||||||
|
this.toggleModal("export");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keyCode: keyCodes["d"],
|
||||||
|
handler: () => {
|
||||||
|
this.toggleModal("sources");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keyCode: keyCodes["s"],
|
||||||
|
handler: () => {
|
||||||
|
this.toggleModal("settings");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keyCode: keyCodes["i"],
|
||||||
|
handler: () => {
|
||||||
|
this.changeInspectMode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keyCode: keyCodes["m"],
|
||||||
|
handler: () => {
|
||||||
|
document.querySelector(".mapboxgl-canvas").focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
document.body.addEventListener("keyup", (e) => {
|
||||||
|
if(e.keyCode === keyCodes["esc"]) {
|
||||||
|
e.target.blur();
|
||||||
|
document.body.focus();
|
||||||
|
}
|
||||||
|
else if(document.activeElement === document.body) {
|
||||||
|
const shortcut = shortcuts.find((shortcut) => {
|
||||||
|
return (shortcut.keyCode === e.keyCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
if(shortcut) {
|
||||||
|
shortcut.handler(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const styleUrl = initialStyleUrl()
|
const styleUrl = initialStyleUrl()
|
||||||
if(styleUrl) {
|
if(styleUrl) {
|
||||||
this.styleStore = new StyleStore()
|
this.styleStore = new StyleStore()
|
||||||
|
@ -86,6 +165,13 @@ export default class App extends React.Component {
|
||||||
vectorLayers: {},
|
vectorLayers: {},
|
||||||
inspectModeEnabled: false,
|
inspectModeEnabled: false,
|
||||||
spec: styleSpec.latest,
|
spec: styleSpec.latest,
|
||||||
|
isOpen: {
|
||||||
|
settings: false,
|
||||||
|
sources: false,
|
||||||
|
open: false,
|
||||||
|
shortcuts: false,
|
||||||
|
export: false,
|
||||||
|
},
|
||||||
mapFilter: queryObj["color-blindness-emulation"],
|
mapFilter: queryObj["color-blindness-emulation"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,6 +437,15 @@ export default class App extends React.Component {
|
||||||
this.setState({ selectedLayerIndex: idx })
|
this.setState({ selectedLayerIndex: idx })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleModal(modalName) {
|
||||||
|
this.setState({
|
||||||
|
isOpen: {
|
||||||
|
...this.state.isOpen,
|
||||||
|
[modalName]: !this.state.isOpen[modalName]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const layers = this.state.mapStyle.layers || []
|
const layers = this.state.mapStyle.layers || []
|
||||||
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null
|
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null
|
||||||
|
@ -363,6 +458,7 @@ export default class App extends React.Component {
|
||||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
onStyleChanged={this.onStyleChanged.bind(this)}
|
||||||
onStyleOpen={this.onStyleChanged.bind(this)}
|
onStyleOpen={this.onStyleChanged.bind(this)}
|
||||||
onInspectModeToggle={this.changeInspectMode.bind(this)}
|
onInspectModeToggle={this.changeInspectMode.bind(this)}
|
||||||
|
onToggleModal={this.toggleModal.bind(this)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
const layerList = <LayerList
|
const layerList = <LayerList
|
||||||
|
@ -398,12 +494,44 @@ export default class App extends React.Component {
|
||||||
infos={this.state.infos}
|
infos={this.state.infos}
|
||||||
/> : null
|
/> : null
|
||||||
|
|
||||||
|
|
||||||
|
const modals = <div>
|
||||||
|
<ShortcutsModal
|
||||||
|
isOpen={this.state.isOpen.shortcuts}
|
||||||
|
onOpenToggle={this.toggleModal.bind(this, 'shortcuts')}
|
||||||
|
/>
|
||||||
|
<SettingsModal
|
||||||
|
mapStyle={this.state.mapStyle}
|
||||||
|
onStyleChanged={this.onStyleChanged}
|
||||||
|
isOpen={this.state.isOpen.settings}
|
||||||
|
onOpenToggle={this.toggleModal.bind(this, 'settings')}
|
||||||
|
/>
|
||||||
|
<ExportModal
|
||||||
|
mapStyle={this.state.mapStyle}
|
||||||
|
onStyleChanged={this.onStyleChanged}
|
||||||
|
isOpen={this.state.isOpen.export}
|
||||||
|
onOpenToggle={this.toggleModal.bind(this, 'export')}
|
||||||
|
/>
|
||||||
|
<OpenModal
|
||||||
|
isOpen={this.state.isOpen.open}
|
||||||
|
onStyleOpen={this.onStyleChanged.bind(this)}
|
||||||
|
onOpenToggle={this.toggleModal.bind(this, 'open')}
|
||||||
|
/>
|
||||||
|
<SourcesModal
|
||||||
|
mapStyle={this.state.mapStyle}
|
||||||
|
onStyleChanged={this.onStyleChanged}
|
||||||
|
isOpen={this.state.isOpen.sources}
|
||||||
|
onOpenToggle={this.toggleModal.bind(this, 'sources')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
return <AppLayout
|
return <AppLayout
|
||||||
toolbar={toolbar}
|
toolbar={toolbar}
|
||||||
layerList={layerList}
|
layerList={layerList}
|
||||||
layerEditor={layerEditor}
|
layerEditor={layerEditor}
|
||||||
map={this.mapRenderer()}
|
map={this.mapRenderer()}
|
||||||
bottom={bottomPanel}
|
bottom={bottomPanel}
|
||||||
|
modals={modals}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ class AppLayout extends React.Component {
|
||||||
layerEditor: PropTypes.element,
|
layerEditor: PropTypes.element,
|
||||||
map: PropTypes.element.isRequired,
|
map: PropTypes.element.isRequired,
|
||||||
bottom: PropTypes.element,
|
bottom: PropTypes.element,
|
||||||
|
modals: PropTypes.node,
|
||||||
}
|
}
|
||||||
|
|
||||||
static childContextTypes = {
|
static childContextTypes = {
|
||||||
|
@ -39,6 +40,7 @@ class AppLayout extends React.Component {
|
||||||
{this.props.bottom}
|
{this.props.bottom}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
{this.props.modals}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,6 @@ import HelpIcon from 'react-icons/lib/md/help-outline'
|
||||||
import InspectionIcon from 'react-icons/lib/md/find-in-page'
|
import InspectionIcon from 'react-icons/lib/md/find-in-page'
|
||||||
|
|
||||||
import logoImage from 'maputnik-design/logos/logo-color.svg'
|
import logoImage from 'maputnik-design/logos/logo-color.svg'
|
||||||
import SettingsModal from './modals/SettingsModal'
|
|
||||||
import ExportModal from './modals/ExportModal'
|
|
||||||
import SourcesModal from './modals/SourcesModal'
|
|
||||||
import OpenModal from './modals/OpenModal'
|
|
||||||
import pkgJson from '../../package.json'
|
import pkgJson from '../../package.json'
|
||||||
|
|
||||||
import style from '../libs/style'
|
import style from '../libs/style'
|
||||||
|
@ -41,6 +37,7 @@ class ToolbarLink extends React.Component {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
href: PropTypes.string,
|
href: PropTypes.string,
|
||||||
|
onToggleModal: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -83,7 +80,8 @@ export default class Toolbar extends React.Component {
|
||||||
// A dict of source id's and the available source layers
|
// A dict of source id's and the available source layers
|
||||||
sources: PropTypes.object.isRequired,
|
sources: PropTypes.object.isRequired,
|
||||||
onInspectModeToggle: PropTypes.func.isRequired,
|
onInspectModeToggle: PropTypes.func.isRequired,
|
||||||
children: PropTypes.node
|
children: PropTypes.node,
|
||||||
|
onToggleModal: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -99,40 +97,8 @@ export default class Toolbar extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleModal(modalName) {
|
|
||||||
this.setState({
|
|
||||||
isOpen: {
|
|
||||||
...this.state.isOpen,
|
|
||||||
[modalName]: !this.state.isOpen[modalName]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className='maputnik-toolbar'>
|
return <div className='maputnik-toolbar'>
|
||||||
<SettingsModal
|
|
||||||
mapStyle={this.props.mapStyle}
|
|
||||||
onStyleChanged={this.props.onStyleChanged}
|
|
||||||
isOpen={this.state.isOpen.settings}
|
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'settings')}
|
|
||||||
/>
|
|
||||||
<ExportModal
|
|
||||||
mapStyle={this.props.mapStyle}
|
|
||||||
onStyleChanged={this.props.onStyleChanged}
|
|
||||||
isOpen={this.state.isOpen.export}
|
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'export')}
|
|
||||||
/>
|
|
||||||
<OpenModal
|
|
||||||
isOpen={this.state.isOpen.open}
|
|
||||||
onStyleOpen={this.props.onStyleOpen}
|
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'open')}
|
|
||||||
/>
|
|
||||||
<SourcesModal
|
|
||||||
mapStyle={this.props.mapStyle}
|
|
||||||
onStyleChanged={this.props.onStyleChanged}
|
|
||||||
isOpen={this.state.isOpen.sources}
|
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'sources')}
|
|
||||||
/>
|
|
||||||
<div className="maputnik-toolbar__inner">
|
<div className="maputnik-toolbar__inner">
|
||||||
<ToolbarLink
|
<ToolbarLink
|
||||||
tabIndex="2"
|
tabIndex="2"
|
||||||
|
@ -148,19 +114,19 @@ export default class Toolbar extends React.Component {
|
||||||
</h1>
|
</h1>
|
||||||
</ToolbarLink>
|
</ToolbarLink>
|
||||||
<div className="maputnik-toolbar__actions">
|
<div className="maputnik-toolbar__actions">
|
||||||
<ToolbarAction wdKey="nav:open" onClick={this.toggleModal.bind(this, 'open')}>
|
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
|
||||||
<OpenIcon />
|
<OpenIcon />
|
||||||
<IconText>Open</IconText>
|
<IconText>Open</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:export" onClick={this.toggleModal.bind(this, 'export')}>
|
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
|
||||||
<MdFileDownload />
|
<MdFileDownload />
|
||||||
<IconText>Export</IconText>
|
<IconText>Export</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:sources" onClick={this.toggleModal.bind(this, 'sources')}>
|
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
|
||||||
<SourcesIcon />
|
<SourcesIcon />
|
||||||
<IconText>Sources</IconText>
|
<IconText>Data Sources</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:settings" onClick={this.toggleModal.bind(this, 'settings')}>
|
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
<IconText>Style Settings</IconText>
|
<IconText>Style Settings</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
|
|
|
@ -32,12 +32,12 @@ class Modal extends React.Component {
|
||||||
<header className="maputnik-modal-header">
|
<header className="maputnik-modal-header">
|
||||||
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
|
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
|
||||||
<span className="maputnik-modal-header-space"></span>
|
<span className="maputnik-modal-header-space"></span>
|
||||||
<a className="maputnik-modal-header-toggle"
|
<button className="maputnik-modal-header-toggle"
|
||||||
onClick={() => this.props.onOpenToggle(false)}
|
onClick={() => this.props.onOpenToggle(false)}
|
||||||
data-wd-key={this.props["data-wd-key"]+".close-modal"}
|
data-wd-key={this.props["data-wd-key"]+".close-modal"}
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</a>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<div className="maputnik-modal-scroller">
|
<div className="maputnik-modal-scroller">
|
||||||
<div className="maputnik-modal-content">{this.props.children}</div>
|
<div className="maputnik-modal-content">{this.props.children}</div>
|
||||||
|
|
|
@ -144,7 +144,7 @@ class OpenModal extends React.Component {
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h2>Upload Style</h2>
|
<h2>Upload Style</h2>
|
||||||
<p>Upload a JSON style from your computer.</p>
|
<p>Upload a JSON style from your computer.</p>
|
||||||
<FileReaderInput onChange={this.onUpload.bind(this)}>
|
<FileReaderInput onChange={this.onUpload.bind(this)} tabIndex="-1">
|
||||||
<Button className="maputnik-upload-button"><FileUploadIcon /> Upload</Button>
|
<Button className="maputnik-upload-button"><FileUploadIcon /> Upload</Button>
|
||||||
</FileReaderInput>
|
</FileReaderInput>
|
||||||
</section>
|
</section>
|
||||||
|
|
73
src/components/modals/ShortcutsModal.jsx
Normal file
73
src/components/modals/ShortcutsModal.jsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import Button from '../Button'
|
||||||
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
|
||||||
|
class ShortcutsModal extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onOpenToggle: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const help = [
|
||||||
|
{
|
||||||
|
key: "?",
|
||||||
|
text: "Shortcuts menu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "o",
|
||||||
|
text: "Open modal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "e",
|
||||||
|
text: "Export modal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "d",
|
||||||
|
text: "Data Sources modal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "s",
|
||||||
|
text: "Style Settings modal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "i",
|
||||||
|
text: "Toggle inspect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "m",
|
||||||
|
text: "Focus map"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
return <Modal
|
||||||
|
data-wd-key="shortcuts-modal"
|
||||||
|
isOpen={this.props.isOpen}
|
||||||
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
|
title={'Shortcuts'}
|
||||||
|
>
|
||||||
|
<div className="maputnik-modal-section maputnik-modal-shortcuts">
|
||||||
|
<p>
|
||||||
|
Press <code>ESC</code> to lose focus of any active elements, then press one of:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{help.map((item) => {
|
||||||
|
return <li key={item.key}>
|
||||||
|
<code>{item.key}</code> {item.text}
|
||||||
|
</li>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShortcutsModal
|
|
@ -43,7 +43,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-modal-header-toggle {
|
.maputnik-modal-header-toggle {
|
||||||
cursor: pointer;
|
border: none;
|
||||||
|
background: initial;
|
||||||
|
color: white;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-modal-scroller {
|
.maputnik-modal-scroller {
|
||||||
|
@ -220,3 +223,20 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #ef5350;
|
color: #ef5350;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-modal-shortcuts {
|
||||||
|
code {
|
||||||
|
color: white;
|
||||||
|
background: #3c3c3c;
|
||||||
|
padding: 2px 6px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-right: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue