mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-28 18:01:17 +01:00
Merge remote-tracking branch 'upstream/master' into fix/issue-567-better-solution-for-tooltips
This commit is contained in:
commit
17aa88e3b6
40 changed files with 934 additions and 211 deletions
|
@ -41,6 +41,7 @@ templates:
|
||||||
|
|
||||||
- run: mkdir -p /tmp/artifacts/logs
|
- run: mkdir -p /tmp/artifacts/logs
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
|
- run: npm run profiling-build
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
- run: npm run lint-styles
|
- run: npm run lint-styles
|
||||||
- run: DOCKER_HOST=localhost npm test
|
- run: DOCKER_HOST=localhost npm test
|
||||||
|
@ -49,11 +50,6 @@ templates:
|
||||||
path: /tmp/artifacts
|
path: /tmp/artifacts
|
||||||
destination: /artifacts
|
destination: /artifacts
|
||||||
jobs:
|
jobs:
|
||||||
build-linux-node-v8:
|
|
||||||
docker:
|
|
||||||
- image: node:8
|
|
||||||
working_directory: ~/repo-linux-node-v8
|
|
||||||
steps: *build-steps
|
|
||||||
build-linux-node-v10:
|
build-linux-node-v10:
|
||||||
docker:
|
docker:
|
||||||
- image: node:10
|
- image: node:10
|
||||||
|
@ -65,13 +61,10 @@ jobs:
|
||||||
- image: node:12
|
- image: node:12
|
||||||
working_directory: ~/repo-linux-node-v12
|
working_directory: ~/repo-linux-node-v12
|
||||||
steps: *build-steps
|
steps: *build-steps
|
||||||
build-osx-node-v8:
|
build-linux-node-v13:
|
||||||
macos:
|
docker:
|
||||||
xcode: "9.0"
|
- image: node:13
|
||||||
dependencies:
|
working_directory: ~/repo-linux-node-v13
|
||||||
override:
|
|
||||||
- brew install node@8
|
|
||||||
working_directory: ~/repo-osx-node-v8
|
|
||||||
steps: *build-steps
|
steps: *build-steps
|
||||||
build-osx-node-v10:
|
build-osx-node-v10:
|
||||||
macos:
|
macos:
|
||||||
|
@ -89,14 +82,22 @@ jobs:
|
||||||
- brew install node@12
|
- brew install node@12
|
||||||
working_directory: ~/repo-osx-node-v12
|
working_directory: ~/repo-osx-node-v12
|
||||||
steps: *build-steps
|
steps: *build-steps
|
||||||
|
build-osx-node-v13:
|
||||||
|
macos:
|
||||||
|
xcode: "9.0"
|
||||||
|
dependencies:
|
||||||
|
override:
|
||||||
|
- brew install node@13
|
||||||
|
working_directory: ~/repo-osx-node-v13
|
||||||
|
steps: *build-steps
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
build:
|
build:
|
||||||
jobs:
|
jobs:
|
||||||
- build-linux-node-v8
|
|
||||||
- build-linux-node-v10
|
- build-linux-node-v10
|
||||||
- build-linux-node-v12
|
- build-linux-node-v12
|
||||||
- build-osx-node-v8
|
- build-linux-node-v13
|
||||||
- build-osx-node-v10
|
- build-osx-node-v10
|
||||||
- build-osx-node-v12
|
- build-osx-node-v12
|
||||||
|
- build-osx-node-v13
|
||||||
|
|
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
custom: "https://maputnik.github.io/donate"
|
|
@ -1,9 +1,9 @@
|
||||||
image: Visual Studio 2015
|
image: Visual Studio 2019
|
||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- nodejs_version: "8"
|
|
||||||
- nodejs_version: "10"
|
- nodejs_version: "10"
|
||||||
- nodejs_version: "12"
|
- nodejs_version: "12"
|
||||||
|
- nodejs_version: "13"
|
||||||
platform:
|
platform:
|
||||||
- x86
|
- x86
|
||||||
- x64
|
- x64
|
||||||
|
@ -17,7 +17,7 @@ install:
|
||||||
Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform
|
Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform
|
||||||
}
|
}
|
||||||
- md public
|
- md public
|
||||||
- npm --vs2015 install --global windows-build-tools
|
- npm install --global windows-build-tools
|
||||||
- npm install
|
- npm install
|
||||||
build_script:
|
build_script:
|
||||||
- npm run build
|
- npm run build
|
||||||
|
|
20
config/webpack.profiling.config.js
Normal file
20
config/webpack.profiling.config.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
const webpackProdConfig = require('./webpack.production.config');
|
||||||
|
const artifacts = require("../test/artifacts");
|
||||||
|
|
||||||
|
const OUTPATH = artifacts.pathSync("/profiling");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...webpackProdConfig,
|
||||||
|
output: {
|
||||||
|
...webpackProdConfig.output,
|
||||||
|
path: OUTPATH,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
...webpackProdConfig.resolve,
|
||||||
|
alias: {
|
||||||
|
...webpackProdConfig.resolve.alias,
|
||||||
|
'react-dom$': 'react-dom/profiling',
|
||||||
|
'scheduler/tracing': 'scheduler/tracing-profiling',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
23
package-lock.json
generated
23
package-lock.json
generated
|
@ -964,9 +964,9 @@
|
||||||
"integrity": "sha512-RaCYfnxULUUUxNwcUimV9C/o2295ktTyLEUzD/+VWkqXqvaVfFcZ5slytGzb2Sd/Jj4MlbxD0DCZbfa6CzcmMw=="
|
"integrity": "sha512-RaCYfnxULUUUxNwcUimV9C/o2295ktTyLEUzD/+VWkqXqvaVfFcZ5slytGzb2Sd/Jj4MlbxD0DCZbfa6CzcmMw=="
|
||||||
},
|
},
|
||||||
"@mapbox/mapbox-gl-style-spec": {
|
"@mapbox/mapbox-gl-style-spec": {
|
||||||
"version": "13.9.0",
|
"version": "13.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.9.1.tgz",
|
||||||
"integrity": "sha512-w7wqxZ9pIyqyk30cj3ujmhaldnGhg9aNTmQX7nUE6aMuhhen0mMrVhTNgET11/LIMkr/yZE1BOdQ8Fbyb/2/FA==",
|
"integrity": "sha512-7sOXtrliGz3LAErjJc0q1MtYGcmgwwE1G/PzoTrhvSQTcexSVz+v88QKZ4lAzvhF36ItxzI/UdFilsssAw6hYQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@mapbox/jsonlint-lines-primitives": "~2.0.2",
|
"@mapbox/jsonlint-lines-primitives": "~2.0.2",
|
||||||
"@mapbox/unitbezier": "^0.0.0",
|
"@mapbox/unitbezier": "^0.0.0",
|
||||||
|
@ -7095,9 +7095,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mapbox-gl": {
|
"mapbox-gl": {
|
||||||
"version": "1.4.1",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.5.0.tgz",
|
||||||
"integrity": "sha512-4Jkf1JjsBFKlZA3BHHghgIogbbOuodjVjsdOR/j4AtfLx0G4jXrPcGvwSEVcwyQ27kVECBCn6EyRb6eUNUQujw==",
|
"integrity": "sha512-seTQUttE7XaL93on+zfLv06HmROsIdTh3riEPrBdbylSirLmBRnofG+iV873ZbJQElf+d2USyHpWAJm37RehEQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@mapbox/geojson-rewind": "^0.4.0",
|
"@mapbox/geojson-rewind": "^0.4.0",
|
||||||
"@mapbox/geojson-types": "^1.0.2",
|
"@mapbox/geojson-types": "^1.0.2",
|
||||||
|
@ -7115,7 +7115,7 @@
|
||||||
"grid-index": "^1.1.0",
|
"grid-index": "^1.1.0",
|
||||||
"minimist": "0.0.8",
|
"minimist": "0.0.8",
|
||||||
"murmurhash-js": "^1.0.0",
|
"murmurhash-js": "^1.0.0",
|
||||||
"pbf": "^3.0.5",
|
"pbf": "^3.2.1",
|
||||||
"potpack": "^1.0.1",
|
"potpack": "^1.0.1",
|
||||||
"quickselect": "^2.0.0",
|
"quickselect": "^2.0.0",
|
||||||
"rw": "^1.3.3",
|
"rw": "^1.3.3",
|
||||||
|
@ -9070,12 +9070,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-collapse": {
|
"react-collapse": {
|
||||||
"version": "4.0.3",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-collapse/-/react-collapse-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-collapse/-/react-collapse-5.0.1.tgz",
|
||||||
"integrity": "sha512-OO4NhtEqFtz+1ma31J1B7+ezdRnzHCZiTGSSd/Pxoks9hxrZYhzFEddeYt05A/1477xTtdrwo7xEa2FLJyWGCQ==",
|
"integrity": "sha512-cN2tkxBWizhPQ2JHfe0aUSJtmMthKA17NZkTElpiQ2snQAAi1hssXZ2fv88rAPNNvG5ss4t0PbOZT0TIl9Lk3Q=="
|
||||||
"requires": {
|
|
||||||
"prop-types": "^15.5.8"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"react-color": {
|
"react-color": {
|
||||||
"version": "2.17.3",
|
"version": "2.17.3",
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"stats": "webpack --config config/webpack.production.config.js --profile --json > stats.json",
|
"stats": "webpack --config config/webpack.production.config.js --profile --json > stats.json",
|
||||||
"build": "webpack --config config/webpack.production.config.js --progress --profile --colors",
|
"build": "webpack --config config/webpack.production.config.js --progress --profile --colors",
|
||||||
|
"profiling-build": "webpack --config config/webpack.profiling.config.js --progress --profile --colors",
|
||||||
"test": "cross-env NODE_ENV=test wdio config/wdio.conf.js",
|
"test": "cross-env NODE_ENV=test wdio config/wdio.conf.js",
|
||||||
"test-watch": "cross-env NODE_ENV=test wdio config/wdio.conf.js --watch",
|
"test-watch": "cross-env NODE_ENV=test wdio config/wdio.conf.js --watch",
|
||||||
"start": "webpack-dev-server --progress --profile --colors --config config/webpack.config.js",
|
"start": "webpack-dev-server --progress --profile --colors --config config/webpack.config.js",
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.6.3",
|
"@babel/runtime": "^7.6.3",
|
||||||
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",
|
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",
|
||||||
"@mapbox/mapbox-gl-style-spec": "^13.9.0",
|
"@mapbox/mapbox-gl-style-spec": "^13.9.1",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"codemirror": "^5.49.0",
|
"codemirror": "^5.49.0",
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.2",
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"mapbox-gl": "^1.4.1",
|
"mapbox-gl": "^1.5.0",
|
||||||
"mapbox-gl-inspect": "^1.3.1",
|
"mapbox-gl-inspect": "^1.3.1",
|
||||||
"maputnik-design": "github:maputnik/design",
|
"maputnik-design": "github:maputnik/design",
|
||||||
"ol": "^6.0.1",
|
"ol": "^6.0.1",
|
||||||
|
@ -47,7 +48,7 @@
|
||||||
"react-aria-modal": "^4.0.0",
|
"react-aria-modal": "^4.0.0",
|
||||||
"react-autobind": "^1.0.6",
|
"react-autobind": "^1.0.6",
|
||||||
"react-autocomplete": "^1.8.1",
|
"react-autocomplete": "^1.8.1",
|
||||||
"react-collapse": "^4.0.3",
|
"react-collapse": "^5.0.1",
|
||||||
"react-color": "^2.17.3",
|
"react-color": "^2.17.3",
|
||||||
"react-dom": "^16.10.2",
|
"react-dom": "^16.10.2",
|
||||||
"react-file-reader-input": "^2.0.0",
|
"react-file-reader-input": "^2.0.0",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-maputnik .CodeMirror-cursor {
|
.cm-s-maputnik .CodeMirror-cursor {
|
||||||
border-left: solid thin #8e8e8e !important;
|
border-left: solid thin #f0f0f0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-maputnik.CodeMirror-focused div.CodeMirror-selected {
|
.cm-s-maputnik.CodeMirror-focused div.CodeMirror-selected {
|
||||||
|
@ -47,5 +47,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-maputnik .CodeMirror-matchingbracket {
|
.cm-s-maputnik .CodeMirror-matchingbracket {
|
||||||
text-decoration: underline; color: white !important;
|
background-color: #f0f0f0;
|
||||||
|
color: #565659 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-s-maputnik .CodeMirror-nonmatchingbracket {
|
||||||
|
background-color: #bb0000;
|
||||||
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,6 +212,13 @@ export default class App extends React.Component {
|
||||||
vectorLayers: {},
|
vectorLayers: {},
|
||||||
mapState: "map",
|
mapState: "map",
|
||||||
spec: latest,
|
spec: latest,
|
||||||
|
mapView: {
|
||||||
|
zoom: 0,
|
||||||
|
center: {
|
||||||
|
lng: 0,
|
||||||
|
lat: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
isOpen: {
|
isOpen: {
|
||||||
settings: false,
|
settings: false,
|
||||||
sources: false,
|
sources: false,
|
||||||
|
@ -531,11 +538,22 @@ export default class App extends React.Component {
|
||||||
return metadata['maputnik:renderer'] || 'mbgljs';
|
return metadata['maputnik:renderer'] || 'mbgljs';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMapChange = (mapView) => {
|
||||||
|
this.setState({
|
||||||
|
mapView,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
mapRenderer() {
|
mapRenderer() {
|
||||||
const metadata = this.state.mapStyle.metadata || {};
|
const metadata = this.state.mapStyle.metadata || {};
|
||||||
|
|
||||||
const mapProps = {
|
const mapProps = {
|
||||||
mapStyle: style.replaceAccessTokens(this.state.mapStyle, {allowFallback: true}),
|
mapStyle: this.state.mapStyle,
|
||||||
|
replaceAccessTokens: (mapStyle) => {
|
||||||
|
return style.replaceAccessTokens(mapStyle, {
|
||||||
|
allowFallback: true
|
||||||
|
});
|
||||||
|
},
|
||||||
onDataChange: (e) => {
|
onDataChange: (e) => {
|
||||||
this.layerWatcher.analyzeMap(e.map)
|
this.layerWatcher.analyzeMap(e.map)
|
||||||
this.fetchSources();
|
this.fetchSources();
|
||||||
|
@ -550,11 +568,13 @@ export default class App extends React.Component {
|
||||||
if(renderer === 'ol') {
|
if(renderer === 'ol') {
|
||||||
mapElement = <OpenLayersMap
|
mapElement = <OpenLayersMap
|
||||||
{...mapProps}
|
{...mapProps}
|
||||||
|
onChange={this.onMapChange}
|
||||||
debugToolbox={this.state.openlayersDebugOptions.debugToolbox}
|
debugToolbox={this.state.openlayersDebugOptions.debugToolbox}
|
||||||
onLayerSelect={this.onLayerSelect}
|
onLayerSelect={this.onLayerSelect}
|
||||||
/>
|
/>
|
||||||
} else {
|
} else {
|
||||||
mapElement = <MapboxGlMap {...mapProps}
|
mapElement = <MapboxGlMap {...mapProps}
|
||||||
|
onChange={this.onMapChange}
|
||||||
options={this.state.mapboxGlDebugOptions}
|
options={this.state.mapboxGlDebugOptions}
|
||||||
inspectModeEnabled={this.state.mapState === "inspect"}
|
inspectModeEnabled={this.state.mapState === "inspect"}
|
||||||
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
|
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
|
||||||
|
@ -676,6 +696,7 @@ export default class App extends React.Component {
|
||||||
onChangeOpenlayersDebug={this.onChangeOpenlayersDebug}
|
onChangeOpenlayersDebug={this.onChangeOpenlayersDebug}
|
||||||
isOpen={this.state.isOpen.debug}
|
isOpen={this.state.isOpen.debug}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'debug')}
|
onOpenToggle={this.toggleModal.bind(this, 'debug')}
|
||||||
|
mapView={this.state.mapView}
|
||||||
/>
|
/>
|
||||||
<ShortcutsModal
|
<ShortcutsModal
|
||||||
ref={(el) => this.shortcutEl = el}
|
ref={(el) => this.shortcutEl = el}
|
||||||
|
|
|
@ -14,6 +14,25 @@ function isDataField(value) {
|
||||||
return typeof value === 'object' && value.stops && typeof value.property !== 'undefined'
|
return typeof value === 'object' && value.stops && typeof value.property !== 'undefined'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we don't have a default value just make one up
|
||||||
|
*/
|
||||||
|
function findDefaultFromSpec (spec) {
|
||||||
|
if (spec.hasOwnProperty('default')) {
|
||||||
|
return spec.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
'color': '#000000',
|
||||||
|
'string': '',
|
||||||
|
'boolean': false,
|
||||||
|
'number': 0,
|
||||||
|
'array': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaults[spec.type] || '';
|
||||||
|
}
|
||||||
|
|
||||||
/** Supports displaying spec field for zoom function objects
|
/** Supports displaying spec field for zoom function objects
|
||||||
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
|
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
|
||||||
*/
|
*/
|
||||||
|
@ -82,8 +101,8 @@ export default class FunctionSpecProperty extends React.Component {
|
||||||
makeZoomFunction = () => {
|
makeZoomFunction = () => {
|
||||||
const zoomFunc = {
|
const zoomFunc = {
|
||||||
stops: [
|
stops: [
|
||||||
[6, this.props.value],
|
[6, this.props.value || findDefaultFromSpec(this.props.fieldSpec)],
|
||||||
[10, this.props.value]
|
[10, this.props.value || findDefaultFromSpec(this.props.fieldSpec)]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
this.props.onChange(this.props.fieldName, zoomFunc)
|
this.props.onChange(this.props.fieldName, zoomFunc)
|
||||||
|
@ -96,8 +115,8 @@ export default class FunctionSpecProperty extends React.Component {
|
||||||
property: "",
|
property: "",
|
||||||
type: functionType,
|
type: functionType,
|
||||||
stops: [
|
stops: [
|
||||||
[{zoom: 6, value: stopValue}, this.props.value || stopValue],
|
[{zoom: 6, value: stopValue}, this.props.value || findDefaultFromSpec(this.props.fieldSpec)],
|
||||||
[{zoom: 10, value: stopValue}, this.props.value || stopValue]
|
[{zoom: 10, value: stopValue}, this.props.value || findDefaultFromSpec(this.props.fieldSpec)]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
this.props.onChange(this.props.fieldName, dataFunc)
|
this.props.onChange(this.props.fieldName, dataFunc)
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default class PropertyGroup extends React.Component {
|
||||||
onChange={this.onPropertyChange}
|
onChange={this.onPropertyChange}
|
||||||
key={fieldName}
|
key={fieldName}
|
||||||
fieldName={fieldName}
|
fieldName={fieldName}
|
||||||
value={fieldValue === undefined ? fieldSpec.default : fieldValue}
|
value={fieldValue}
|
||||||
fieldSpec={fieldSpec}
|
fieldSpec={fieldSpec}
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,7 +11,7 @@ import ArrayInput from '../inputs/ArrayInput'
|
||||||
import DynamicArrayInput from '../inputs/DynamicArrayInput'
|
import DynamicArrayInput from '../inputs/DynamicArrayInput'
|
||||||
import FontInput from '../inputs/FontInput'
|
import FontInput from '../inputs/FontInput'
|
||||||
import IconInput from '../inputs/IconInput'
|
import IconInput from '../inputs/IconInput'
|
||||||
import EnumInput from '../inputs/SelectInput'
|
import EnumInput from '../inputs/EnumInput'
|
||||||
import capitalize from 'lodash.capitalize'
|
import capitalize from 'lodash.capitalize'
|
||||||
|
|
||||||
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
|
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
|
||||||
|
@ -75,6 +75,7 @@ export default class SpecField extends React.Component {
|
||||||
{...commonProps}
|
{...commonProps}
|
||||||
options={options}
|
options={options}
|
||||||
/>
|
/>
|
||||||
|
case 'resolvedImage':
|
||||||
case 'formatted':
|
case 'formatted':
|
||||||
case 'string':
|
case 'string':
|
||||||
if(iconProperties.indexOf(this.props.fieldName) >= 0) {
|
if(iconProperties.indexOf(this.props.fieldName) >= 0) {
|
||||||
|
|
|
@ -8,11 +8,32 @@ import StringInput from '../inputs/StringInput'
|
||||||
import SelectInput from '../inputs/SelectInput'
|
import SelectInput from '../inputs/SelectInput'
|
||||||
import DocLabel from './DocLabel'
|
import DocLabel from './DocLabel'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
|
import docUid from '../../libs/document-uid'
|
||||||
|
import sortNumerically from '../../libs/sort-numerically'
|
||||||
|
|
||||||
import labelFromFieldName from './_labelFromFieldName'
|
import labelFromFieldName from './_labelFromFieldName'
|
||||||
import DeleteStopButton from './_DeleteStopButton'
|
import DeleteStopButton from './_DeleteStopButton'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function setStopRefs(props, state) {
|
||||||
|
// This is initialsed below only if required to improved performance.
|
||||||
|
let newRefs;
|
||||||
|
|
||||||
|
if(props.value && props.value.stops) {
|
||||||
|
props.value.stops.forEach((val, idx) => {
|
||||||
|
if(!state.refs.hasOwnProperty(idx)) {
|
||||||
|
if(!newRefs) {
|
||||||
|
newRefs = {...state};
|
||||||
|
}
|
||||||
|
newRefs[idx] = docUid("stop-");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRefs;
|
||||||
|
}
|
||||||
|
|
||||||
export default class DataProperty extends React.Component {
|
export default class DataProperty extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
|
@ -29,6 +50,30 @@ export default class DataProperty extends React.Component {
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
refs: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const newRefs = setStopRefs(this.props, this.state);
|
||||||
|
|
||||||
|
if(newRefs) {
|
||||||
|
this.setState({
|
||||||
|
refs: newRefs
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
const newRefs = setStopRefs(props, state);
|
||||||
|
if(newRefs) {
|
||||||
|
return {
|
||||||
|
refs: newRefs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getFieldFunctionType(fieldSpec) {
|
getFieldFunctionType(fieldSpec) {
|
||||||
if (fieldSpec.expression.interpolated) {
|
if (fieldSpec.expression.interpolated) {
|
||||||
return "exponential"
|
return "exponential"
|
||||||
|
@ -48,14 +93,42 @@ export default class DataProperty extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Order the stops altering the refs to reflect their new position.
|
||||||
|
orderStopsByZoom(stops) {
|
||||||
|
const mappedWithRef = stops
|
||||||
|
.map((stop, idx) => {
|
||||||
|
return {
|
||||||
|
ref: this.state.refs[idx],
|
||||||
|
data: stop
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Sort by zoom
|
||||||
|
.sort((a, b) => sortNumerically(a.data[0].zoom, b.data[0].zoom));
|
||||||
|
|
||||||
|
// Fetch the new position of the stops
|
||||||
|
const newRefs = {};
|
||||||
|
mappedWithRef
|
||||||
|
.forEach((stop, idx) =>{
|
||||||
|
newRefs[idx] = stop.ref;
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
refs: newRefs
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappedWithRef.map((item) => item.data);
|
||||||
|
}
|
||||||
|
|
||||||
changeStop(changeIdx, stopData, value) {
|
changeStop(changeIdx, stopData, value) {
|
||||||
const stops = this.props.value.stops.slice(0)
|
const stops = this.props.value.stops.slice(0)
|
||||||
const changedStop = stopData.zoom === undefined ? stopData.value : stopData
|
const changedStop = stopData.zoom === undefined ? stopData.value : stopData
|
||||||
stops[changeIdx] = [changedStop, value]
|
stops[changeIdx] = [changedStop, value]
|
||||||
|
|
||||||
|
const orderedStops = this.orderStopsByZoom(stops);
|
||||||
|
|
||||||
const changedValue = {
|
const changedValue = {
|
||||||
...this.props.value,
|
...this.props.value,
|
||||||
stops: stops,
|
stops: orderedStops,
|
||||||
}
|
}
|
||||||
this.props.onChange(this.props.fieldName, changedValue)
|
this.props.onChange(this.props.fieldName, changedValue)
|
||||||
}
|
}
|
||||||
|
@ -77,6 +150,7 @@ export default class DataProperty extends React.Component {
|
||||||
|
|
||||||
const dataFields = this.props.value.stops.map((stop, idx) => {
|
const dataFields = this.props.value.stops.map((stop, idx) => {
|
||||||
const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined;
|
const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined;
|
||||||
|
const key = this.state.refs[idx];
|
||||||
const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0];
|
const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0];
|
||||||
const value = stop[1]
|
const value = stop[1]
|
||||||
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} />
|
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} />
|
||||||
|
@ -107,7 +181,7 @@ export default class DataProperty extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <InputBlock key={idx} action={deleteStopBtn} label="">
|
return <InputBlock key={key} action={deleteStopBtn} label="">
|
||||||
{zoomInput}
|
{zoomInput}
|
||||||
<div className="maputnik-data-spec-property-stop-data">
|
<div className="maputnik-data-spec-property-stop-data">
|
||||||
{dataInput}
|
{dataInput}
|
||||||
|
|
|
@ -12,29 +12,89 @@ class ArrayInput extends React.Component {
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
changeValue(idx, newValue) {
|
static defaultProps = {
|
||||||
console.log(idx, newValue)
|
value: [],
|
||||||
const values = this.values.slice(0)
|
default: [],
|
||||||
values[idx] = newValue
|
|
||||||
this.props.onChange(values)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get values() {
|
constructor (props) {
|
||||||
return this.props.value || this.props.default || []
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
value: this.props.value.slice(0),
|
||||||
|
// This is so we can compare changes in getDerivedStateFromProps
|
||||||
|
initialPropsValue: this.props.value.slice(0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
const value = [];
|
||||||
|
const initialPropsValue = state.initialPropsValue.slice(0);
|
||||||
|
|
||||||
|
Array(props.length).fill(null).map((_, i) => {
|
||||||
|
if (props.value[i] === state.initialPropsValue[i]) {
|
||||||
|
value[i] = state.value[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value[i] = state.value[i];
|
||||||
|
initialPropsValue[i] = state.value[i];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
initialPropsValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
isComplete (value) {
|
||||||
|
return Array(this.props.length).fill(null).every((_, i) => {
|
||||||
|
const val = value[i]
|
||||||
|
return !(val === undefined || val === "");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changeValue(idx, newValue) {
|
||||||
|
const value = this.state.value.slice(0);
|
||||||
|
value[idx] = newValue;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
value,
|
||||||
|
}, () => {
|
||||||
|
if (this.isComplete(value)) {
|
||||||
|
this.props.onChange(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Unset until complete
|
||||||
|
this.props.onChange(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const inputs = this.values.map((v, i) => {
|
const {value} = this.state;
|
||||||
|
|
||||||
|
const containsValues = (
|
||||||
|
value.length > 0 &&
|
||||||
|
!value.every(val => {
|
||||||
|
return (val === "" || val === undefined)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const inputs = Array(this.props.length).fill(null).map((_, i) => {
|
||||||
if(this.props.type === 'number') {
|
if(this.props.type === 'number') {
|
||||||
return <NumberInput
|
return <NumberInput
|
||||||
key={i}
|
key={i}
|
||||||
value={v}
|
default={containsValues ? undefined : this.props.default[i]}
|
||||||
|
value={value[i]}
|
||||||
|
required={containsValues ? true : false}
|
||||||
onChange={this.changeValue.bind(this, i)}
|
onChange={this.changeValue.bind(this, i)}
|
||||||
/>
|
/>
|
||||||
} else {
|
} else {
|
||||||
return <StringInput
|
return <StringInput
|
||||||
key={i}
|
key={i}
|
||||||
value={v}
|
default={containsValues ? undefined : this.props.default[i]}
|
||||||
|
value={value[i]}
|
||||||
|
required={containsValues ? true : false}
|
||||||
onChange={this.changeValue.bind(this, i)}
|
onChange={this.changeValue.bind(this, i)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,17 +29,17 @@ class EnumInput extends React.Component {
|
||||||
if(options.length <= 3 && optionsLabelLength(options) <= 20) {
|
if(options.length <= 3 && optionsLabelLength(options) <= 20) {
|
||||||
return <MultiButtonInput
|
return <MultiButtonInput
|
||||||
options={options}
|
options={options}
|
||||||
value={value}
|
value={value || this.props.default}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
} else {
|
} else {
|
||||||
return <SelectInput
|
return <SelectInput
|
||||||
options={options}
|
options={options}
|
||||||
value={value}
|
value={value || this.props.default}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default StringInput
|
export default EnumInput
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
let IDX = 0;
|
||||||
|
|
||||||
class NumberInput extends React.Component {
|
class NumberInput extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
value: PropTypes.number,
|
value: PropTypes.number,
|
||||||
|
@ -8,36 +10,52 @@ class NumberInput extends React.Component {
|
||||||
min: PropTypes.number,
|
min: PropTypes.number,
|
||||||
max: PropTypes.number,
|
max: PropTypes.number,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
|
allowRange: PropTypes.bool,
|
||||||
|
rangeStep: PropTypes.number,
|
||||||
|
wdKey: PropTypes.string,
|
||||||
|
required: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
rangeStep: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
|
uuid: IDX++,
|
||||||
editing: false,
|
editing: false,
|
||||||
value: props.value,
|
value: props.value,
|
||||||
|
dirtyValue: props.value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props, state) {
|
static getDerivedStateFromProps(props, state) {
|
||||||
if (!state.editing) {
|
if (!state.editing) {
|
||||||
return {
|
return {
|
||||||
value: props.value
|
value: props.value,
|
||||||
|
dirtyValue: props.value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {};
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
changeValue(newValue) {
|
changeValue(newValue) {
|
||||||
this.setState({editing: true});
|
|
||||||
const value = (newValue === "" || newValue === undefined) ?
|
const value = (newValue === "" || newValue === undefined) ?
|
||||||
undefined :
|
undefined :
|
||||||
parseFloat(newValue);
|
parseFloat(newValue);
|
||||||
|
|
||||||
const hasChanged = this.state.value !== value
|
const hasChanged = this.props.value !== value;
|
||||||
if(this.isValid(value) && hasChanged) {
|
if(this.isValid(value) && hasChanged) {
|
||||||
this.props.onChange(value)
|
this.props.onChange(value)
|
||||||
|
this.setState({
|
||||||
|
dirtyValue: newValue,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.setState({ value: newValue })
|
|
||||||
|
this.setState({
|
||||||
|
value: newValue,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid(v) {
|
isValid(v) {
|
||||||
|
@ -65,7 +83,7 @@ class NumberInput extends React.Component {
|
||||||
this.setState({editing: false});
|
this.setState({editing: false});
|
||||||
// Reset explicitly to default value if value has been cleared
|
// Reset explicitly to default value if value has been cleared
|
||||||
if(this.state.value === "") {
|
if(this.state.value === "") {
|
||||||
return this.changeValue(this.props.default)
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If set value is invalid fall back to the last valid value from props or at last resort the default value
|
// If set value is invalid fall back to the last valid value from props or at last resort the default value
|
||||||
|
@ -73,21 +91,118 @@ class NumberInput extends React.Component {
|
||||||
if(this.isValid(this.props.value)) {
|
if(this.isValid(this.props.value)) {
|
||||||
this.changeValue(this.props.value)
|
this.changeValue(this.props.value)
|
||||||
} else {
|
} else {
|
||||||
this.changeValue(this.props.default)
|
this.changeValue(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeRange = (e) => {
|
||||||
|
let value = parseFloat(e.target.value, 10);
|
||||||
|
const step = this.props.rangeStep;
|
||||||
|
let dirtyValue = value;
|
||||||
|
|
||||||
|
if(step) {
|
||||||
|
// Can't do this with the <input/> range step attribute else we won't be able to set a high precision value via the text input.
|
||||||
|
const snap = value % step;
|
||||||
|
|
||||||
|
// Round up/down to step
|
||||||
|
if (this._keyboardEvent) {
|
||||||
|
// If it's keyboard event we might get a low positive/negative value,
|
||||||
|
// for example we might go from 13 to 13.23, however because we know
|
||||||
|
// that came from a keyboard event we always want to increase by a
|
||||||
|
// single step value.
|
||||||
|
if (value < this.state.dirtyValue) {
|
||||||
|
value = this.state.value - step;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = this.state.value + step
|
||||||
|
}
|
||||||
|
dirtyValue = value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (snap < step/2) {
|
||||||
|
value = value - snap;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = value + (step - snap);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._keyboardEvent = false;
|
||||||
|
|
||||||
|
// Clamp between min/max
|
||||||
|
value = Math.max(this.props.min, Math.min(this.props.max, value));
|
||||||
|
|
||||||
|
this.setState({value, dirtyValue});
|
||||||
|
this.props.onChange(value);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if(
|
||||||
|
this.props.hasOwnProperty("min") && this.props.hasOwnProperty("max") &&
|
||||||
|
this.props.min !== undefined && this.props.max !== undefined &&
|
||||||
|
this.props.allowRange
|
||||||
|
) {
|
||||||
|
const dirtyValue = this.state.dirtyValue === undefined ? this.props.default : this.state.dirtyValue
|
||||||
|
const value = this.state.value === undefined ? "" : this.state.value;
|
||||||
|
|
||||||
|
return <div className="maputnik-number-container">
|
||||||
|
<input
|
||||||
|
className="maputnik-number-range"
|
||||||
|
key="range"
|
||||||
|
type="range"
|
||||||
|
max={this.props.max}
|
||||||
|
min={this.props.min}
|
||||||
|
step="any"
|
||||||
|
spellCheck="false"
|
||||||
|
value={dirtyValue}
|
||||||
|
aria-hidden="true"
|
||||||
|
onChange={this.onChangeRange}
|
||||||
|
onKeyDown={() => {
|
||||||
|
this._keyboardEvent = true;
|
||||||
|
}}
|
||||||
|
onPointerDown={() => {
|
||||||
|
this.setState({editing: true});
|
||||||
|
}}
|
||||||
|
onPointerUp={() => {
|
||||||
|
// Safari doesn't get onBlur event
|
||||||
|
this.setState({editing: false});
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
this.setState({editing: false});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
key="text"
|
||||||
|
type="text"
|
||||||
|
spellCheck="false"
|
||||||
|
className="maputnik-number"
|
||||||
|
placeholder={this.props.default}
|
||||||
|
value={value}
|
||||||
|
onChange={e => {
|
||||||
|
if (!this.state.editing) {
|
||||||
|
this.changeValue(e.target.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={this.resetValue}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const value = this.state.value === undefined ? "" : this.state.value;
|
||||||
|
|
||||||
return <input
|
return <input
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
className="maputnik-number"
|
className="maputnik-number"
|
||||||
placeholder={this.props.default}
|
placeholder={this.props.default}
|
||||||
value={this.state.value || ""}
|
value={value}
|
||||||
onChange={e => this.changeValue(e.target.value)}
|
onChange={e => this.changeValue(e.target.value)}
|
||||||
onBlur={this.resetValue}
|
onBlur={this.resetValue}
|
||||||
|
required={this.props.required}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NumberInput
|
export default NumberInput
|
||||||
|
|
|
@ -8,7 +8,13 @@ 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
onInput: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -50,20 +56,23 @@ class StringInput extends React.Component {
|
||||||
spellCheck: !(tag === "input"),
|
spellCheck: !(tag === "input"),
|
||||||
className: classes.join(" "),
|
className: classes.join(" "),
|
||||||
style: this.props.style,
|
style: this.props.style,
|
||||||
value: this.state.value,
|
value: this.state.value === undefined ? "" : this.state.value,
|
||||||
placeholder: this.props.default,
|
placeholder: this.props.default,
|
||||||
onChange: e => {
|
onChange: e => {
|
||||||
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) {
|
||||||
this.setState({editing: false});
|
this.setState({editing: false});
|
||||||
this.props.onChange(this.state.value);
|
this.props.onChange(this.state.value);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
required: this.props.required,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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
|
|
@ -7,6 +7,7 @@ import CodeMirror from 'codemirror';
|
||||||
|
|
||||||
import 'codemirror/mode/javascript/javascript'
|
import 'codemirror/mode/javascript/javascript'
|
||||||
import 'codemirror/addon/lint/lint'
|
import 'codemirror/addon/lint/lint'
|
||||||
|
import 'codemirror/addon/edit/matchbrackets'
|
||||||
import 'codemirror/lib/codemirror.css'
|
import 'codemirror/lib/codemirror.css'
|
||||||
import 'codemirror/addon/lint/lint.css'
|
import 'codemirror/addon/lint/lint.css'
|
||||||
import '../../codemirror-maputnik.css'
|
import '../../codemirror-maputnik.css'
|
||||||
|
@ -19,6 +20,7 @@ import '../../vendor/codemirror/addon/lint/json-lint'
|
||||||
class JSONEditor extends React.Component {
|
class JSONEditor extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
layer: PropTypes.object.isRequired,
|
layer: PropTypes.object.isRequired,
|
||||||
|
maxHeight: PropTypes.number,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +48,7 @@ class JSONEditor extends React.Component {
|
||||||
viewportMargin: Infinity,
|
viewportMargin: Infinity,
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
lint: true,
|
lint: true,
|
||||||
|
matchBrackets: true,
|
||||||
gutters: ["CodeMirror-lint-markers"],
|
gutters: ["CodeMirror-lint-markers"],
|
||||||
scrollbarStyle: "null",
|
scrollbarStyle: "null",
|
||||||
});
|
});
|
||||||
|
@ -104,20 +107,15 @@ class JSONEditor extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const codeMirrorOptions = {
|
const style = {};
|
||||||
mode: {name: "javascript", json: true},
|
if (this.props.maxHeight) {
|
||||||
tabSize: 2,
|
style.maxHeight = this.props.maxHeight;
|
||||||
theme: 'maputnik',
|
|
||||||
viewportMargin: Infinity,
|
|
||||||
lineNumbers: true,
|
|
||||||
lint: true,
|
|
||||||
gutters: ["CodeMirror-lint-markers"],
|
|
||||||
scrollbarStyle: "null",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div
|
return <div
|
||||||
className="codemirror-container"
|
className="codemirror-container"
|
||||||
ref={(el) => this._el = el}
|
ref={(el) => this._el = el}
|
||||||
|
style={style}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ export default class LayerEditor extends React.Component {
|
||||||
onChange={v => this.changeProperty(null, 'source', v)}
|
onChange={v => this.changeProperty(null, 'source', v)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
|
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.props.layer.type) < 0 &&
|
||||||
<LayerSourceLayerBlock
|
<LayerSourceLayerBlock
|
||||||
sourceLayerIds={sourceLayerIds}
|
sourceLayerIds={sourceLayerIds}
|
||||||
value={this.props.layer['source-layer']}
|
value={this.props.layer['source-layer']}
|
||||||
|
|
|
@ -16,6 +16,7 @@ class MaxZoomBlock extends React.Component {
|
||||||
data-wd-key="max-zoom"
|
data-wd-key="max-zoom"
|
||||||
>
|
>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
|
allowRange={true}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
min={latest.layer.maxzoom.minimum}
|
min={latest.layer.maxzoom.minimum}
|
||||||
|
|
|
@ -16,6 +16,7 @@ class MinZoomBlock extends React.Component {
|
||||||
data-wd-key="min-zoom"
|
data-wd-key="min-zoom"
|
||||||
>
|
>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
|
allowRange={true}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
min={latest.layer.minzoom.minimum}
|
min={latest.layer.minzoom.minimum}
|
||||||
|
|
|
@ -19,8 +19,7 @@ const IS_SUPPORTED = MapboxGl.supported();
|
||||||
|
|
||||||
function renderPopup(popup, mountNode) {
|
function renderPopup(popup, mountNode) {
|
||||||
ReactDOM.render(popup, mountNode);
|
ReactDOM.render(popup, mountNode);
|
||||||
var content = mountNode.innerHTML;
|
return mountNode;
|
||||||
return content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
|
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
|
||||||
|
@ -61,12 +60,15 @@ export default class MapboxGlMap extends React.Component {
|
||||||
inspectModeEnabled: PropTypes.bool.isRequired,
|
inspectModeEnabled: PropTypes.bool.isRequired,
|
||||||
highlightedLayer: PropTypes.object,
|
highlightedLayer: PropTypes.object,
|
||||||
options: PropTypes.object,
|
options: PropTypes.object,
|
||||||
|
replaceAccessTokens: PropTypes.func.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onMapLoaded: () => {},
|
onMapLoaded: () => {},
|
||||||
onDataChange: () => {},
|
onDataChange: () => {},
|
||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
|
onChange: () => {},
|
||||||
mapboxAccessToken: tokens.mapbox,
|
mapboxAccessToken: tokens.mapbox,
|
||||||
options: {},
|
options: {},
|
||||||
}
|
}
|
||||||
|
@ -87,21 +89,12 @@ export default class MapboxGlMap extends React.Component {
|
||||||
const metadata = props.mapStyle.metadata || {}
|
const metadata = props.mapStyle.metadata || {}
|
||||||
MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox
|
MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox
|
||||||
|
|
||||||
if(!props.inspectModeEnabled) {
|
|
||||||
//Mapbox GL now does diffing natively so we don't need to calculate
|
//Mapbox GL now does diffing natively so we don't need to calculate
|
||||||
//the necessary operations ourselves!
|
//the necessary operations ourselves!
|
||||||
this.state.map.setStyle(props.mapStyle, { diff: true})
|
this.state.map.setStyle(
|
||||||
}
|
this.props.replaceAccessTokens(props.mapStyle),
|
||||||
}
|
{diff: true}
|
||||||
|
)
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
let should = false;
|
|
||||||
try {
|
|
||||||
should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState);
|
|
||||||
} catch(e) {
|
|
||||||
// no biggie, carry on
|
|
||||||
}
|
|
||||||
return should;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
@ -112,6 +105,9 @@ export default class MapboxGlMap extends React.Component {
|
||||||
this.updateMapFromProps(this.props);
|
this.updateMapFromProps(this.props);
|
||||||
|
|
||||||
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
|
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
|
||||||
|
// HACK: Fix for <https://github.com/maputnik/editor/issues/576>, while we wait for a proper fix.
|
||||||
|
// eslint-disable-next-line
|
||||||
|
this.state.inspect._popupBlocked = false;
|
||||||
this.state.inspect.toggleInspector()
|
this.state.inspect.toggleInspector()
|
||||||
}
|
}
|
||||||
if(this.props.inspectModeEnabled) {
|
if(this.props.inspectModeEnabled) {
|
||||||
|
@ -133,16 +129,24 @@ export default class MapboxGlMap extends React.Component {
|
||||||
container: this.container,
|
container: this.container,
|
||||||
style: this.props.mapStyle,
|
style: this.props.mapStyle,
|
||||||
hash: true,
|
hash: true,
|
||||||
|
maxZoom: 24
|
||||||
}
|
}
|
||||||
|
|
||||||
const map = new MapboxGl.Map(mapOpts);
|
const map = new MapboxGl.Map(mapOpts);
|
||||||
|
|
||||||
|
const mapViewChange = () => {
|
||||||
|
const center = map.getCenter();
|
||||||
|
const zoom = map.getZoom();
|
||||||
|
this.props.onChange({center, zoom});
|
||||||
|
}
|
||||||
|
mapViewChange();
|
||||||
|
|
||||||
map.showTileBoundaries = mapOpts.showTileBoundaries;
|
map.showTileBoundaries = mapOpts.showTileBoundaries;
|
||||||
map.showCollisionBoxes = mapOpts.showCollisionBoxes;
|
map.showCollisionBoxes = mapOpts.showCollisionBoxes;
|
||||||
map.showOverdrawInspector = mapOpts.showOverdrawInspector;
|
map.showOverdrawInspector = mapOpts.showOverdrawInspector;
|
||||||
|
|
||||||
const zoom = new ZoomControl;
|
const zoomControl = new ZoomControl;
|
||||||
map.addControl(zoom, 'top-right');
|
map.addControl(zoomControl, 'top-right');
|
||||||
|
|
||||||
const nav = new MapboxGl.NavigationControl({visualizePitch:true});
|
const nav = new MapboxGl.NavigationControl({visualizePitch:true});
|
||||||
map.addControl(nav, 'top-right');
|
map.addControl(nav, 'top-right');
|
||||||
|
@ -190,11 +194,18 @@ 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()
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
map.on("dragend", mapViewChange);
|
||||||
|
map.on("zoomend", mapViewChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -32,6 +32,8 @@ export default class OpenLayersMap extends React.Component {
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
onLayerSelect: PropTypes.func.isRequired,
|
onLayerSelect: PropTypes.func.isRequired,
|
||||||
debugToolbox: PropTypes.bool.isRequired,
|
debugToolbox: PropTypes.bool.isRequired,
|
||||||
|
replaceAccessTokens: PropTypes.func.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -61,7 +63,9 @@ export default class OpenLayersMap extends React.Component {
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.mapStyle !== prevProps.mapStyle) {
|
if (this.props.mapStyle !== prevProps.mapStyle) {
|
||||||
this.updateStyle(this.props.mapStyle);
|
this.updateStyle(
|
||||||
|
this.props.replaceAccessTokens(this.props.mapStyle)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +97,22 @@ export default class OpenLayersMap extends React.Component {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onMoveEnd = () => {
|
||||||
|
const zoom = map.getView().getZoom();
|
||||||
|
const center = toLonLat(map.getView().getCenter());
|
||||||
|
|
||||||
|
this.props.onChange({
|
||||||
|
zoom,
|
||||||
|
center: {
|
||||||
|
lng: center[0],
|
||||||
|
lat: center[1],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMoveEnd();
|
||||||
|
map.on('moveend', onMoveEnd);
|
||||||
|
|
||||||
map.on('postrender', (evt) => {
|
map.on('postrender', (evt) => {
|
||||||
const center = toLonLat(map.getView().getCenter());
|
const center = toLonLat(map.getView().getCenter());
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -108,7 +128,9 @@ export default class OpenLayersMap extends React.Component {
|
||||||
|
|
||||||
|
|
||||||
this.map = map;
|
this.map = map;
|
||||||
this.updateStyle(this.props.mapStyle);
|
this.updateStyle(
|
||||||
|
this.props.replaceAccessTokens(this.props.mapStyle)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
closeOverlay = (e) => {
|
closeOverlay = (e) => {
|
||||||
|
|
|
@ -13,9 +13,16 @@ class DebugModal extends React.Component {
|
||||||
onOpenToggle: PropTypes.func.isRequired,
|
onOpenToggle: PropTypes.func.isRequired,
|
||||||
mapboxGlDebugOptions: PropTypes.object,
|
mapboxGlDebugOptions: PropTypes.object,
|
||||||
openlayersDebugOptions: PropTypes.object,
|
openlayersDebugOptions: PropTypes.object,
|
||||||
|
mapView: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {mapView} = this.props;
|
||||||
|
|
||||||
|
const osmZoom = Math.round(mapView.zoom)+1;
|
||||||
|
const osmLon = Number.parseFloat(mapView.center.lng).toFixed(5);
|
||||||
|
const osmLat = Number.parseFloat(mapView.center.lat).toFixed(5);
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="debug-modal"
|
data-wd-key="debug-modal"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
|
@ -23,6 +30,7 @@ class DebugModal extends React.Component {
|
||||||
title={'Debug'}
|
title={'Debug'}
|
||||||
>
|
>
|
||||||
<div className="maputnik-modal-section maputnik-modal-shortcuts">
|
<div className="maputnik-modal-section maputnik-modal-shortcuts">
|
||||||
|
<h4>Options</h4>
|
||||||
{this.props.renderer === 'mbgljs' &&
|
{this.props.renderer === 'mbgljs' &&
|
||||||
<ul>
|
<ul>
|
||||||
{Object.entries(this.props.mapboxGlDebugOptions).map(([key, val]) => {
|
{Object.entries(this.props.mapboxGlDebugOptions).map(([key, val]) => {
|
||||||
|
@ -46,6 +54,18 @@ class DebugModal extends React.Component {
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="maputnik-modal-section">
|
||||||
|
<h4>Links</h4>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href={`https://www.openstreetmap.org/#map=${osmZoom}/${osmLat}/${osmLon}`}
|
||||||
|
>
|
||||||
|
Open in OSM
|
||||||
|
</a> — Opens the current view on openstreetmap.org
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -3,8 +3,13 @@ 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 ArrayInput from '../inputs/ArrayInput'
|
||||||
|
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 ColorField from '../fields/ColorField'
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
|
|
||||||
class SettingsModal extends React.Component {
|
class SettingsModal extends React.Component {
|
||||||
|
@ -16,25 +21,71 @@ class SettingsModal extends React.Component {
|
||||||
onOpenToggle: PropTypes.func.isRequired,
|
onOpenToggle: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeTransitionProperty(property, value) {
|
||||||
|
const transition = {
|
||||||
|
...this.props.mapStyle.transition,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === undefined) {
|
||||||
|
delete transition[property];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
transition[property] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onStyleChanged({
|
||||||
|
...this.props.mapStyle,
|
||||||
|
transition,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changeLightProperty(property, value) {
|
||||||
|
const light = {
|
||||||
|
...this.props.mapStyle.light,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === undefined) {
|
||||||
|
delete light[property];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
light[property] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onStyleChanged({
|
||||||
|
...this.props.mapStyle,
|
||||||
|
light,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
changeStyleProperty(property, value) {
|
changeStyleProperty(property, value) {
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...this.props.mapStyle,
|
...this.props.mapStyle,
|
||||||
[property]: value
|
};
|
||||||
|
|
||||||
|
if (value === undefined) {
|
||||||
|
delete changedStyle[property];
|
||||||
}
|
}
|
||||||
this.props.onStyleChanged(changedStyle)
|
else {
|
||||||
|
changedStyle[property] = value;
|
||||||
|
}
|
||||||
|
this.props.onStyleChanged(changedStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const metadata = this.props.mapStyle.metadata || {}
|
const metadata = this.props.mapStyle.metadata || {}
|
||||||
const {onChangeMetadataProperty} = this.props;
|
const {onChangeMetadataProperty, mapStyle} = this.props;
|
||||||
const inputProps = { }
|
const inputProps = { }
|
||||||
|
|
||||||
|
const light = this.props.mapStyle.light || {};
|
||||||
|
const transition = this.props.mapStyle.transition || {};
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="modal-settings"
|
data-wd-key="modal-settings"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
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"
|
||||||
|
@ -50,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")}
|
||||||
|
@ -58,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")}
|
||||||
|
@ -89,6 +140,100 @@ class SettingsModal extends React.Component {
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Center"} doc={latest.$root.center.doc}>
|
||||||
|
<ArrayInput
|
||||||
|
length={2}
|
||||||
|
type="number"
|
||||||
|
value={mapStyle.center}
|
||||||
|
default={latest.$root.center.default || [0, 0]}
|
||||||
|
onChange={this.changeStyleProperty.bind(this, "center")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Zoom"} doc={latest.$root.zoom.doc}>
|
||||||
|
<NumberInput
|
||||||
|
{...inputProps}
|
||||||
|
value={mapStyle.zoom}
|
||||||
|
default={latest.$root.zoom.default || 0}
|
||||||
|
onChange={this.changeStyleProperty.bind(this, "zoom")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Bearing"} doc={latest.$root.bearing.doc}>
|
||||||
|
<NumberInput
|
||||||
|
{...inputProps}
|
||||||
|
value={mapStyle.bearing}
|
||||||
|
default={latest.$root.bearing.default}
|
||||||
|
onChange={this.changeStyleProperty.bind(this, "bearing")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Pitch"} doc={latest.$root.pitch.doc}>
|
||||||
|
<NumberInput
|
||||||
|
{...inputProps}
|
||||||
|
value={mapStyle.pitch}
|
||||||
|
default={latest.$root.pitch.default}
|
||||||
|
onChange={this.changeStyleProperty.bind(this, "pitch")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Light anchor"} doc={latest.light.anchor.doc}>
|
||||||
|
<EnumInput
|
||||||
|
{...inputProps}
|
||||||
|
value={light.anchor}
|
||||||
|
options={Object.keys(latest.light.anchor.values)}
|
||||||
|
default={latest.light.anchor.default}
|
||||||
|
onChange={this.changeLightProperty.bind(this, "anchor")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Light color"} doc={latest.light.color.doc}>
|
||||||
|
<ColorField
|
||||||
|
{...inputProps}
|
||||||
|
value={light.color}
|
||||||
|
default={latest.light.color.default}
|
||||||
|
onChange={this.changeLightProperty.bind(this, "color")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Light intensity"} doc={latest.light.intensity.doc}>
|
||||||
|
<NumberInput
|
||||||
|
{...inputProps}
|
||||||
|
value={light.intensity}
|
||||||
|
default={latest.light.intensity.default}
|
||||||
|
onChange={this.changeLightProperty.bind(this, "intensity")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Light position"} doc={latest.light.position.doc}>
|
||||||
|
<ArrayInput
|
||||||
|
{...inputProps}
|
||||||
|
type="number"
|
||||||
|
length={latest.light.position.length}
|
||||||
|
value={light.position}
|
||||||
|
default={latest.light.position.default}
|
||||||
|
onChange={this.changeLightProperty.bind(this, "position")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Transition delay"} doc={latest.transition.delay.doc}>
|
||||||
|
<NumberInput
|
||||||
|
{...inputProps}
|
||||||
|
value={transition.delay}
|
||||||
|
default={latest.transition.delay.default}
|
||||||
|
onChange={this.changeTransitionProperty.bind(this, "delay")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Transition duration"} doc={latest.transition.duration.doc}>
|
||||||
|
<NumberInput
|
||||||
|
{...inputProps}
|
||||||
|
value={transition.duration}
|
||||||
|
default={latest.transition.duration.default}
|
||||||
|
onChange={this.changeTransitionProperty.bind(this, "duration")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
<InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}>
|
<InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}>
|
||||||
<SelectInput {...inputProps}
|
<SelectInput {...inputProps}
|
||||||
data-wd-key="modal-settings.maputnik:renderer"
|
data-wd-key="modal-settings.maputnik:renderer"
|
||||||
|
@ -101,6 +246,8 @@ class SettingsModal extends React.Component {
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,14 @@ function editorMode(source) {
|
||||||
if(source.tiles) return 'tilexyz_vector'
|
if(source.tiles) return 'tilexyz_vector'
|
||||||
return 'tilejson_vector'
|
return 'tilejson_vector'
|
||||||
}
|
}
|
||||||
if(source.type === 'geojson') return 'geojson'
|
if(source.type === 'geojson') {
|
||||||
|
if (typeof(source.data) === "string") {
|
||||||
|
return 'geojson_url';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'geojson_json';
|
||||||
|
}
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,38 +112,44 @@ 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': return {
|
case 'geojson_url': return {
|
||||||
type: 'geojson',
|
type: 'geojson',
|
||||||
data: source.data || 'http://localhost:3000/geojson.json'
|
data: `${protocol}//localhost:3000/geojson.json`
|
||||||
|
}
|
||||||
|
case 'geojson_json': return {
|
||||||
|
type: 'geojson',
|
||||||
|
data: {}
|
||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -144,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."}>
|
||||||
|
@ -155,7 +177,8 @@ class AddSource extends React.Component {
|
||||||
<InputBlock label={"Source Type"} doc={latest.source_vector.type.doc}>
|
<InputBlock label={"Source Type"} doc={latest.source_vector.type.doc}>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
options={[
|
options={[
|
||||||
['geojson', 'GeoJSON'],
|
['geojson_json', 'GeoJSON (JSON)'],
|
||||||
|
['geojson_url', 'GeoJSON (URL)'],
|
||||||
['tilejson_vector', 'Vector (TileJSON URL)'],
|
['tilejson_vector', 'Vector (TileJSON URL)'],
|
||||||
['tilexyz_vector', 'Vector (XYZ URLs)'],
|
['tilexyz_vector', 'Vector (XYZ URLs)'],
|
||||||
['tilejson_raster', 'Raster (TileJSON URL)'],
|
['tilejson_raster', 'Raster (TileJSON URL)'],
|
||||||
|
@ -168,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,8 +3,10 @@ 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'
|
||||||
|
|
||||||
|
|
||||||
class TileJSONSourceEditor extends React.Component {
|
class TileJSONSourceEditor extends React.Component {
|
||||||
|
@ -17,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,
|
||||||
|
@ -51,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)}
|
||||||
/>
|
/>
|
||||||
|
@ -86,15 +88,15 @@ class TileURLSourceEditor extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GeoJSONSourceEditor extends React.Component {
|
class GeoJSONSourceUrlEditor extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
source: PropTypes.object.isRequired,
|
source: PropTypes.object.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"GeoJSON Data"} 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,
|
||||||
|
@ -105,6 +107,28 @@ class GeoJSONSourceEditor extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GeoJSONSourceJSONEditor extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
source: PropTypes.object.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <InputBlock label={"GeoJSON"} doc={latest.source_geojson.data.doc}>
|
||||||
|
<JSONEditor
|
||||||
|
layer={this.props.source.data}
|
||||||
|
maxHeight={200}
|
||||||
|
onChange={data => {
|
||||||
|
this.props.onChange({
|
||||||
|
...this.props.source,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SourceTypeEditor extends React.Component {
|
class SourceTypeEditor extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
mode: PropTypes.string.isRequired,
|
mode: PropTypes.string.isRequired,
|
||||||
|
@ -118,7 +142,8 @@ class SourceTypeEditor extends React.Component {
|
||||||
onChange: this.props.onChange,
|
onChange: this.props.onChange,
|
||||||
}
|
}
|
||||||
switch(this.props.mode) {
|
switch(this.props.mode) {
|
||||||
case 'geojson': return <GeoJSONSourceEditor {...commonProps} />
|
case 'geojson_url': return <GeoJSONSourceUrlEditor {...commonProps} />
|
||||||
|
case 'geojson_json': return <GeoJSONSourceJSONEditor {...commonProps} />
|
||||||
case 'tilejson_vector': return <TileJSONSourceEditor {...commonProps} />
|
case 'tilejson_vector': return <TileJSONSourceEditor {...commonProps} />
|
||||||
case 'tilexyz_vector': return <TileURLSourceEditor {...commonProps} />
|
case 'tilexyz_vector': return <TileURLSourceEditor {...commonProps} />
|
||||||
case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} />
|
case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} />
|
||||||
|
|
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
|
||||||
|
}
|
|
@ -1,62 +1,62 @@
|
||||||
[
|
[
|
||||||
{
|
|
||||||
"id": "klokantech-basic",
|
|
||||||
"title": "Klokantech Basic",
|
|
||||||
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/klokantech-basic-gl-style@e142f83/style.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/klokantech-basic.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dark-matter",
|
|
||||||
"title": "Dark Matter",
|
|
||||||
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/dark-matter-gl-style@1dcc1d3/style.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/dark-matter.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "positron",
|
|
||||||
"title": "Positron",
|
|
||||||
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/positron-gl-style@2877814/style.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/positron.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "osm-bright",
|
|
||||||
"title": "OSM Bright",
|
|
||||||
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/osm-bright-gl-style@500e26e/style.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/osm-bright.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "toner-gl-style",
|
|
||||||
"title": "Toner",
|
|
||||||
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/toner-gl-style@bb49571/style.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/toner.png"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "osm-liberty",
|
"id": "osm-liberty",
|
||||||
"title": "OSM Liberty",
|
"title": "OSM Liberty",
|
||||||
"url": "https://maputnik.github.io/osm-liberty/style.json",
|
"url": "https://maputnik.github.io/osm-liberty/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/osm-liberty.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/osm-liberty.png"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "klokantech-basic",
|
||||||
|
"title": "Klokantech Basic",
|
||||||
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/klokantech-basic-gl-style@v1.9/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/klokantech-basic.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dark-matter",
|
||||||
|
"title": "Dark Matter",
|
||||||
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/dark-matter-gl-style@v1.8/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/dark-matter.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "positron",
|
||||||
|
"title": "Positron",
|
||||||
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/positron-gl-style@v1.8/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/positron.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "osm-bright",
|
||||||
|
"title": "OSM Bright",
|
||||||
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/osm-bright-gl-style@v1.9/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/osm-bright.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "toner-gl-style",
|
||||||
|
"title": "Toner",
|
||||||
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/toner-gl-style@dcb6e64/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/toner.png"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "os-zoomstack-outdoor",
|
"id": "os-zoomstack-outdoor",
|
||||||
"title": "Zoomstack Outdoor",
|
"title": "Zoomstack Outdoor",
|
||||||
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-outdoor/style.json",
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/styles/open-zoomstack-outdoor/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-outdoor.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-outdoor.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "os-zoomstack-road",
|
"id": "os-zoomstack-road",
|
||||||
"title": "Zoomstack Road",
|
"title": "Zoomstack Road",
|
||||||
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-road/style.json",
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/styles/open-zoomstack-road/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-road.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-road.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "os-zoomstack-light",
|
"id": "os-zoomstack-light",
|
||||||
"title": "Zoomstack Light",
|
"title": "Zoomstack Light",
|
||||||
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-light/style.json",
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/styles/open-zoomstack-light/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-light.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-light.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "os-zoomstack-night",
|
"id": "os-zoomstack-night",
|
||||||
"title": "Zoomstack Night",
|
"title": "Zoomstack Night",
|
||||||
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-night/style.json",
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/styles/open-zoomstack-night/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-night.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-night.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
},
|
},
|
||||||
"open_zoomstack": {
|
"open_zoomstack": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/data/vector/open-zoomstack/config.json",
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/data/vector/open-zoomstack/config.json",
|
||||||
"title": "OS Open Zoomstack"
|
"title": "OS Open Zoomstack"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
.CodeMirror-lint-tooltip {
|
||||||
|
z-index: 2000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.codemirror-container {
|
.codemirror-container {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,11 @@
|
||||||
border: none;
|
border: none;
|
||||||
background-color: $color-gray;
|
background-color: $color-gray;
|
||||||
color: lighten($color-lowgray, 12);
|
color: lighten($color-lowgray, 12);
|
||||||
|
|
||||||
|
&:invalid {
|
||||||
|
border: solid 1px #B71C1C;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-string {
|
.maputnik-string {
|
||||||
|
@ -22,6 +27,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-number-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-number-range {
|
||||||
|
width: calc(100% - 4.5em);
|
||||||
|
margin-right: 0.5em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.maputnik-number {
|
.maputnik-number {
|
||||||
@extend .maputnik-input;
|
@extend .maputnik-input;
|
||||||
}
|
}
|
||||||
|
@ -173,3 +188,8 @@
|
||||||
margin-bottom: $margin-3;
|
margin-bottom: $margin-3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-input-block-content {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
|
@ -180,10 +180,26 @@
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
padding: $margin-2;
|
padding: $margin-2;
|
||||||
|
|
||||||
|
.maputnik-input-block-label {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-input-block-content {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-add-source {
|
.maputnik-add-source {
|
||||||
@extend .clearfix;
|
@extend .clearfix;
|
||||||
|
|
||||||
|
.maputnik-input-block-label {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-input-block-content {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-add-source-button {
|
.maputnik-add-source-button {
|
||||||
|
@ -264,3 +280,7 @@
|
||||||
color: $color-green;
|
color: $color-green;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-settings {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
|
@ -7,3 +7,7 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ReactCollapse--collapse {
|
||||||
|
transition: height 180ms;
|
||||||
|
}
|
||||||
|
|
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;
|
||||||
|
|
|
@ -45,17 +45,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-delete-stop {
|
.maputnik-delete-stop {
|
||||||
|
display: inline-block;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
@extend .maputnik-icon-button;
|
@extend .maputnik-icon-button;
|
||||||
|
|
||||||
vertical-align: top;
|
|
||||||
|
|
||||||
.maputnik-doc-wrapper {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maputnik-doc-target {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-add-stop {
|
.maputnik-add-stop {
|
||||||
|
|
|
@ -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';
|
||||||
|
@ -37,8 +15,8 @@ $toolbar-offset: 0;
|
||||||
@import 'zoomproperty';
|
@import 'zoomproperty';
|
||||||
@import 'popup';
|
@import 'popup';
|
||||||
@import 'map';
|
@import 'map';
|
||||||
@import 'react-collapse';
|
|
||||||
@import 'codemirror';
|
@import 'codemirror';
|
||||||
|
@import 'react-collapse';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hacks for webdriverio isVisibleWithinViewport
|
* Hacks for webdriverio isVisibleWithinViewport
|
||||||
|
|
|
@ -200,7 +200,7 @@ describe("layers", function() {
|
||||||
|
|
||||||
const elem = $(wd.$("layer-list-item:background:"+bgId));
|
const elem = $(wd.$("layer-list-item:background:"+bgId));
|
||||||
elem.click();
|
elem.click();
|
||||||
browser.setValueSafe(wd.$("min-zoom", "input"), 1)
|
browser.setValueSafe(wd.$("min-zoom", 'input[type="text"]'), 1)
|
||||||
const elem2 = $(wd.$("layer-editor.layer-id", "input"));
|
const elem2 = $(wd.$("layer-editor.layer-id", "input"));
|
||||||
elem2.click();
|
elem2.click();
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ describe("layers", function() {
|
||||||
|
|
||||||
const elem = $(wd.$("layer-list-item:background:"+bgId));
|
const elem = $(wd.$("layer-list-item:background:"+bgId));
|
||||||
elem.click();
|
elem.click();
|
||||||
browser.setValueSafe(wd.$("max-zoom", "input"), 1)
|
browser.setValueSafe(wd.$("max-zoom", 'input[type="text"]'), 1)
|
||||||
const elem2 = $(wd.$("layer-editor.layer-id", "input"));
|
const elem2 = $(wd.$("layer-editor.layer-id", "input"));
|
||||||
elem2.click();
|
elem2.click();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue