diff --git a/src/components/App.jsx b/src/components/App.jsx index 5e2ee060..d070dfa1 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -13,6 +13,12 @@ import Toolbar from './Toolbar' import AppLayout from './AppLayout' 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 * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec' import style from '../libs/style.js' @@ -51,6 +57,79 @@ export default class App extends React.Component { 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() if(styleUrl) { this.styleStore = new StyleStore() @@ -86,6 +165,13 @@ export default class App extends React.Component { vectorLayers: {}, inspectModeEnabled: false, spec: styleSpec.latest, + isOpen: { + settings: false, + sources: false, + open: false, + shortcuts: false, + export: false, + }, mapFilter: queryObj["color-blindness-emulation"], } @@ -351,6 +437,15 @@ export default class App extends React.Component { this.setState({ selectedLayerIndex: idx }) } + toggleModal(modalName) { + this.setState({ + isOpen: { + ...this.state.isOpen, + [modalName]: !this.state.isOpen[modalName] + } + }) + } + render() { const layers = this.state.mapStyle.layers || [] 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)} onStyleOpen={this.onStyleChanged.bind(this)} onInspectModeToggle={this.changeInspectMode.bind(this)} + onToggleModal={this.toggleModal.bind(this)} /> const layerList = : null + + const modals =
+ + + + + +
+ return } } diff --git a/src/components/AppLayout.jsx b/src/components/AppLayout.jsx index 1804686d..8ea4cae5 100644 --- a/src/components/AppLayout.jsx +++ b/src/components/AppLayout.jsx @@ -9,6 +9,7 @@ class AppLayout extends React.Component { layerEditor: PropTypes.element, map: PropTypes.element.isRequired, bottom: PropTypes.element, + modals: PropTypes.node, } static childContextTypes = { @@ -39,6 +40,7 @@ class AppLayout extends React.Component { {this.props.bottom} } + {this.props.modals} } } diff --git a/src/components/Toolbar.jsx b/src/components/Toolbar.jsx index f53996a7..9f7516bc 100644 --- a/src/components/Toolbar.jsx +++ b/src/components/Toolbar.jsx @@ -18,10 +18,6 @@ import HelpIcon from 'react-icons/lib/md/help-outline' import InspectionIcon from 'react-icons/lib/md/find-in-page' 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 style from '../libs/style' @@ -41,6 +37,7 @@ class ToolbarLink extends React.Component { className: PropTypes.string, children: PropTypes.node, href: PropTypes.string, + onToggleModal: PropTypes.func, } render() { @@ -83,7 +80,8 @@ export default class Toolbar extends React.Component { // A dict of source id's and the available source layers sources: PropTypes.object.isRequired, onInspectModeToggle: PropTypes.func.isRequired, - children: PropTypes.node + children: PropTypes.node, + onToggleModal: PropTypes.func, } 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() { return
- - - -
- + Open - + Export - + - Sources + Data Sources - + Style Settings diff --git a/src/components/modals/Modal.jsx b/src/components/modals/Modal.jsx index e2e24a3a..2a4a97c9 100644 --- a/src/components/modals/Modal.jsx +++ b/src/components/modals/Modal.jsx @@ -32,12 +32,12 @@ class Modal extends React.Component {

{this.props.title}

- this.props.onOpenToggle(false)} data-wd-key={this.props["data-wd-key"]+".close-modal"} > - +
{this.props.children}
diff --git a/src/components/modals/OpenModal.jsx b/src/components/modals/OpenModal.jsx index 64df02f7..c31ffd3d 100644 --- a/src/components/modals/OpenModal.jsx +++ b/src/components/modals/OpenModal.jsx @@ -144,7 +144,7 @@ class OpenModal extends React.Component {

Upload Style

Upload a JSON style from your computer.

- +
diff --git a/src/components/modals/ShortcutsModal.jsx b/src/components/modals/ShortcutsModal.jsx new file mode 100644 index 00000000..6a17e4bf --- /dev/null +++ b/src/components/modals/ShortcutsModal.jsx @@ -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 +
+

+ Press ESC to lose focus of any active elements, then press one of: +

+
    + {help.map((item) => { + return
  • + {item.key} {item.text} +
  • + })} +
+
+
+ } +} + +export default ShortcutsModal diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index 0e93dca8..c54016ad 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -43,7 +43,10 @@ } .maputnik-modal-header-toggle { - cursor: pointer; + border: none; + background: initial; + color: white; + padding: 0; } .maputnik-modal-scroller { @@ -220,3 +223,20 @@ text-decoration: none; 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; + } +}