mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-28 04:35:23 +01:00
Merge remote-tracking branch 'upstream/master' into fix/remove-componentWillUpdate
Conflicts: src/components/map/MapboxGlMap.jsx src/components/modals/ExportModal.jsx
This commit is contained in:
commit
26ff9f63bb
43 changed files with 277 additions and 502 deletions
4
.babelrc
4
.babelrc
|
@ -5,9 +5,9 @@
|
|||
"test": {
|
||||
"plugins": [
|
||||
["istanbul", {
|
||||
exclude: ["node_modules/**", "test/**"]
|
||||
"exclude": ["node_modules/**", "test/**"]
|
||||
}]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,25 +14,6 @@ var OUTPATH = artifacts.pathSync("/build");
|
|||
module.exports = {
|
||||
entry: {
|
||||
app: './src/index.jsx',
|
||||
vendor: [
|
||||
'file-saver',
|
||||
'mapbox-gl/dist/mapbox-gl.js',
|
||||
"lodash.clonedeep",
|
||||
"lodash.throttle",
|
||||
'color',
|
||||
'react',
|
||||
"react-dom",
|
||||
"react-color",
|
||||
"react-file-reader-input",
|
||||
"react-collapse",
|
||||
"react-height",
|
||||
"react-icon-base",
|
||||
"react-motion",
|
||||
"react-sortable-hoc",
|
||||
"request",
|
||||
//TODO: Icons raise multi vendor errors?
|
||||
//"react-icons",
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: OUTPATH,
|
||||
|
@ -55,7 +36,6 @@ module.exports = {
|
|||
},
|
||||
plugins: [
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: '[chunkhash].vendor.js' }),
|
||||
new WebpackCleanupPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "maputnik",
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
16
package.json
16
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "maputnik",
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.0",
|
||||
"description": "A MapboxGL visual style editor",
|
||||
"main": "''",
|
||||
"scripts": {
|
||||
|
@ -10,8 +10,7 @@
|
|||
"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",
|
||||
"lint": "eslint --ext js --ext jsx {src,test}",
|
||||
"lint-styles": "stylelint 'src/styles/*.scss'",
|
||||
"nsp": "nsp check --reporter summary"
|
||||
"lint-styles": "stylelint 'src/styles/*.scss'"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -27,7 +26,6 @@
|
|||
"codemirror": "^5.37.0",
|
||||
"color": "^3.0.0",
|
||||
"file-saver": "^1.3.8",
|
||||
"github-api": "^3.0.0",
|
||||
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
||||
"lodash.capitalize": "^4.2.1",
|
||||
"lodash.clamp": "^4.0.3",
|
||||
|
@ -37,28 +35,24 @@
|
|||
"mapbox-gl": "^0.47.0",
|
||||
"mapbox-gl-inspect": "^1.3.1",
|
||||
"maputnik-design": "github:maputnik/design",
|
||||
"mousetrap": "^1.6.1",
|
||||
"ol-mapbox-style": "^2.10.1",
|
||||
"ol": "^4.6.5",
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.3.2",
|
||||
"react-addons-pure-render-mixin": "^15.6.2",
|
||||
"react-aria-menubutton": "^5.1.1",
|
||||
"react-aria-modal": "^2.12.1",
|
||||
"react-autobind": "^1.0.6",
|
||||
"react-autocomplete": "^1.7.2",
|
||||
"react-codemirror2": "^4.2.1",
|
||||
"react-collapse": "^4.0.3",
|
||||
"react-color": "^2.14.1",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-dom": "^16.3.2",
|
||||
"react-file-reader-input": "^1.1.4",
|
||||
"react-height": "^3.0.0",
|
||||
"react-icon-base": "^2.1.1",
|
||||
"react-icons": "^2.2.7",
|
||||
"react-motion": "^0.5.2",
|
||||
"react-sortable-hoc": "^0.6.8",
|
||||
"reconnecting-websocket": "^3.2.2",
|
||||
"request": "^2.85.0",
|
||||
"url": "^0.11.0"
|
||||
},
|
||||
"jshintConfig": {
|
||||
|
@ -117,7 +111,6 @@
|
|||
"babel-preset-react": "^6.24.1",
|
||||
"babel-register": "^6.26.0",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"base64-loader": "^1.0.0",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"cors": "^2.8.4",
|
||||
"cross-env": "^5.1.4",
|
||||
|
@ -135,7 +128,7 @@
|
|||
"mkdirp": "^0.5.1",
|
||||
"mocha": "^5.1.1",
|
||||
"node-sass": "^4.9.0",
|
||||
"nsp": "^3.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react-hot-loader": "^3.1.1",
|
||||
"sass-loader": "^7.0.1",
|
||||
"selenium-standalone": "^6.14.0",
|
||||
|
@ -147,7 +140,6 @@
|
|||
"uglifyjs-webpack-plugin": "^1.2.4",
|
||||
"uuid": "^3.1.0",
|
||||
"wdio-mocha-framework": "^0.5.13",
|
||||
"wdio-phantomjs-service": "^0.2.2",
|
||||
"wdio-selenium-standalone-service": "0.0.10",
|
||||
"wdio-spec-reporter": "^0.1.2",
|
||||
"webdriverio": "^4.12.0",
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import autoBind from 'react-autobind';
|
||||
import React from 'react'
|
||||
import Mousetrap from 'mousetrap'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import clamp from 'lodash.clamp'
|
||||
import {arrayMove} from 'react-sortable-hoc'
|
||||
import url from 'url'
|
||||
|
||||
import MapboxGlMap from './map/MapboxGlMap'
|
||||
import OpenLayers3Map from './map/OpenLayers3Map'
|
||||
import LayerList from './layers/LayerList'
|
||||
import LayerEditor from './layers/LayerEditor'
|
||||
import Toolbar from './Toolbar'
|
||||
|
@ -25,7 +24,7 @@ import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
|||
import style from '../libs/style'
|
||||
import { initialStyleUrl, loadStyleUrl } from '../libs/urlopen'
|
||||
import { undoMessages, redoMessages } from '../libs/diffmessage'
|
||||
import { loadDefaultStyle, StyleStore } from '../libs/stylestore'
|
||||
import { StyleStore } from '../libs/stylestore'
|
||||
import { ApiStyleStore } from '../libs/apistore'
|
||||
import { RevisionStore } from '../libs/revisions'
|
||||
import LayerWatcher from '../libs/layerwatcher'
|
||||
|
@ -54,6 +53,8 @@ function updateRootSpec(spec, fieldName, newValues) {
|
|||
export default class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
autoBind(this);
|
||||
|
||||
this.revisionStore = new RevisionStore()
|
||||
this.styleStore = new ApiStyleStore({
|
||||
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false)
|
||||
|
@ -176,7 +177,8 @@ export default class App extends React.Component {
|
|||
survey: localStorage.hasOwnProperty('survey') ? false : true
|
||||
},
|
||||
mapOptions: {
|
||||
showTileBoundaries: queryUtil.asBool(queryObj, "show-tile-boundaries")
|
||||
showTileBoundaries: queryUtil.asBool(queryObj, "show-tile-boundaries"),
|
||||
showCollisionBoxes: queryUtil.asBool(queryObj, "show-collision-boxes")
|
||||
},
|
||||
mapFilter: queryObj["color-blindness-emulation"],
|
||||
}
|
||||
|
@ -186,14 +188,31 @@ export default class App extends React.Component {
|
|||
})
|
||||
}
|
||||
|
||||
handleKeyPress(e) {
|
||||
if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
|
||||
if(e.metaKey && e.shiftKey && e.keyCode === 90) {
|
||||
this.onRedo(e);
|
||||
}
|
||||
else if(e.metaKey && e.keyCode === 90) {
|
||||
this.onUndo(e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(e.ctrlKey && e.keyCode === 90) {
|
||||
this.onUndo(e);
|
||||
}
|
||||
else if(e.ctrlKey && e.keyCode === 89) {
|
||||
this.onRedo(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
Mousetrap.bind(['mod+z'], this.onUndo.bind(this));
|
||||
Mousetrap.bind(['mod+y', 'mod+shift+z'], this.onRedo.bind(this));
|
||||
window.addEventListener("keydown", this.handleKeyPress);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Mousetrap.unbind(['mod+z'], this.onUndo.bind(this));
|
||||
Mousetrap.unbind(['mod+y', 'mod+shift+z'], this.onRedo.bind(this));
|
||||
window.removeEventListener("keydown", this.handleKeyPress);
|
||||
}
|
||||
|
||||
saveStyle(snapshotStyle) {
|
||||
|
@ -216,7 +235,7 @@ export default class App extends React.Component {
|
|||
})
|
||||
}
|
||||
|
||||
onStyleChanged(newStyle, save=true) {
|
||||
onStyleChanged = (newStyle, save=true) => {
|
||||
|
||||
const errors = styleSpec.validate(newStyle, styleSpec.latest)
|
||||
if(errors.length === 0) {
|
||||
|
@ -243,7 +262,7 @@ export default class App extends React.Component {
|
|||
this.fetchSources();
|
||||
}
|
||||
|
||||
onUndo() {
|
||||
onUndo = () => {
|
||||
const activeStyle = this.revisionStore.undo()
|
||||
const messages = undoMessages(this.state.mapStyle, activeStyle)
|
||||
this.saveStyle(activeStyle)
|
||||
|
@ -253,7 +272,7 @@ export default class App extends React.Component {
|
|||
})
|
||||
}
|
||||
|
||||
onRedo() {
|
||||
onRedo = () => {
|
||||
const activeStyle = this.revisionStore.redo()
|
||||
const messages = redoMessages(this.state.mapStyle, activeStyle)
|
||||
this.saveStyle(activeStyle)
|
||||
|
@ -263,7 +282,7 @@ export default class App extends React.Component {
|
|||
})
|
||||
}
|
||||
|
||||
onMoveLayer(move) {
|
||||
onMoveLayer = (move) => {
|
||||
let { oldIndex, newIndex } = move;
|
||||
let layers = this.state.mapStyle.layers;
|
||||
oldIndex = clamp(oldIndex, 0, layers.length-1);
|
||||
|
@ -281,7 +300,7 @@ export default class App extends React.Component {
|
|||
this.onLayersChange(layers);
|
||||
}
|
||||
|
||||
onLayersChange(changedLayers) {
|
||||
onLayersChange = (changedLayers) => {
|
||||
const changedStyle = {
|
||||
...this.state.mapStyle,
|
||||
layers: changedLayers
|
||||
|
@ -289,7 +308,7 @@ export default class App extends React.Component {
|
|||
this.onStyleChanged(changedStyle)
|
||||
}
|
||||
|
||||
onLayerDestroy(layerId) {
|
||||
onLayerDestroy = (layerId) => {
|
||||
let layers = this.state.mapStyle.layers;
|
||||
const remainingLayers = layers.slice(0);
|
||||
const idx = style.indexOfLayer(remainingLayers, layerId)
|
||||
|
@ -297,7 +316,7 @@ export default class App extends React.Component {
|
|||
this.onLayersChange(remainingLayers);
|
||||
}
|
||||
|
||||
onLayerCopy(layerId) {
|
||||
onLayerCopy = (layerId) => {
|
||||
let layers = this.state.mapStyle.layers;
|
||||
const changedLayers = layers.slice(0)
|
||||
const idx = style.indexOfLayer(changedLayers, layerId)
|
||||
|
@ -308,7 +327,7 @@ export default class App extends React.Component {
|
|||
this.onLayersChange(changedLayers)
|
||||
}
|
||||
|
||||
onLayerVisibilityToggle(layerId) {
|
||||
onLayerVisibilityToggle = (layerId) => {
|
||||
let layers = this.state.mapStyle.layers;
|
||||
const changedLayers = layers.slice(0)
|
||||
const idx = style.indexOfLayer(changedLayers, layerId)
|
||||
|
@ -323,7 +342,7 @@ export default class App extends React.Component {
|
|||
}
|
||||
|
||||
|
||||
onLayerIdChange(oldId, newId) {
|
||||
onLayerIdChange = (oldId, newId) => {
|
||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
||||
const idx = style.indexOfLayer(changedLayers, oldId)
|
||||
|
||||
|
@ -335,7 +354,7 @@ export default class App extends React.Component {
|
|||
this.onLayersChange(changedLayers)
|
||||
}
|
||||
|
||||
onLayerChanged(layer) {
|
||||
onLayerChanged = (layer) => {
|
||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
||||
const idx = style.indexOfLayer(changedLayers, layer.id)
|
||||
changedLayers[idx] = layer
|
||||
|
@ -343,7 +362,7 @@ export default class App extends React.Component {
|
|||
this.onLayersChange(changedLayers)
|
||||
}
|
||||
|
||||
changeInspectMode() {
|
||||
changeInspectMode = () => {
|
||||
this.setState({
|
||||
inspectModeEnabled: !this.state.inspectModeEnabled
|
||||
})
|
||||
|
@ -370,7 +389,9 @@ export default class App extends React.Component {
|
|||
console.warn("Failed to normalizeSourceURL: ", err);
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
fetch(url, {
|
||||
mode: 'cors',
|
||||
})
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
|
@ -422,12 +443,12 @@ export default class App extends React.Component {
|
|||
|
||||
// Check if OL3 code has been loaded?
|
||||
if(renderer === 'ol3') {
|
||||
mapElement = <OpenLayers3Map {...mapProps} />
|
||||
mapElement = <div>TODO</div>
|
||||
} else {
|
||||
mapElement = <MapboxGlMap {...mapProps}
|
||||
inspectModeEnabled={this.state.inspectModeEnabled}
|
||||
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
|
||||
onLayerSelect={this.onLayerSelect.bind(this)} />
|
||||
onLayerSelect={this.onLayerSelect} />
|
||||
}
|
||||
|
||||
const elementStyle = {};
|
||||
|
@ -440,7 +461,7 @@ export default class App extends React.Component {
|
|||
</div>
|
||||
}
|
||||
|
||||
onLayerSelect(layerId) {
|
||||
onLayerSelect = (layerId) => {
|
||||
const idx = style.indexOfLayer(this.state.mapStyle.layers, layerId)
|
||||
this.setState({ selectedLayerIndex: idx })
|
||||
}
|
||||
|
@ -467,19 +488,19 @@ export default class App extends React.Component {
|
|||
mapStyle={this.state.mapStyle}
|
||||
inspectModeEnabled={this.state.inspectModeEnabled}
|
||||
sources={this.state.sources}
|
||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
||||
onStyleOpen={this.onStyleChanged.bind(this)}
|
||||
onInspectModeToggle={this.changeInspectMode.bind(this)}
|
||||
onStyleChanged={this.onStyleChanged}
|
||||
onStyleOpen={this.onStyleChanged}
|
||||
onInspectModeToggle={this.changeInspectMode}
|
||||
onToggleModal={this.toggleModal.bind(this)}
|
||||
/>
|
||||
|
||||
const layerList = <LayerList
|
||||
onMoveLayer={this.onMoveLayer.bind(this)}
|
||||
onLayerDestroy={this.onLayerDestroy.bind(this)}
|
||||
onLayerCopy={this.onLayerCopy.bind(this)}
|
||||
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
|
||||
onLayersChange={this.onLayersChange.bind(this)}
|
||||
onLayerSelect={this.onLayerSelect.bind(this)}
|
||||
onMoveLayer={this.onMoveLayer}
|
||||
onLayerDestroy={this.onLayerDestroy}
|
||||
onLayerCopy={this.onLayerCopy}
|
||||
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
|
||||
onLayersChange={this.onLayersChange}
|
||||
onLayerSelect={this.onLayerSelect}
|
||||
selectedLayerIndex={this.state.selectedLayerIndex}
|
||||
layers={layers}
|
||||
sources={this.state.sources}
|
||||
|
@ -493,12 +514,12 @@ export default class App extends React.Component {
|
|||
sources={this.state.sources}
|
||||
vectorLayers={this.state.vectorLayers}
|
||||
spec={this.state.spec}
|
||||
onMoveLayer={this.onMoveLayer.bind(this)}
|
||||
onLayerChanged={this.onLayerChanged.bind(this)}
|
||||
onLayerDestroy={this.onLayerDestroy.bind(this)}
|
||||
onLayerCopy={this.onLayerCopy.bind(this)}
|
||||
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
|
||||
onLayerIdChange={this.onLayerIdChange.bind(this)}
|
||||
onMoveLayer={this.onMoveLayer}
|
||||
onLayerChanged={this.onLayerChanged}
|
||||
onLayerDestroy={this.onLayerDestroy}
|
||||
onLayerCopy={this.onLayerCopy}
|
||||
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
|
||||
onLayerIdChange={this.onLayerIdChange}
|
||||
/> : null
|
||||
|
||||
const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? <MessagePanel
|
||||
|
@ -514,24 +535,24 @@ export default class App extends React.Component {
|
|||
/>
|
||||
<SettingsModal
|
||||
mapStyle={this.state.mapStyle}
|
||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
||||
onStyleChanged={this.onStyleChanged}
|
||||
isOpen={this.state.isOpen.settings}
|
||||
onOpenToggle={this.toggleModal.bind(this, 'settings')}
|
||||
/>
|
||||
<ExportModal
|
||||
mapStyle={this.state.mapStyle}
|
||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
||||
onStyleChanged={this.onStyleChanged}
|
||||
isOpen={this.state.isOpen.export}
|
||||
onOpenToggle={this.toggleModal.bind(this, 'export')}
|
||||
/>
|
||||
<OpenModal
|
||||
isOpen={this.state.isOpen.open}
|
||||
onStyleOpen={this.onStyleChanged.bind(this)}
|
||||
onStyleOpen={this.onStyleChanged}
|
||||
onOpenToggle={this.toggleModal.bind(this, 'open')}
|
||||
/>
|
||||
<SourcesModal
|
||||
mapStyle={this.state.mapStyle}
|
||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
||||
onStyleChanged={this.onStyleChanged}
|
||||
isOpen={this.state.isOpen.sources}
|
||||
onOpenToggle={this.toggleModal.bind(this, 'sources')}
|
||||
/>
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import FileReaderInput from 'react-file-reader-input'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import MdFileDownload from 'react-icons/lib/md/file-download'
|
||||
import MdFileUpload from 'react-icons/lib/md/file-upload'
|
||||
import OpenIcon from 'react-icons/lib/md/open-in-browser'
|
||||
import SettingsIcon from 'react-icons/lib/md/settings'
|
||||
import MdInfo from 'react-icons/lib/md/info'
|
||||
import SourcesIcon from 'react-icons/lib/md/layers'
|
||||
import MdSave from 'react-icons/lib/md/save'
|
||||
import MdStyle from 'react-icons/lib/md/style'
|
||||
import MdMap from 'react-icons/lib/md/map'
|
||||
import MdInsertEmoticon from 'react-icons/lib/md/insert-emoticon'
|
||||
import MdFontDownload from 'react-icons/lib/md/font-download'
|
||||
import HelpIcon from 'react-icons/lib/md/help-outline'
|
||||
import InspectionIcon from 'react-icons/lib/md/find-in-page'
|
||||
import SurveyIcon from 'react-icons/lib/md/assignment-turned-in'
|
||||
|
@ -21,7 +13,6 @@ import SurveyIcon from 'react-icons/lib/md/assignment-turned-in'
|
|||
import logoImage from 'maputnik-design/logos/logo-color.svg'
|
||||
import pkgJson from '../../package.json'
|
||||
|
||||
import style from '../libs/style'
|
||||
|
||||
class IconText extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -107,16 +98,13 @@ export default class Toolbar extends React.Component {
|
|||
onToggleModal: PropTypes.func,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isOpen: {
|
||||
settings: false,
|
||||
sources: false,
|
||||
open: false,
|
||||
add: false,
|
||||
export: false,
|
||||
}
|
||||
state = {
|
||||
isOpen: {
|
||||
settings: false,
|
||||
sources: false,
|
||||
open: false,
|
||||
add: false,
|
||||
export: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,17 +19,14 @@ class ColorField extends React.Component {
|
|||
default: PropTypes.string,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
pickerOpened: false,
|
||||
}
|
||||
state = {
|
||||
pickerOpened: false
|
||||
}
|
||||
|
||||
//TODO: I much rather would do this with absolute positioning
|
||||
//but I am too stupid to get it to work together with fixed position
|
||||
//and scrollbars so I have to fallback to JavaScript
|
||||
calcPickerOffset() {
|
||||
calcPickerOffset = () => {
|
||||
const elem = this.colorInput
|
||||
if(elem) {
|
||||
const pos = elem.getBoundingClientRect()
|
||||
|
@ -45,7 +42,7 @@ class ColorField extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
togglePicker() {
|
||||
togglePicker = () => {
|
||||
this.setState({ pickerOpened: !this.state.pickerOpened })
|
||||
}
|
||||
|
||||
|
@ -85,7 +82,7 @@ class ColorField extends React.Component {
|
|||
/>
|
||||
<div
|
||||
className="maputnik-color-picker-offset"
|
||||
onClick={this.togglePicker.bind(this)}
|
||||
onClick={this.togglePicker}
|
||||
style={{
|
||||
zIndex: -1,
|
||||
position: 'fixed',
|
||||
|
@ -108,7 +105,7 @@ class ColorField extends React.Component {
|
|||
spellCheck="false"
|
||||
className="maputnik-color"
|
||||
ref={(input) => this.colorInput = input}
|
||||
onClick={this.togglePicker.bind(this)}
|
||||
onClick={this.togglePicker}
|
||||
style={this.props.style}
|
||||
name={this.props.name}
|
||||
placeholder={this.props.default}
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class DocLabel extends React.Component {
|
|||
<div className="maputnik-doc-popup">
|
||||
{this.props.doc}
|
||||
</div>
|
||||
</div >
|
||||
</div>
|
||||
</label>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
]),
|
||||
}
|
||||
|
||||
addStop() {
|
||||
addStop = () => {
|
||||
const stops = this.props.value.stops.slice(0)
|
||||
const lastStop = stops[stops.length - 1]
|
||||
if (typeof lastStop[0] === "object") {
|
||||
|
@ -53,7 +53,7 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
this.props.onChange(this.props.fieldName, changedValue)
|
||||
}
|
||||
|
||||
deleteStop(stopIdx) {
|
||||
deleteStop = (stopIdx) => {
|
||||
const stops = this.props.value.stops.slice(0)
|
||||
stops.splice(stopIdx, 1)
|
||||
|
||||
|
@ -69,7 +69,7 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
this.props.onChange(this.props.fieldName, changedValue)
|
||||
}
|
||||
|
||||
makeZoomFunction() {
|
||||
makeZoomFunction = () => {
|
||||
const zoomFunc = {
|
||||
stops: [
|
||||
[6, this.props.value],
|
||||
|
@ -79,7 +79,7 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
this.props.onChange(this.props.fieldName, zoomFunc)
|
||||
}
|
||||
|
||||
makeDataFunction() {
|
||||
makeDataFunction = () => {
|
||||
const dataFunc = {
|
||||
property: "",
|
||||
type: "categorical",
|
||||
|
@ -102,8 +102,8 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
fieldName={this.props.fieldName}
|
||||
fieldSpec={this.props.fieldSpec}
|
||||
value={this.props.value}
|
||||
onDeleteStop={this.deleteStop.bind(this)}
|
||||
onAddStop={this.addStop.bind(this)}
|
||||
onDeleteStop={this.deleteStop}
|
||||
onAddStop={this.addStop}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -114,8 +114,8 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
fieldName={this.props.fieldName}
|
||||
fieldSpec={this.props.fieldSpec}
|
||||
value={this.props.value}
|
||||
onDeleteStop={this.deleteStop.bind(this)}
|
||||
onAddStop={this.addStop.bind(this)}
|
||||
onDeleteStop={this.deleteStop}
|
||||
onAddStop={this.addStop}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -126,8 +126,8 @@ export default class FunctionSpecProperty extends React.Component {
|
|||
fieldName={this.props.fieldName}
|
||||
fieldSpec={this.props.fieldSpec}
|
||||
value={this.props.value}
|
||||
onZoomClick={this.makeZoomFunction.bind(this)}
|
||||
onDataClick={this.makeDataFunction.bind(this)}
|
||||
onZoomClick={this.makeZoomFunction}
|
||||
onDataClick={this.makeDataFunction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ export default class PropertyGroup extends React.Component {
|
|||
spec: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
onPropertyChange(property, newValue) {
|
||||
onPropertyChange = (property, newValue) => {
|
||||
const group = getGroupName(this.props.spec, this.props.layer.type, property)
|
||||
this.props.onChange(group , property, newValue)
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ export default class PropertyGroup extends React.Component {
|
|||
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName]
|
||||
|
||||
return <FunctionSpecField
|
||||
onChange={this.onPropertyChange.bind(this)}
|
||||
onChange={this.onPropertyChange}
|
||||
key={fieldName}
|
||||
fieldName={fieldName}
|
||||
value={fieldValue === undefined ? fieldSpec.default : fieldValue}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import color from 'color'
|
||||
|
||||
import ColorField from './ColorField'
|
||||
import NumberInput from '../inputs/NumberInput'
|
||||
|
|
|
@ -53,12 +53,8 @@ export default class ZoomProperty extends React.Component {
|
|||
]),
|
||||
}
|
||||
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.state = {
|
||||
refs: {}
|
||||
}
|
||||
state = {
|
||||
refs: {}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
|
@ -9,9 +9,6 @@ import SingleFilterEditor from './SingleFilterEditor'
|
|||
import FilterEditorBlock from './FilterEditorBlock'
|
||||
import Button from '../Button'
|
||||
|
||||
import DeleteIcon from 'react-icons/lib/md/delete'
|
||||
import AddIcon from 'react-icons/lib/fa/plus'
|
||||
|
||||
function hasCombiningFilter(filter) {
|
||||
return combiningFilterOps.indexOf(filter[0]) >= 0
|
||||
}
|
||||
|
@ -60,7 +57,7 @@ export default class CombiningFilterEditor extends React.Component {
|
|||
this.props.onChange(newFilter)
|
||||
}
|
||||
|
||||
addFilterItem() {
|
||||
addFilterItem = () => {
|
||||
const newFilterItem = this.combiningFilter().slice(0)
|
||||
newFilterItem.push(['==', 'name', ''])
|
||||
this.props.onChange(newFilterItem)
|
||||
|
@ -105,7 +102,7 @@ export default class CombiningFilterEditor extends React.Component {
|
|||
<Button
|
||||
data-wd-key="layer-filter-button"
|
||||
className="maputnik-add-filter"
|
||||
onClick={this.addFilterItem.bind(this)}>
|
||||
onClick={this.addFilterItem}>
|
||||
Add filter
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -14,18 +14,15 @@ class AutocompleteInput extends React.Component {
|
|||
keepMenuWithinWindowBounds: PropTypes.bool
|
||||
}
|
||||
|
||||
state = {
|
||||
maxHeight: MAX_HEIGHT
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onChange: () => {},
|
||||
options: [],
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
maxHeight: MAX_HEIGHT
|
||||
};
|
||||
}
|
||||
|
||||
calcMaxHeight() {
|
||||
if(this.props.keepMenuWithinWindowBounds) {
|
||||
const maxHeight = window.innerHeight - this.autocompleteMenuEl.getBoundingClientRect().top;
|
||||
|
@ -38,6 +35,7 @@ class AutocompleteInput extends React.Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.calcMaxHeight();
|
||||
}
|
||||
|
|
|
@ -27,14 +27,13 @@ class DynamicArrayInput extends React.Component {
|
|||
return this.props.value || this.props.default || []
|
||||
}
|
||||
|
||||
addValue() {
|
||||
addValue = () => {
|
||||
const values = this.values.slice(0)
|
||||
if (this.props.type === 'number') {
|
||||
values.push(0)
|
||||
} else {
|
||||
values.push("")
|
||||
}
|
||||
|
||||
|
||||
this.props.onChange(values)
|
||||
}
|
||||
|
@ -77,7 +76,7 @@ class DynamicArrayInput extends React.Component {
|
|||
{inputs}
|
||||
<Button
|
||||
className="maputnik-array-add-value"
|
||||
onClick={this.addValue.bind(this)}
|
||||
onClick={this.addValue}
|
||||
>
|
||||
Add value
|
||||
</Button>
|
||||
|
|
|
@ -51,7 +51,7 @@ class NumberInput extends React.Component {
|
|||
return true
|
||||
}
|
||||
|
||||
resetValue() {
|
||||
resetValue = () => {
|
||||
// Reset explicitly to default value if value has been cleared
|
||||
if(this.state.value === "") {
|
||||
return this.changeValue(this.props.default)
|
||||
|
@ -74,7 +74,7 @@ class NumberInput extends React.Component {
|
|||
placeholder={this.props.default}
|
||||
value={this.state.value}
|
||||
onChange={e => this.changeValue(e.target.value)}
|
||||
onBlur={this.resetValue.bind(this)}
|
||||
onBlur={this.resetValue}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,6 @@ import LayerSourceLayerBlock from './LayerSourceLayerBlock'
|
|||
|
||||
import MoreVertIcon from 'react-icons/lib/md/more-vert'
|
||||
|
||||
import InputBlock from '../inputs/InputBlock'
|
||||
import MultiButtonInput from '../inputs/MultiButtonInput'
|
||||
|
||||
import { changeType, changeProperty } from '../../libs/layer'
|
||||
import layout from '../../config/layout.json'
|
||||
|
||||
|
@ -36,7 +33,7 @@ function layoutGroups(layerType) {
|
|||
title: 'JSON Editor',
|
||||
type: 'jsoneditor'
|
||||
}
|
||||
return [layerGroup, filterGroup].concat(layout[layerType].groups).concat([editorGroup])
|
||||
return [layerGroup, filterGroup].concat(layout[layerType].groups).concat([editorGroup])
|
||||
}
|
||||
|
||||
/** Layer editor supporting multiple types of layers. */
|
||||
|
|
|
@ -2,13 +2,10 @@ import React from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import Button from '../Button'
|
||||
import LayerListGroup from './LayerListGroup'
|
||||
import LayerListItem from './LayerListItem'
|
||||
import AddIcon from 'react-icons/lib/md/add-circle-outline'
|
||||
import AddModal from '../modals/AddModal'
|
||||
|
||||
import style from '../../libs/style.js'
|
||||
import {SortableContainer, SortableHandle} from 'react-sortable-hoc';
|
||||
|
||||
const layerListPropTypes = {
|
||||
|
@ -45,15 +42,12 @@ class LayerListContainer extends React.Component {
|
|||
onLayerSelect: () => {},
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
collapsedGroups: {},
|
||||
areAllGroupsExpanded: false,
|
||||
isOpen: {
|
||||
add: false,
|
||||
}
|
||||
}
|
||||
state = {
|
||||
collapsedGroups: {},
|
||||
areAllGroupsExpanded: false,
|
||||
isOpen: {
|
||||
add: false,
|
||||
}
|
||||
}
|
||||
|
||||
toggleModal(modalName) {
|
||||
|
@ -65,7 +59,7 @@ class LayerListContainer extends React.Component {
|
|||
})
|
||||
}
|
||||
|
||||
toggleLayers() {
|
||||
toggleLayers = () => {
|
||||
let idx=0
|
||||
|
||||
let newGroups=[]
|
||||
|
@ -179,7 +173,7 @@ class LayerListContainer extends React.Component {
|
|||
<div className="maputnik-multibutton">
|
||||
<button
|
||||
id="skip-menu"
|
||||
onClick={this.toggleLayers.bind(this)}
|
||||
onClick={this.toggleLayers}
|
||||
className="maputnik-button">
|
||||
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
|
||||
</button>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Color from 'color'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import CopyIcon from 'react-icons/lib/md/content-copy'
|
||||
|
@ -9,7 +8,6 @@ import VisibilityOffIcon from 'react-icons/lib/md/visibility-off'
|
|||
import DeleteIcon from 'react-icons/lib/md/delete'
|
||||
|
||||
import LayerIcon from '../icons/LayerIcon'
|
||||
import LayerEditor from './LayerEditor'
|
||||
import {SortableElement, SortableHandle} from 'react-sortable-hoc'
|
||||
|
||||
@SortableHandle
|
||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types'
|
|||
|
||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
||||
import InputBlock from '../inputs/InputBlock'
|
||||
import StringInput from '../inputs/StringInput'
|
||||
import AutocompleteInput from '../inputs/AutocompleteInput'
|
||||
|
||||
class LayerSourceBlock extends React.Component {
|
||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types'
|
|||
|
||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
||||
import InputBlock from '../inputs/InputBlock'
|
||||
import StringInput from '../inputs/StringInput'
|
||||
import AutocompleteInput from '../inputs/AutocompleteInput'
|
||||
|
||||
class LayerSourceLayer extends React.Component {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import InputBlock from '../inputs/InputBlock'
|
||||
import StringInput from '../inputs/StringInput'
|
||||
import LayerIcon from '../icons/LayerIcon'
|
||||
|
||||
function groupFeaturesBySourceLayer(features) {
|
||||
|
|
|
@ -5,7 +5,6 @@ import MapboxGl from 'mapbox-gl'
|
|||
import MapboxInspect from 'mapbox-gl-inspect'
|
||||
import FeatureLayerPopup from './FeatureLayerPopup'
|
||||
import FeaturePropertyPopup from './FeaturePropertyPopup'
|
||||
import style from '../../libs/style.js'
|
||||
import tokens from '../../config/tokens.json'
|
||||
import colors from 'mapbox-gl-inspect/lib/colors'
|
||||
import Color from 'color'
|
||||
|
@ -15,6 +14,9 @@ import 'mapbox-gl/dist/mapbox-gl.css'
|
|||
import '../../mapboxgl.css'
|
||||
import '../../libs/mapbox-rtl'
|
||||
|
||||
|
||||
const IS_SUPPORTED = MapboxGl.supported();
|
||||
|
||||
function renderPropertyPopup(features) {
|
||||
var mountNode = document.createElement('div');
|
||||
ReactDOM.render(<FeaturePropertyPopup features={features} />, mountNode)
|
||||
|
@ -82,6 +84,8 @@ export default class MapboxGlMap extends React.Component {
|
|||
}
|
||||
|
||||
updateMapFromProps(props) {
|
||||
if(!IS_SUPPORTED) return;
|
||||
|
||||
if(!this.state.map) return
|
||||
const metadata = props.mapStyle.metadata || {}
|
||||
MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox
|
||||
|
@ -94,6 +98,8 @@ export default class MapboxGlMap extends React.Component {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if(!IS_SUPPORTED) return;
|
||||
|
||||
const map = this.state.map;
|
||||
|
||||
this.updateMapFromProps(this.props);
|
||||
|
@ -106,9 +112,12 @@ export default class MapboxGlMap extends React.Component {
|
|||
}
|
||||
|
||||
map.showTileBoundaries = this.props.options.showTileBoundaries;
|
||||
map.showCollisionBoxes = this.props.options.showCollisionBoxes;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if(!IS_SUPPORTED) return;
|
||||
|
||||
const mapOpts = {
|
||||
...this.props.options,
|
||||
container: this.container,
|
||||
|
@ -119,6 +128,7 @@ export default class MapboxGlMap extends React.Component {
|
|||
const map = new MapboxGl.Map(mapOpts);
|
||||
|
||||
map.showTileBoundaries = mapOpts.showTileBoundaries;
|
||||
map.showCollisionBoxes = mapOpts.showCollisionBoxes;
|
||||
|
||||
const zoom = new ZoomControl;
|
||||
map.addControl(zoom, 'top-right');
|
||||
|
@ -164,9 +174,20 @@ export default class MapboxGlMap extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
return <div
|
||||
className="maputnik-map"
|
||||
ref={x => this.container = x}
|
||||
></div>
|
||||
if(IS_SUPPORTED) {
|
||||
return <div
|
||||
className="maputnik-map"
|
||||
ref={x => this.container = x}
|
||||
></div>
|
||||
}
|
||||
else {
|
||||
return <div
|
||||
className="maputnik-map maputnik-map--error"
|
||||
>
|
||||
<div className="maputnik-map__error-message">
|
||||
Error: Cannot load MapboxGL, WebGL is either unsupported or disabled
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import style from '../../libs/style.js'
|
||||
import isEqual from 'lodash.isequal'
|
||||
import { loadJSON } from '../../libs/urlopen'
|
||||
import 'ol/ol.css'
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@ import React from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
|
||||
import Button from '../Button'
|
||||
import InputBlock from '../inputs/InputBlock'
|
||||
import StringInput from '../inputs/StringInput'
|
||||
import Modal from './Modal'
|
||||
|
||||
import LayerTypeBlock from '../layers/LayerTypeBlock'
|
||||
|
@ -22,7 +20,7 @@ class AddModal extends React.Component {
|
|||
sources: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
addLayer() {
|
||||
addLayer = () => {
|
||||
const changedLayers = this.props.layers.slice(0)
|
||||
const layer = {
|
||||
id: this.state.id,
|
||||
|
@ -151,7 +149,7 @@ class AddModal extends React.Component {
|
|||
}
|
||||
<Button
|
||||
className="maputnik-add-layer-button"
|
||||
onClick={this.addLayer.bind(this)}
|
||||
onClick={this.addLayer}
|
||||
data-wd-key="add-layer"
|
||||
>
|
||||
Add Layer
|
||||
|
|
|
@ -11,205 +11,8 @@ import Modal from './Modal'
|
|||
import MdFileDownload from 'react-icons/lib/md/file-download'
|
||||
import TiClipboard from 'react-icons/lib/ti/clipboard'
|
||||
import style from '../../libs/style'
|
||||
import GitHub from 'github-api'
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
||||
|
||||
|
||||
class Gist extends React.Component {
|
||||
static propTypes = {
|
||||
mapStyle: PropTypes.object.isRequired,
|
||||
onStyleChanged: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
preview: false,
|
||||
public: false,
|
||||
saving: false,
|
||||
latestGist: null,
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
return {
|
||||
preview: !!(props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']
|
||||
};
|
||||
}
|
||||
|
||||
onSave() {
|
||||
this.setState({
|
||||
...this.state,
|
||||
saving: true
|
||||
});
|
||||
|
||||
const preview = this.state.preview;
|
||||
|
||||
const mapboxToken = (this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token'];
|
||||
|
||||
const mapStyleStr = preview ?
|
||||
styleSpec.format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle))) :
|
||||
styleSpec.format(stripAccessTokens(this.props.mapStyle));
|
||||
const styleTitle = this.props.mapStyle.name || 'Style';
|
||||
const htmlStr = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>`+styleTitle+` Preview</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://api.mapbox.com/mapbox-gl-js/v0.44.0/mapbox-gl.css" />
|
||||
<script src="https://api.mapbox.com/mapbox-gl-js/v0.44.0/mapbox-gl.js"></script>
|
||||
<style>
|
||||
body { margin:0; padding:0; }
|
||||
#map { position:absolute; top:0; bottom:0; width:100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='map'></div>
|
||||
<script>
|
||||
mapboxgl.accessToken = '${mapboxToken}';
|
||||
var map = new mapboxgl.Map({
|
||||
container: 'map',
|
||||
style: 'style.json',
|
||||
attributionControl: true,
|
||||
hash: true
|
||||
});
|
||||
map.addControl(new mapboxgl.NavigationControl());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
const files = {
|
||||
"style.json": {
|
||||
content: mapStyleStr
|
||||
}
|
||||
}
|
||||
if(preview) {
|
||||
files["index.html"] = {
|
||||
content: htmlStr
|
||||
}
|
||||
}
|
||||
const gh = new GitHub();
|
||||
let gist = gh.getGist(); // not a gist yet
|
||||
gist.create({
|
||||
public: this.state.public,
|
||||
description: styleTitle,
|
||||
files: files
|
||||
}).then(function({data}) {
|
||||
return gist.read();
|
||||
}).then(function({data}) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
latestGist: data,
|
||||
saving: false,
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
onPreviewChange(value) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
preview: value
|
||||
})
|
||||
}
|
||||
|
||||
onPublicChange(value) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
public: value
|
||||
})
|
||||
}
|
||||
|
||||
changeMetadataProperty(property, value) {
|
||||
const changedStyle = {
|
||||
...this.props.mapStyle,
|
||||
metadata: {
|
||||
...this.props.mapStyle.metadata,
|
||||
[property]: value
|
||||
}
|
||||
}
|
||||
this.props.onStyleChanged(changedStyle)
|
||||
}
|
||||
|
||||
renderPreviewLink() {
|
||||
const gist = this.state.latestGist;
|
||||
const user = gist.user || 'anonymous';
|
||||
const preview = !!gist.files['index.html'];
|
||||
if(preview) {
|
||||
return <span><a target="_blank" rel="noopener noreferrer" href={"https://bl.ocks.org/"+user+"/"+gist.id}>Preview</a>,{' '}</span>
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderLatestGist() {
|
||||
const gist = this.state.latestGist;
|
||||
const saving = this.state.saving;
|
||||
if(saving) {
|
||||
return <p>Saving...</p>
|
||||
} else if(gist) {
|
||||
const user = gist.user || 'anonymous';
|
||||
const rawGistLink = "https://gist.githubusercontent.com/" + user + "/" + gist.id + "/raw/" + gist.history[0].version + "/style.json"
|
||||
const maputnikStyleLink = "https://maputnik.github.io/editor/?style=" + rawGistLink
|
||||
return <div className="maputnik-render-gist">
|
||||
<p>
|
||||
Latest saved gist:{' '}
|
||||
{this.renderPreviewLink(this)}
|
||||
<a target="_blank" rel="noopener noreferrer" href={"https://gist.github.com/" + user + "/" + gist.id}>Source</a>
|
||||
</p>
|
||||
<p>
|
||||
<CopyToClipboard text={maputnikStyleLink}>
|
||||
<span>Share this style: <Button><TiClipboard size={18} /></Button></span>
|
||||
</CopyToClipboard>
|
||||
<StringInput value={maputnikStyleLink} />
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className="maputnik-export-gist">
|
||||
<Button onClick={this.onSave.bind(this)}>
|
||||
<MdFileDownload />
|
||||
Save to Gist (anonymous)
|
||||
</Button>
|
||||
<div className="maputnik-modal-sub-section">
|
||||
<CheckboxInput
|
||||
value={this.state.public}
|
||||
name='gist-style-public'
|
||||
onChange={this.onPublicChange.bind(this)}
|
||||
/>
|
||||
<span> Public gist</span>
|
||||
</div>
|
||||
<div className="maputnik-modal-sub-section">
|
||||
<CheckboxInput
|
||||
value={this.state.preview}
|
||||
name='gist-style-preview'
|
||||
onChange={this.onPreviewChange.bind(this)}
|
||||
/>
|
||||
<span> Include preview</span>
|
||||
</div>
|
||||
{this.state.preview ?
|
||||
<div>
|
||||
<InputBlock
|
||||
label={"OpenMapTiles Access Token: "}>
|
||||
<StringInput
|
||||
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
|
||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}/>
|
||||
</InputBlock>
|
||||
<InputBlock
|
||||
label={"Mapbox Access Token: "}>
|
||||
<StringInput
|
||||
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
|
||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}/>
|
||||
</InputBlock>
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://openmaptiles.com/hosting/">Get your free access token</a>
|
||||
</div>
|
||||
: null}
|
||||
{this.renderLatestGist()}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
function stripAccessTokens(mapStyle) {
|
||||
const changedMetadata = { ...mapStyle.metadata }
|
||||
|
@ -293,10 +96,6 @@ class ExportModal extends React.Component {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="maputnik-modal-section hide">
|
||||
<h4>Save style</h4>
|
||||
<Gist mapStyle={this.props.mapStyle} onStyleChanged={this.props.onStyleChanged}/>
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,6 @@ class LoadingModal extends React.Component {
|
|||
message: PropTypes.node.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
underlayOnClick(e) {
|
||||
// This stops click events falling through to underlying modals.
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -4,7 +4,6 @@ import LoadingModal from './LoadingModal'
|
|||
import Modal from './Modal'
|
||||
import Button from '../Button'
|
||||
import FileReaderInput from 'react-file-reader-input'
|
||||
import request from 'request'
|
||||
|
||||
import FileUploadIcon from 'react-icons/lib/md/file-upload'
|
||||
import AddIcon from 'react-icons/lib/md/add-circle-outline'
|
||||
|
@ -74,42 +73,48 @@ class OpenModal extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
onStyleSelect(styleUrl) {
|
||||
onStyleSelect = (styleUrl) => {
|
||||
this.clearError();
|
||||
|
||||
const reqOpts = {
|
||||
url: styleUrl,
|
||||
withCredentials: false,
|
||||
}
|
||||
|
||||
const activeRequest = request(reqOpts, (error, response, body) => {
|
||||
const activeRequest = fetch(styleUrl, {
|
||||
mode: 'cors',
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then((body) => {
|
||||
this.setState({
|
||||
activeRequest: null,
|
||||
activeRequestUrl: null
|
||||
});
|
||||
|
||||
if (!error && response.statusCode == 200) {
|
||||
const mapStyle = style.ensureStyleValidity(JSON.parse(body))
|
||||
console.log('Loaded style ', mapStyle.id)
|
||||
this.props.onStyleOpen(mapStyle)
|
||||
this.onOpenToggle()
|
||||
} else {
|
||||
console.warn('Could not open the style URL', styleUrl)
|
||||
}
|
||||
const mapStyle = style.ensureStyleValidity(body)
|
||||
console.log('Loaded style ', mapStyle.id)
|
||||
this.props.onStyleOpen(mapStyle)
|
||||
this.onOpenToggle()
|
||||
})
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
activeRequest: null,
|
||||
activeRequestUrl: null
|
||||
});
|
||||
console.error(err);
|
||||
console.warn('Could not open the style URL', styleUrl)
|
||||
})
|
||||
|
||||
this.setState({
|
||||
activeRequest: activeRequest,
|
||||
activeRequestUrl: reqOpts.url
|
||||
activeRequestUrl: styleUrl
|
||||
})
|
||||
}
|
||||
|
||||
onOpenUrl() {
|
||||
onOpenUrl = () => {
|
||||
const url = this.styleUrlElement.value;
|
||||
this.onStyleSelect(url);
|
||||
}
|
||||
|
||||
onUpload(_, files) {
|
||||
onUpload = (_, files) => {
|
||||
const [e, file] = files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
|
@ -146,7 +151,7 @@ class OpenModal extends React.Component {
|
|||
url={style.url}
|
||||
title={style.title}
|
||||
thumbnailUrl={style.thumbnail}
|
||||
onSelect={this.onStyleSelect.bind(this)}
|
||||
onSelect={this.onStyleSelect}
|
||||
/>
|
||||
})
|
||||
|
||||
|
@ -170,7 +175,7 @@ class OpenModal extends React.Component {
|
|||
<section className="maputnik-modal-section">
|
||||
<h2>Upload Style</h2>
|
||||
<p>Upload a JSON style from your computer.</p>
|
||||
<FileReaderInput onChange={this.onUpload.bind(this)} tabIndex="-1">
|
||||
<FileReaderInput onChange={this.onUpload} tabIndex="-1">
|
||||
<Button className="maputnik-upload-button"><FileUploadIcon /> Upload</Button>
|
||||
</FileReaderInput>
|
||||
</section>
|
||||
|
@ -182,7 +187,7 @@ class OpenModal extends React.Component {
|
|||
</p>
|
||||
<input data-wd-key="open-modal.url.input" type="text" ref={(input) => this.styleUrlElement = input} className="maputnik-input" placeholder="Enter URL..."/>
|
||||
<div>
|
||||
<Button data-wd-key="open-modal.url.button" className="maputnik-big-button" onClick={this.onOpenUrl.bind(this)}>Open URL</Button>
|
||||
<Button data-wd-key="open-modal.url.button" className="maputnik-big-button" onClick={this.onOpenUrl}>Open URL</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -15,10 +15,6 @@ class SettingsModal extends React.Component {
|
|||
onOpenToggle: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
changeStyleProperty(property, value) {
|
||||
const changedStyle = {
|
||||
...this.props.mapStyle,
|
||||
|
@ -107,7 +103,7 @@ class SettingsModal extends React.Component {
|
|||
data-wd-key="modal-settings.maputnik:renderer"
|
||||
options={[
|
||||
['mbgljs', 'MapboxGL JS'],
|
||||
['ol3', 'Open Layers 3'],
|
||||
// ['ol3', 'Open Layers 3'],
|
||||
]}
|
||||
value={metadata['maputnik:renderer'] || 'mbgljs'}
|
||||
onChange={this.changeMetadataProperty.bind(this, 'maputnik:renderer')}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Button from '../Button'
|
||||
import Modal from './Modal'
|
||||
|
||||
|
||||
|
@ -11,10 +10,6 @@ class ShortcutsModal extends React.Component {
|
|||
onOpenToggle: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const help = [
|
||||
{
|
||||
|
|
|
@ -12,8 +12,6 @@ class SurveyModal extends React.Component {
|
|||
onOpenToggle: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) { super(props); }
|
||||
|
||||
onClick = () => {
|
||||
window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank');
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import lodash from 'lodash'
|
||||
import throttle from 'lodash.throttle'
|
||||
|
||||
|
||||
// Throttle for 3 seconds so when a user enables it they don't have to refresh the page.
|
||||
const reducedMotionEnabled = lodash.throttle(() => {
|
||||
const reducedMotionEnabled = throttle(() => {
|
||||
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
}, 3000);
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import request from 'request'
|
||||
import style from './style.js'
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||
|
||||
|
@ -14,15 +13,20 @@ export class ApiStyleStore {
|
|||
}
|
||||
|
||||
init(cb) {
|
||||
request(localUrl + '/styles', (error, response, body) => {
|
||||
if (!error && body && response.statusCode == 200) {
|
||||
const styleIds = JSON.parse(body)
|
||||
this.latestStyleId = styleIds[0]
|
||||
this.notifyLocalChanges()
|
||||
cb(null)
|
||||
} else {
|
||||
cb(new Error('Can not connect to style API'))
|
||||
}
|
||||
fetch(localUrl + '/styles', {
|
||||
mode: 'cors',
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(body) {
|
||||
const styleIds = body;
|
||||
this.latestStyleId = styleIds[0]
|
||||
this.notifyLocalChanges()
|
||||
cb(null)
|
||||
})
|
||||
.catch(function() {
|
||||
cb(new Error('Can not connect to style API'))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -44,8 +48,14 @@ export class ApiStyleStore {
|
|||
|
||||
latestStyle(cb) {
|
||||
if(this.latestStyleId) {
|
||||
request(localUrl + '/styles/' + this.latestStyleId, (error, response, body) => {
|
||||
cb(style.ensureStyleValidity(JSON.parse(body)))
|
||||
fetch(localUrl + '/styles/' + this.latestStyleId, {
|
||||
mode: 'cors',
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(body) {
|
||||
cb(style.ensureStyleValidity(body))
|
||||
})
|
||||
} else {
|
||||
throw new Error('No latest style available. You need to init the api backend first.')
|
||||
|
@ -55,11 +65,15 @@ export class ApiStyleStore {
|
|||
// Save current style replacing previous version
|
||||
save(mapStyle) {
|
||||
const id = mapStyle.id
|
||||
request.put({
|
||||
url: localUrl + '/styles/' + id,
|
||||
json: true,
|
||||
body: mapStyle
|
||||
}, (error, response, body) => {
|
||||
fetch(localUrl + '/styles/' + id, {
|
||||
method: "PUT",
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
body: JSON.stringify(mapStyle)
|
||||
})
|
||||
.catch(function(error) {
|
||||
if(error) console.error(error)
|
||||
})
|
||||
return mapStyle
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import MapboxGl from 'mapbox-gl/dist/mapbox-gl.js'
|
||||
import MapboxGl from 'mapbox-gl'
|
||||
|
||||
// Load mapbox-gl-rtl-text using object urls without needing http://localhost for AJAX.
|
||||
const data = require("base64-loader?mimetype=text/javascript!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.js");
|
||||
const data = require("raw-loader?mimetype=text/javascript!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.js");
|
||||
|
||||
const blob = new window.Blob([window.atob(data)]);
|
||||
const blob = new window.Blob([data]);
|
||||
const objectUrl = window.URL.createObjectURL(blob, {
|
||||
type: "text/javascript"
|
||||
});
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
import request from 'request'
|
||||
import npmurl from 'url'
|
||||
|
||||
function loadJSON(url, defaultValue, cb) {
|
||||
request({
|
||||
url: url,
|
||||
withCredentials: false,
|
||||
}, (error, response, body) => {
|
||||
if (!error && body && response.statusCode == 200) {
|
||||
try {
|
||||
cb(JSON.parse(body))
|
||||
} catch(err) {
|
||||
console.error(err)
|
||||
cb(defaultValue)
|
||||
}
|
||||
} else {
|
||||
console.warn('Can not metadata for ' + url)
|
||||
cb(defaultValue)
|
||||
}
|
||||
fetch(url, {
|
||||
mode: 'cors',
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(body) {
|
||||
cb(body)
|
||||
})
|
||||
.catch(function() {
|
||||
console.warn('Can not metadata for ' + url)
|
||||
cb(defaultValue)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import React from 'react';
|
||||
import deref from '@mapbox/mapbox-gl-style-spec/deref'
|
||||
import tokens from '../config/tokens.json'
|
||||
|
||||
|
@ -54,27 +53,27 @@ function indexOfLayer(layers, layerId) {
|
|||
return null
|
||||
}
|
||||
|
||||
function getAccessToken(key, mapStyle, opts) {
|
||||
if(key === "thunderforest_transport" || key === "thunderforest_outdoors") {
|
||||
key = "thunderforest";
|
||||
function getAccessToken(sourceName, mapStyle, opts) {
|
||||
if(sourceName === "thunderforest_transport" || sourceName === "thunderforest_outdoors") {
|
||||
sourceName = "thunderforest"
|
||||
}
|
||||
|
||||
const metadata = mapStyle.metadata || {}
|
||||
let accessToken = metadata['maputnik:'+key+'_access_token'];
|
||||
let accessToken = metadata[`maputnik:${sourceName}_access_token`]
|
||||
|
||||
if(opts.allowFallback && !accessToken) {
|
||||
accessToken = tokens[key];
|
||||
accessToken = tokens[sourceName]
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
function replaceSourceAccessToken(mapStyle, key, opts={}) {
|
||||
const source = mapStyle.sources[key]
|
||||
function replaceSourceAccessToken(mapStyle, sourceName, opts={}) {
|
||||
const source = mapStyle.sources[sourceName]
|
||||
if(!source) return mapStyle
|
||||
if(!source.hasOwnProperty("url")) return mapStyle
|
||||
|
||||
const accessToken = getAccessToken(key, mapStyle, opts)
|
||||
const accessToken = getAccessToken(sourceName, mapStyle, opts)
|
||||
|
||||
if(!accessToken) {
|
||||
// Early exit.
|
||||
|
@ -83,7 +82,7 @@ function replaceSourceAccessToken(mapStyle, key, opts={}) {
|
|||
|
||||
const changedSources = {
|
||||
...mapStyle.sources,
|
||||
[key]: {
|
||||
[sourceName]: {
|
||||
...source,
|
||||
url: source.url.replace('{key}', accessToken)
|
||||
}
|
||||
|
@ -92,21 +91,20 @@ function replaceSourceAccessToken(mapStyle, key, opts={}) {
|
|||
...mapStyle,
|
||||
sources: changedSources
|
||||
}
|
||||
|
||||
return changedStyle
|
||||
}
|
||||
|
||||
function replaceAccessTokens(mapStyle, opts={}) {
|
||||
let changedStyle = mapStyle;
|
||||
let changedStyle = mapStyle
|
||||
|
||||
Object.keys(mapStyle.sources).forEach((tokenKey) => {
|
||||
changedStyle = replaceSourceAccessToken(changedStyle, tokenKey, opts);
|
||||
Object.keys(mapStyle.sources).forEach((sourceName) => {
|
||||
changedStyle = replaceSourceAccessToken(changedStyle, sourceName, opts);
|
||||
})
|
||||
|
||||
if(mapStyle.glyphs && mapStyle.glyphs.match(/\.tileserver\.org/)) {
|
||||
if (mapStyle.glyphs && mapStyle.glyphs.match(/\.tilehosting\.com/)) {
|
||||
changedStyle = {
|
||||
...changedStyle,
|
||||
glyphs: mapStyle.glyphs ? mapStyle.glyphs.replace('{key}', getAccessToken("openmaptiles", mapStyle, opts)) : mapStyle.glyphs
|
||||
glyphs: mapStyle.glyphs.replace('{key}', getAccessToken("openmaptiles", mapStyle, opts))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { colorizeLayers } from './style.js'
|
||||
import style from './style.js'
|
||||
import { loadStyleUrl } from './urlopen'
|
||||
import publicSources from '../config/styles.json'
|
||||
import request from 'request'
|
||||
|
||||
const storagePrefix = "maputnik"
|
||||
const stylePrefix = 'style'
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import request from 'request'
|
||||
import url from 'url'
|
||||
import style from './style.js'
|
||||
|
||||
|
@ -9,34 +8,40 @@ export function initialStyleUrl() {
|
|||
|
||||
export function loadStyleUrl(styleUrl, cb) {
|
||||
console.log('Loading style', styleUrl)
|
||||
request({
|
||||
url: styleUrl,
|
||||
withCredentials: false,
|
||||
}, (error, response, body) => {
|
||||
if (!error && response.statusCode == 200) {
|
||||
cb(style.ensureStyleValidity(JSON.parse(body)))
|
||||
} else {
|
||||
console.warn('Could not fetch default style', styleUrl)
|
||||
cb(style.emptyStyle)
|
||||
}
|
||||
fetch(styleUrl, {
|
||||
mode: 'cors',
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(body) {
|
||||
cb(style.ensureStyleValidity(body))
|
||||
})
|
||||
.catch(function() {
|
||||
console.warn('Could not fetch default style', styleUrl)
|
||||
cb(style.emptyStyle)
|
||||
})
|
||||
}
|
||||
|
||||
export function loadJSON(url, defaultValue, cb) {
|
||||
request({
|
||||
url: url,
|
||||
withCredentials: false,
|
||||
}, (error, response, body) => {
|
||||
if (!error && body && response.statusCode == 200) {
|
||||
try {
|
||||
cb(JSON.parse(body))
|
||||
} catch(err) {
|
||||
console.error(err)
|
||||
cb(defaultValue)
|
||||
}
|
||||
} else {
|
||||
console.error('Can not load JSON from ' + url)
|
||||
fetch(url, {
|
||||
mode: 'cors',
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(body) {
|
||||
try {
|
||||
cb(body)
|
||||
} catch(err) {
|
||||
console.error(err)
|
||||
cb(defaultValue)
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
console.error('Can not load JSON from ' + url)
|
||||
cb(defaultValue)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// MAP
|
||||
.maputnik-map {
|
||||
display: flex;
|
||||
position: fixed !important;
|
||||
top: $toolbar-height + $toolbar-offset;
|
||||
right: 0;
|
||||
|
@ -10,6 +11,16 @@
|
|||
- 200px /* layer list */
|
||||
- 350px /* layer editor */
|
||||
);
|
||||
|
||||
&--error {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__error-message {
|
||||
margin: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// DOC LABEL
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
var assert = require("assert");
|
||||
var config = require("../../config/specs");
|
||||
var helper = require("../helper");
|
||||
var wd = require("../../wd-helper");
|
||||
|
||||
|
||||
describe.skip("history", function() {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
var assert = require('assert');
|
||||
var config = require("../config/specs");
|
||||
var geoServer = require("../geojson-server");
|
||||
var helper = require("./helper");
|
||||
|
||||
require("./util/webdriverio-ext");
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
var assert = require('assert');
|
||||
var wd = require("../../wd-helper");
|
||||
var config = require("../../config/specs");
|
||||
var helper = require("../helper");
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
var artifacts = require("../../artifacts");
|
||||
var config = require("../../config/specs");
|
||||
var helper = require("../helper");
|
||||
var wd = require("../../wd-helper");
|
||||
|
|
Loading…
Reference in a new issue