Merge remote-tracking branch 'upstream/master' into feature/add-thunderforest-source

Conflicts:
	src/components/App.jsx
This commit is contained in:
orangemug
2018-07-27 15:43:02 +01:00
90 changed files with 11345 additions and 6012 deletions

View File

@@ -4,7 +4,6 @@ import PropTypes from 'prop-types'
import Button from '../Button'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import Modal from './Modal'
import LayerTypeBlock from '../layers/LayerTypeBlock'
@@ -56,7 +55,7 @@ class AddModal extends React.Component {
}
}
componentWillUpdate(nextProps, nextState) {
UNSAFE_componentWillUpdate(nextProps, nextState) {
// Check if source is valid for new type
const oldType = this.state.type;
const newType = nextState.type;
@@ -119,19 +118,25 @@ class AddModal extends React.Component {
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title={'Add Layer'}
data-wd-key="modal:add-layer"
>
<div className="maputnik-add-layer">
<LayerIdBlock
value={this.state.id}
onChange={v => this.setState({ id: v })}
wdKey="add-layer.layer-id"
onChange={v => {
this.setState({ id: v })
}}
/>
<LayerTypeBlock
value={this.state.type}
wdKey="add-layer.layer-type"
onChange={v => this.setState({ type: v })}
/>
{this.state.type !== 'background' &&
<LayerSourceBlock
sourceIds={sources}
wdKey="add-layer.layer-source-block"
value={this.state.source}
onChange={v => this.setState({ source: v })}
/>
@@ -144,7 +149,11 @@ class AddModal extends React.Component {
onChange={v => this.setState({ 'source-layer': v })}
/>
}
<Button className="maputnik-add-layer-button" onClick={this.addLayer.bind(this)}>
<Button
className="maputnik-add-layer-button"
onClick={this.addLayer.bind(this)}
data-wd-key="add-layer"
>
Add Layer
</Button>
</div>

View File

@@ -2,10 +2,9 @@ import React from 'react'
import PropTypes from 'prop-types'
import { saveAs } from 'file-saver'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import CheckboxInput from '../inputs/CheckboxInput'
import Button from '../Button'
import Modal from './Modal'
@@ -32,7 +31,7 @@ class Gist extends React.Component {
}
}
componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({
...this.state,
preview: !!(nextProps.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']
@@ -236,12 +235,27 @@ class ExportModal extends React.Component {
}
downloadStyle() {
const blob = new Blob([styleSpec.format(stripAccessTokens(this.props.mapStyle))], {type: "application/json;charset=utf-8"});
const tokenStyle = styleSpec.format(stripAccessTokens(style.replaceAccessToken(this.props.mapStyle)));
const blob = new Blob([tokenStyle], {type: "application/json;charset=utf-8"});
saveAs(blob, this.props.mapStyle.id + ".json");
}
changeMetadataProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
metadata: {
...this.props.mapStyle.metadata,
[property]: value
}
}
this.props.onStyleChanged(changedStyle)
}
render() {
return <Modal
data-wd-key="export-modal"
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title={'Export Style'}
@@ -252,13 +266,29 @@ class ExportModal extends React.Component {
<p>
Download a JSON style to your computer.
</p>
<p>
<InputBlock label={"OpenMapTiles Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/>
</InputBlock>
<InputBlock label={"Mapbox Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
/>
</InputBlock>
</p>
<Button onClick={this.downloadStyle.bind(this)}>
<MdFileDownload />
Download
</Button>
</div>
<div className="maputnik-modal-section">
<div className="maputnik-modal-section hide">
<h4>Save style</h4>
<Gist mapStyle={this.props.mapStyle} onStyleChanged={this.props.onStyleChanged}/>
</div>

View File

@@ -0,0 +1,49 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import Modal from './Modal'
class LoadingModal extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
message: PropTypes.node.isRequired,
}
constructor(props) {
super(props);
}
underlayOnClick(e) {
// This stops click events falling through to underlying modals.
e.stopPropagation();
}
render() {
return <Modal
data-wd-key="loading-modal"
isOpen={this.props.isOpen}
underlayClickExits={false}
underlayProps={{
onClick: (e) => underlayProps(e)
}}
closeable={false}
title={this.props.title}
onOpenToggle={() => this.props.onCancel()}
>
<p>
{this.props.message}
</p>
<p className="maputnik-dialog__buttons">
<Button onClick={(e) => this.props.onCancel(e)}>
Cancel
</Button>
</p>
</Modal>
}
}
export default LoadingModal

View File

@@ -1,33 +1,61 @@
import React from 'react'
import PropTypes from 'prop-types'
import CloseIcon from 'react-icons/lib/md/close'
import Overlay from './Overlay'
import AriaModal from 'react-aria-modal'
class Modal extends React.Component {
static propTypes = {
"data-wd-key": PropTypes.string,
isOpen: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
onOpenToggle: PropTypes.func.isRequired,
children: PropTypes.node,
underlayClickExits: PropTypes.bool,
underlayProps: PropTypes.object,
}
static defaultProps = {
underlayClickExits: true
}
getApplicationNode() {
return document.getElementById('app');
}
render() {
return <Overlay isOpen={this.props.isOpen}>
<div className="maputnik-modal">
<header className="maputnik-modal-header">
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
<span className="maputnik-modal-header-space"></span>
<a className="maputnik-modal-header-toggle"
onClick={() => this.props.onOpenToggle(false)}
>
<CloseIcon />
</a>
</header>
<div className="maputnik-modal-scroller">
<div className="maputnik-modal-content">{this.props.children}</div>
if(this.props.isOpen) {
return <AriaModal
titleText={this.props.title}
underlayClickExits={this.props.underlayClickExits}
underlayProps={this.props.underlayProps}
getApplicationNode={this.getApplicationNode}
data-wd-key={this.props["data-wd-key"]}
verticallyCenter={true}
onExit={() => this.props.onOpenToggle(false)}
>
<div className="maputnik-modal"
data-wd-key={this.props["data-wd-key"]}
>
<header className="maputnik-modal-header">
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
<span className="maputnik-modal-header-space"></span>
<button className="maputnik-modal-header-toggle"
onClick={() => this.props.onOpenToggle(false)}
data-wd-key={this.props["data-wd-key"]+".close-modal"}
>
<CloseIcon />
</button>
</header>
<div className="maputnik-modal-scroller">
<div className="maputnik-modal-content">{this.props.children}</div>
</div>
</div>
</div>
</Overlay>
</AriaModal>
}
else {
return false;
}
}
}

View File

@@ -1,5 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import LoadingModal from './LoadingModal'
import Modal from './Modal'
import Button from '../Button'
import FileReaderInput from 'react-file-reader-input'
@@ -23,6 +24,7 @@ class PublicStyle extends React.Component {
return <div className="maputnik-public-style">
<Button
className="maputnik-public-style-button"
aria-label={this.props.title}
onClick={() => this.props.onSelect(this.props.url)}
>
<header className="maputnik-public-style-header">
@@ -30,11 +32,12 @@ class PublicStyle extends React.Component {
<span className="maputnik-space" />
<AddIcon />
</header>
<img
<div
className="maputnik-public-style-thumbnail"
src={this.props.thumbnailUrl}
alt={this.props.title}
/>
style={{
backgroundImage: `url(${this.props.thumbnailUrl})`
}}
></div>
</Button>
</div>
}
@@ -58,13 +61,33 @@ class OpenModal extends React.Component {
})
}
onCancelActiveRequest(e) {
// Else the click propagates to the underlying modal
if(e) e.stopPropagation();
if(this.state.activeRequest) {
this.state.activeRequest.abort();
this.setState({
activeRequest: null,
activeRequestUrl: null
});
}
}
onStyleSelect(styleUrl) {
this.clearError();
request({
const reqOpts = {
url: styleUrl,
withCredentials: false,
}, (error, response, body) => {
}
const activeRequest = request(reqOpts, (error, response, body) => {
this.setState({
activeRequest: null,
activeRequestUrl: null
});
if (!error && response.statusCode == 200) {
const mapStyle = style.ensureStyleValidity(JSON.parse(body))
console.log('Loaded style ', mapStyle.id)
@@ -74,6 +97,11 @@ class OpenModal extends React.Component {
console.warn('Could not open the style URL', styleUrl)
}
})
this.setState({
activeRequest: activeRequest,
activeRequestUrl: reqOpts.url
})
}
onOpenUrl() {
@@ -133,6 +161,7 @@ class OpenModal extends React.Component {
}
return <Modal
data-wd-key="open-modal"
isOpen={this.props.isOpen}
onOpenToggle={() => this.onOpenToggle()}
title={'Open Style'}
@@ -141,7 +170,7 @@ class OpenModal extends React.Component {
<section className="maputnik-modal-section">
<h2>Upload Style</h2>
<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>
</FileReaderInput>
</section>
@@ -151,9 +180,9 @@ class OpenModal extends React.Component {
<p>
Load from a URL. Note that the URL must have <a href="https://enable-cors.org" target="_blank" rel="noopener noreferrer">CORS enabled</a>.
</p>
<input type="text" ref={(input) => this.styleUrlElement = input} className="maputnik-input" placeholder="Enter URL..."/>
<input data-wd-key="open-modal.url.input" type="text" ref={(input) => this.styleUrlElement = input} className="maputnik-input" placeholder="Enter URL..."/>
<div>
<Button className="maputnik-big-button" onClick={this.onOpenUrl.bind(this)}>Open URL</Button>
<Button data-wd-key="open-modal.url.button" className="maputnik-big-button" onClick={this.onOpenUrl.bind(this)}>Open URL</Button>
</div>
</section>
@@ -166,6 +195,13 @@ class OpenModal extends React.Component {
{styleOptions}
</div>
</section>
<LoadingModal
isOpen={!!this.state.activeRequest}
title={'Loading style'}
onCancel={(e) => this.onCancelActiveRequest(e)}
message={"Loading: "+this.state.activeRequestUrl}
/>
</Modal>
}
}

View File

@@ -1,24 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
class Overlay extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
children: PropTypes.element.isRequired
}
render() {
let overlayStyle = {}
if(!this.props.isOpen) {
overlayStyle['display'] = 'none';
}
return <div className={"maputnik-overlay"} style={overlayStyle}>
<div className={"maputnik-overlay-viewport"} />
{this.props.children}
</div>
}
}
export default Overlay

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
@@ -42,6 +42,7 @@ class SettingsModal extends React.Component {
const metadata = this.props.mapStyle.metadata || {}
const inputProps = { }
return <Modal
data-wd-key="modal-settings"
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title={'Style Settings'}
@@ -49,18 +50,21 @@ class SettingsModal extends React.Component {
<div style={{minWidth: 350}}>
<InputBlock label={"Name"} doc={styleSpec.latest.$root.name.doc}>
<StringInput {...inputProps}
data-wd-key="modal-settings.name"
value={this.props.mapStyle.name}
onChange={this.changeStyleProperty.bind(this, "name")}
/>
</InputBlock>
<InputBlock label={"Owner"} doc={"Owner ID of the style. Used by Mapbox or future style APIs."}>
<StringInput {...inputProps}
data-wd-key="modal-settings.owner"
value={this.props.mapStyle.owner}
onChange={this.changeStyleProperty.bind(this, "owner")}
/>
</InputBlock>
<InputBlock label={"Sprite URL"} doc={styleSpec.latest.$root.sprite.doc}>
<StringInput {...inputProps}
data-wd-key="modal-settings.sprite"
value={this.props.mapStyle.sprite}
onChange={this.changeStyleProperty.bind(this, "sprite")}
/>
@@ -68,6 +72,7 @@ class SettingsModal extends React.Component {
<InputBlock label={"Glyphs URL"} doc={styleSpec.latest.$root.glyphs.doc}>
<StringInput {...inputProps}
data-wd-key="modal-settings.glyphs"
value={this.props.mapStyle.glyphs}
onChange={this.changeStyleProperty.bind(this, "glyphs")}
/>
@@ -75,6 +80,7 @@ class SettingsModal extends React.Component {
<InputBlock label={"Mapbox Access Token"} doc={"Public access token for Mapbox services."}>
<StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:mapbox_access_token"
value={metadata['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
/>
@@ -82,6 +88,7 @@ class SettingsModal extends React.Component {
<InputBlock label={"OpenMapTiles Access Token"} doc={"Public access token for the OpenMapTiles CDN."}>
<StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:openmaptiles_access_token"
value={metadata['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/>
@@ -89,6 +96,7 @@ class SettingsModal extends React.Component {
<InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}>
<SelectInput {...inputProps}
data-wd-key="modal-settings.maputnik:renderer"
options={[
['mbgljs', 'MapboxGL JS'],
['ol3', 'Open Layers 3'],

View 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

View File

@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import Modal from './Modal'
import Button from '../Button'
import InputBlock from '../inputs/InputBlock'

View File

@@ -0,0 +1,41 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import Modal from './Modal'
import logoImage from 'maputnik-design/logos/logo-color.svg'
class SurveyModal extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
}
constructor(props) { super(props); }
onClick = () => {
window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank');
this.props.onOpenToggle();
}
render() {
return <Modal
data-wd-key="modal-survey"
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title="Maputnik Survey"
>
<div className="maputnik-modal-survey">
<img className="maputnik-modal-survey__logo" src={logoImage} alt="" width="128" />
<h1>You + Maputnik = Maputnik better for you</h1>
<p className="maputnik-modal-survey__description">We dont track you, so we dont know how you use Maputnik. Help us make Maputnik better for you by completing a 7minute survey carried out by our contributing designer.</p>
<Button onClick={this.onClick} className="maputnik-big-button maputnik-white-button maputnik-wide-button">Take the Maputnik Survey</Button>
<p className="maputnik-modal-survey__footnote">It takes 7 minutes, tops! Every question is optional.</p>
</div>
</Modal>
}
}
export default SurveyModal