mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-29 07:40:28 +01:00
Merge pull request #582 from orangemug/fix/add-source-errors
Add open modal and source http/https errors
This commit is contained in:
commit
673887d93b
12 changed files with 181 additions and 48 deletions
|
@ -8,10 +8,15 @@ class StringInput extends React.Component {
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
default: PropTypes.string,
|
default: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
|
onInput: PropTypes.func,
|
||||||
multi: PropTypes.bool,
|
multi: PropTypes.bool,
|
||||||
required: PropTypes.bool,
|
required: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
onInput: () => {},
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -57,7 +62,9 @@ class StringInput extends React.Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
editing: true,
|
editing: true,
|
||||||
value: e.target.value
|
value: e.target.value
|
||||||
})
|
}, () => {
|
||||||
|
this.props.onInput(this.state.value);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onBlur: () => {
|
onBlur: () => {
|
||||||
if(this.state.value!==this.props.value) {
|
if(this.state.value!==this.props.value) {
|
||||||
|
|
77
src/components/inputs/UrlInput.jsx
Normal file
77
src/components/inputs/UrlInput.jsx
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import StringInput from './StringInput'
|
||||||
|
import SmallError from '../util/SmallError'
|
||||||
|
|
||||||
|
|
||||||
|
function validate (url) {
|
||||||
|
let error;
|
||||||
|
const getProtocol = (url) => {
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
return urlObj.protocol;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const protocol = getProtocol(url);
|
||||||
|
if (
|
||||||
|
protocol &&
|
||||||
|
protocol === "http:" &&
|
||||||
|
window.location.protocol === "https:"
|
||||||
|
) {
|
||||||
|
error = (
|
||||||
|
<SmallError>
|
||||||
|
CORS policy won't allow fetching resources served over http from https, use a <code>https://</code> domain
|
||||||
|
</SmallError>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UrlInput extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
"data-wd-key": PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
style: PropTypes.object,
|
||||||
|
default: PropTypes.string,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
onInput: PropTypes.func,
|
||||||
|
multi: PropTypes.bool,
|
||||||
|
required: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
onInput: () => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
error: validate(props.value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onInput = (url) => {
|
||||||
|
this.setState({
|
||||||
|
error: validate(url)
|
||||||
|
});
|
||||||
|
this.props.onInput(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<StringInput
|
||||||
|
{...this.props}
|
||||||
|
onInput={this.onInput}
|
||||||
|
/>
|
||||||
|
{this.state.error}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UrlInput
|
|
@ -181,6 +181,10 @@ export default class MapboxGlMap extends React.Component {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
map.on("error", e => {
|
||||||
|
console.log("ERROR", e);
|
||||||
|
})
|
||||||
|
|
||||||
map.on("zoom", e => {
|
map.on("zoom", e => {
|
||||||
this.setState({
|
this.setState({
|
||||||
zoom: map.getZoom()
|
zoom: map.getZoom()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import LoadingModal from './LoadingModal'
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import FileReaderInput from 'react-file-reader-input'
|
import FileReaderInput from 'react-file-reader-input'
|
||||||
|
import UrlInput from '../inputs/UrlInput'
|
||||||
|
|
||||||
import {MdFileUpload} from 'react-icons/md'
|
import {MdFileUpload} from 'react-icons/md'
|
||||||
import {MdAddCircleOutline} from 'react-icons/md'
|
import {MdAddCircleOutline} from 'react-icons/md'
|
||||||
|
@ -122,9 +123,8 @@ class OpenModal extends React.Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenUrl = () => {
|
onOpenUrl = (url) => {
|
||||||
const url = this.styleUrlElement.value;
|
this.onStyleSelect(this.state.styleUrl);
|
||||||
this.onStyleSelect(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpload = (_, files) => {
|
onUpload = (_, files) => {
|
||||||
|
@ -160,9 +160,9 @@ class OpenModal extends React.Component {
|
||||||
this.props.onOpenToggle();
|
this.props.onOpenToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeUrl = () => {
|
onChangeUrl = (url) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
styleUrl: this.styleUrlElement.value
|
styleUrl: url,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,14 +209,13 @@ class OpenModal extends React.Component {
|
||||||
<p>
|
<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>.
|
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>
|
</p>
|
||||||
<input
|
<UrlInput
|
||||||
data-wd-key="open-modal.url.input"
|
data-wd-key="open-modal.url.input"
|
||||||
type="text"
|
type="text"
|
||||||
ref={(input) => this.styleUrlElement = input}
|
|
||||||
className="maputnik-input"
|
className="maputnik-input"
|
||||||
placeholder="Enter URL..."
|
default="Enter URL..."
|
||||||
value={this.state.styleUrl}
|
value={this.state.styleUrl}
|
||||||
onChange={this.onChangeUrl}
|
onInput={this.onChangeUrl}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -6,6 +6,7 @@ import InputBlock from '../inputs/InputBlock'
|
||||||
import ArrayInput from '../inputs/ArrayInput'
|
import ArrayInput from '../inputs/ArrayInput'
|
||||||
import NumberInput from '../inputs/NumberInput'
|
import NumberInput from '../inputs/NumberInput'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
|
import UrlInput from '../inputs/UrlInput'
|
||||||
import SelectInput from '../inputs/SelectInput'
|
import SelectInput from '../inputs/SelectInput'
|
||||||
import EnumInput from '../inputs/EnumInput'
|
import EnumInput from '../inputs/EnumInput'
|
||||||
import ColorField from '../fields/ColorField'
|
import ColorField from '../fields/ColorField'
|
||||||
|
@ -84,7 +85,7 @@ class SettingsModal extends React.Component {
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={'Style Settings'}
|
title={'Style Settings'}
|
||||||
>
|
>
|
||||||
<div style={{minWidth: 350}}>
|
<div className="modal-settings">
|
||||||
<InputBlock label={"Name"} doc={latest.$root.name.doc}>
|
<InputBlock label={"Name"} doc={latest.$root.name.doc}>
|
||||||
<StringInput {...inputProps}
|
<StringInput {...inputProps}
|
||||||
data-wd-key="modal-settings.name"
|
data-wd-key="modal-settings.name"
|
||||||
|
@ -100,7 +101,7 @@ class SettingsModal extends React.Component {
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
<InputBlock label={"Sprite URL"} doc={latest.$root.sprite.doc}>
|
<InputBlock label={"Sprite URL"} doc={latest.$root.sprite.doc}>
|
||||||
<StringInput {...inputProps}
|
<UrlInput {...inputProps}
|
||||||
data-wd-key="modal-settings.sprite"
|
data-wd-key="modal-settings.sprite"
|
||||||
value={this.props.mapStyle.sprite}
|
value={this.props.mapStyle.sprite}
|
||||||
onChange={this.changeStyleProperty.bind(this, "sprite")}
|
onChange={this.changeStyleProperty.bind(this, "sprite")}
|
||||||
|
@ -108,7 +109,7 @@ class SettingsModal extends React.Component {
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
|
||||||
<InputBlock label={"Glyphs URL"} doc={latest.$root.glyphs.doc}>
|
<InputBlock label={"Glyphs URL"} doc={latest.$root.glyphs.doc}>
|
||||||
<StringInput {...inputProps}
|
<UrlInput {...inputProps}
|
||||||
data-wd-key="modal-settings.glyphs"
|
data-wd-key="modal-settings.glyphs"
|
||||||
value={this.props.mapStyle.glyphs}
|
value={this.props.mapStyle.glyphs}
|
||||||
onChange={this.changeStyleProperty.bind(this, "glyphs")}
|
onChange={this.changeStyleProperty.bind(this, "glyphs")}
|
||||||
|
|
|
@ -112,10 +112,12 @@ class AddSource extends React.Component {
|
||||||
|
|
||||||
defaultSource(mode) {
|
defaultSource(mode) {
|
||||||
const source = (this.state || {}).source || {}
|
const source = (this.state || {}).source || {}
|
||||||
|
const {protocol} = window.location;
|
||||||
|
|
||||||
switch(mode) {
|
switch(mode) {
|
||||||
case 'geojson_url': return {
|
case 'geojson_url': return {
|
||||||
type: 'geojson',
|
type: 'geojson',
|
||||||
data: 'http://localhost:3000/geojson.json'
|
data: `${protocol}//localhost:3000/geojson.json`
|
||||||
}
|
}
|
||||||
case 'geojson_json': return {
|
case 'geojson_json': return {
|
||||||
type: 'geojson',
|
type: 'geojson',
|
||||||
|
@ -123,31 +125,31 @@ class AddSource extends React.Component {
|
||||||
}
|
}
|
||||||
case 'tilejson_vector': return {
|
case 'tilejson_vector': return {
|
||||||
type: 'vector',
|
type: 'vector',
|
||||||
url: source.url || 'http://localhost:3000/tilejson.json'
|
url: source.url || `${protocol}//localhost:3000/tilejson.json`
|
||||||
}
|
}
|
||||||
case 'tilexyz_vector': return {
|
case 'tilexyz_vector': return {
|
||||||
type: 'vector',
|
type: 'vector',
|
||||||
tiles: source.tiles || ['http://localhost:3000/{x}/{y}/{z}.pbf'],
|
tiles: source.tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
|
||||||
minZoom: source.minzoom || 0,
|
minZoom: source.minzoom || 0,
|
||||||
maxZoom: source.maxzoom || 14
|
maxZoom: source.maxzoom || 14
|
||||||
}
|
}
|
||||||
case 'tilejson_raster': return {
|
case 'tilejson_raster': return {
|
||||||
type: 'raster',
|
type: 'raster',
|
||||||
url: source.url || 'http://localhost:3000/tilejson.json'
|
url: source.url || `${protocol}//localhost:3000/tilejson.json`
|
||||||
}
|
}
|
||||||
case 'tilexyz_raster': return {
|
case 'tilexyz_raster': return {
|
||||||
type: 'raster',
|
type: 'raster',
|
||||||
tiles: source.tiles || ['http://localhost:3000/{x}/{y}/{z}.pbf'],
|
tiles: source.tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
|
||||||
minzoom: source.minzoom || 0,
|
minzoom: source.minzoom || 0,
|
||||||
maxzoom: source.maxzoom || 14
|
maxzoom: source.maxzoom || 14
|
||||||
}
|
}
|
||||||
case 'tilejson_raster-dem': return {
|
case 'tilejson_raster-dem': return {
|
||||||
type: 'raster-dem',
|
type: 'raster-dem',
|
||||||
url: source.url || 'http://localhost:3000/tilejson.json'
|
url: source.url || `${protocol}//localhost:3000/tilejson.json`
|
||||||
}
|
}
|
||||||
case 'tilexyz_raster-dem': return {
|
case 'tilexyz_raster-dem': return {
|
||||||
type: 'raster-dem',
|
type: 'raster-dem',
|
||||||
tiles: source.tiles || ['http://localhost:3000/{x}/{y}/{z}.pbf'],
|
tiles: source.tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
|
||||||
minzoom: source.minzoom || 0,
|
minzoom: source.minzoom || 0,
|
||||||
maxzoom: source.maxzoom || 14
|
maxzoom: source.maxzoom || 14
|
||||||
}
|
}
|
||||||
|
@ -155,6 +157,15 @@ class AddSource extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAdd = () => {
|
||||||
|
const {source, sourceId} = this.state;
|
||||||
|
this.props.onAdd(sourceId, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeSource = (source) => {
|
||||||
|
this.setState({source});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className="maputnik-add-source">
|
return <div className="maputnik-add-source">
|
||||||
<InputBlock label={"Source ID"} doc={"Unique ID that identifies the source and is used in the layer to reference the source."}>
|
<InputBlock label={"Source ID"} doc={"Unique ID that identifies the source and is used in the layer to reference the source."}>
|
||||||
|
@ -180,13 +191,14 @@ class AddSource extends React.Component {
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
<SourceTypeEditor
|
<SourceTypeEditor
|
||||||
onChange={src => this.setState({ source: src })}
|
onChange={this.onChangeSource}
|
||||||
mode={this.state.mode}
|
mode={this.state.mode}
|
||||||
source={this.state.source}
|
source={this.state.source}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="maputnik-add-source-button"
|
className="maputnik-add-source-button"
|
||||||
onClick={() => this.props.onAdd(this.state.sourceId, this.state.source)}>
|
onClick={this.onAdd}
|
||||||
|
>
|
||||||
Add Source
|
Add Source
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
|
||||||
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
|
import UrlInput from '../inputs/UrlInput'
|
||||||
import NumberInput from '../inputs/NumberInput'
|
import NumberInput from '../inputs/NumberInput'
|
||||||
import SelectInput from '../inputs/SelectInput'
|
import SelectInput from '../inputs/SelectInput'
|
||||||
import JSONEditor from '../layers/JSONEditor'
|
import JSONEditor from '../layers/JSONEditor'
|
||||||
|
@ -18,7 +19,7 @@ class TileJSONSourceEditor extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return <div>
|
return <div>
|
||||||
<InputBlock label={"TileJSON URL"} doc={latest.source_vector.url.doc}>
|
<InputBlock label={"TileJSON URL"} doc={latest.source_vector.url.doc}>
|
||||||
<StringInput
|
<UrlInput
|
||||||
value={this.props.source.url}
|
value={this.props.source.url}
|
||||||
onChange={url => this.props.onChange({
|
onChange={url => this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
|
@ -52,7 +53,7 @@ class TileURLSourceEditor extends React.Component {
|
||||||
const tiles = this.props.source.tiles || []
|
const tiles = this.props.source.tiles || []
|
||||||
return tiles.map((tileUrl, tileIndex) => {
|
return tiles.map((tileUrl, tileIndex) => {
|
||||||
return <InputBlock key={tileIndex} label={prefix[tileIndex] + " Tile URL"} doc={latest.source_vector.tiles.doc}>
|
return <InputBlock key={tileIndex} label={prefix[tileIndex] + " Tile URL"} doc={latest.source_vector.tiles.doc}>
|
||||||
<StringInput
|
<UrlInput
|
||||||
value={tileUrl}
|
value={tileUrl}
|
||||||
onChange={this.changeTileUrl.bind(this, tileIndex)}
|
onChange={this.changeTileUrl.bind(this, tileIndex)}
|
||||||
/>
|
/>
|
||||||
|
@ -95,7 +96,7 @@ class GeoJSONSourceUrlEditor extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"GeoJSON URL"} doc={latest.source_geojson.data.doc}>
|
return <InputBlock label={"GeoJSON URL"} doc={latest.source_geojson.data.doc}>
|
||||||
<StringInput
|
<UrlInput
|
||||||
value={this.props.source.data}
|
value={this.props.source.data}
|
||||||
onChange={data => this.props.onChange({
|
onChange={data => this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
|
|
20
src/components/util/SmallError.jsx
Normal file
20
src/components/util/SmallError.jsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import './SmallError.scss';
|
||||||
|
|
||||||
|
|
||||||
|
class SmallError extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className="SmallError">
|
||||||
|
Error: {this.props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SmallError
|
7
src/components/util/SmallError.scss
Normal file
7
src/components/util/SmallError.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
@import '../../styles/vars';
|
||||||
|
|
||||||
|
.SmallError {
|
||||||
|
color: #E57373;
|
||||||
|
font-size: $font-size-6;
|
||||||
|
margin-top: $margin-2
|
||||||
|
}
|
|
@ -280,3 +280,7 @@
|
||||||
color: $color-green;
|
color: $color-green;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-settings {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
23
src/styles/_vars.scss
Normal file
23
src/styles/_vars.scss
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
$color-black: #191b20;
|
||||||
|
$color-gray: #222429;
|
||||||
|
$color-midgray: #303237;
|
||||||
|
$color-lowgray: #a4a4a4;
|
||||||
|
$color-white: #f0f0f0;
|
||||||
|
$color-red: #cf4a4a;
|
||||||
|
$color-green: #53b972;
|
||||||
|
$margin-1: 3px;
|
||||||
|
$margin-2: 5px;
|
||||||
|
$margin-3: 10px;
|
||||||
|
$margin-4: 30px;
|
||||||
|
$margin-5: 40px;
|
||||||
|
$font-size-1: 24px;
|
||||||
|
$font-size-2: 20px;
|
||||||
|
$font-size-3: 18px;
|
||||||
|
$font-size-4: 16px;
|
||||||
|
$font-size-5: 14px;
|
||||||
|
$font-size-6: 12px;
|
||||||
|
$font-family: Roboto, sans-serif;
|
||||||
|
|
||||||
|
$toolbar-height: 40px;
|
||||||
|
$toolbar-offset: 0;
|
||||||
|
|
|
@ -1,26 +1,4 @@
|
||||||
$color-black: #191b20;
|
@import 'vars';
|
||||||
$color-gray: #222429;
|
|
||||||
$color-midgray: #303237;
|
|
||||||
$color-lowgray: #a4a4a4;
|
|
||||||
$color-white: #f0f0f0;
|
|
||||||
$color-red: #cf4a4a;
|
|
||||||
$color-green: #53b972;
|
|
||||||
$margin-1: 3px;
|
|
||||||
$margin-2: 5px;
|
|
||||||
$margin-3: 10px;
|
|
||||||
$margin-4: 30px;
|
|
||||||
$margin-5: 40px;
|
|
||||||
$font-size-1: 24px;
|
|
||||||
$font-size-2: 20px;
|
|
||||||
$font-size-3: 18px;
|
|
||||||
$font-size-4: 16px;
|
|
||||||
$font-size-5: 14px;
|
|
||||||
$font-size-6: 12px;
|
|
||||||
$font-family: Roboto, sans-serif;
|
|
||||||
|
|
||||||
$toolbar-height: 40px;
|
|
||||||
$toolbar-offset: 0;
|
|
||||||
|
|
||||||
@import 'mixins';
|
@import 'mixins';
|
||||||
@import 'reset';
|
@import 'reset';
|
||||||
@import 'base';
|
@import 'base';
|
||||||
|
|
Loading…
Reference in a new issue