diff --git a/.babelrc b/.babelrc index adae42d..27baf2a 100644 --- a/.babelrc +++ b/.babelrc @@ -5,9 +5,9 @@ "test": { "plugins": [ ["istanbul", { - exclude: ["node_modules/**", "test/**"] + "exclude": ["node_modules/**", "test/**"] }] ] } } -} +} \ No newline at end of file diff --git a/config/webpack.production.config.js b/config/webpack.production.config.js index c015f4a..a1e1e58 100644 --- a/config/webpack.production.config.js +++ b/config/webpack.production.config.js @@ -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': { diff --git a/package-lock.json b/package-lock.json index 4e61878..320039f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "maputnik", - "version": "1.4.0", + "version": "1.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 317bfc2..90d191b 100644 --- a/package.json +++ b/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", diff --git a/src/components/App.jsx b/src/components/App.jsx index da0aef4..8c9eae4 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -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 = + mapElement =
TODO
} else { mapElement = + onLayerSelect={this.onLayerSelect} /> } const elementStyle = {}; @@ -440,7 +461,7 @@ export default class App extends React.Component { } - 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 = : null const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? diff --git a/src/components/Toolbar.jsx b/src/components/Toolbar.jsx index ede36f2..77ee1ad 100644 --- a/src/components/Toolbar.jsx +++ b/src/components/Toolbar.jsx @@ -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, } } diff --git a/src/components/fields/ColorField.jsx b/src/components/fields/ColorField.jsx index 6e538c9..9a40090 100644 --- a/src/components/fields/ColorField.jsx +++ b/src/components/fields/ColorField.jsx @@ -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 { />
this.colorInput = input} - onClick={this.togglePicker.bind(this)} + onClick={this.togglePicker} style={this.props.style} name={this.props.name} placeholder={this.props.default} diff --git a/src/components/fields/DocLabel.jsx b/src/components/fields/DocLabel.jsx index 9af6a86..65d7193 100644 --- a/src/components/fields/DocLabel.jsx +++ b/src/components/fields/DocLabel.jsx @@ -17,7 +17,7 @@ export default class DocLabel extends React.Component {
{this.props.doc}
-
+ } } diff --git a/src/components/fields/FunctionSpecField.jsx b/src/components/fields/FunctionSpecField.jsx index 981bb1c..3067c1b 100644 --- a/src/components/fields/FunctionSpecField.jsx +++ b/src/components/fields/FunctionSpecField.jsx @@ -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} /> ) } diff --git a/src/components/fields/PropertyGroup.jsx b/src/components/fields/PropertyGroup.jsx index 5d3c72c..efb567b 100644 --- a/src/components/fields/PropertyGroup.jsx +++ b/src/components/fields/PropertyGroup.jsx @@ -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 = 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 { diff --git a/src/components/inputs/AutocompleteInput.jsx b/src/components/inputs/AutocompleteInput.jsx index d33bdbd..2ba6ef8 100644 --- a/src/components/inputs/AutocompleteInput.jsx +++ b/src/components/inputs/AutocompleteInput.jsx @@ -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(); } diff --git a/src/components/inputs/DynamicArrayInput.jsx b/src/components/inputs/DynamicArrayInput.jsx index 5596e24..6e58d56 100644 --- a/src/components/inputs/DynamicArrayInput.jsx +++ b/src/components/inputs/DynamicArrayInput.jsx @@ -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} diff --git a/src/components/inputs/NumberInput.jsx b/src/components/inputs/NumberInput.jsx index 90cf969..75ed148 100644 --- a/src/components/inputs/NumberInput.jsx +++ b/src/components/inputs/NumberInput.jsx @@ -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} /> } } diff --git a/src/components/layers/LayerEditor.jsx b/src/components/layers/LayerEditor.jsx index 6e245b3..913cef7 100644 --- a/src/components/layers/LayerEditor.jsx +++ b/src/components/layers/LayerEditor.jsx @@ -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. */ diff --git a/src/components/layers/LayerList.jsx b/src/components/layers/LayerList.jsx index 83ebe33..dcabe6b 100644 --- a/src/components/layers/LayerList.jsx +++ b/src/components/layers/LayerList.jsx @@ -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 {
diff --git a/src/components/layers/LayerListItem.jsx b/src/components/layers/LayerListItem.jsx index 5c65188..805e50f 100644 --- a/src/components/layers/LayerListItem.jsx +++ b/src/components/layers/LayerListItem.jsx @@ -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 diff --git a/src/components/layers/LayerSourceBlock.jsx b/src/components/layers/LayerSourceBlock.jsx index 3773f73..9a6b9e2 100644 --- a/src/components/layers/LayerSourceBlock.jsx +++ b/src/components/layers/LayerSourceBlock.jsx @@ -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 { diff --git a/src/components/layers/LayerSourceLayerBlock.jsx b/src/components/layers/LayerSourceLayerBlock.jsx index 804e93f..15b48b5 100644 --- a/src/components/layers/LayerSourceLayerBlock.jsx +++ b/src/components/layers/LayerSourceLayerBlock.jsx @@ -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 { diff --git a/src/components/map/FeatureLayerPopup.jsx b/src/components/map/FeatureLayerPopup.jsx index 5d14d01..2d0eacb 100644 --- a/src/components/map/FeatureLayerPopup.jsx +++ b/src/components/map/FeatureLayerPopup.jsx @@ -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) { diff --git a/src/components/map/MapboxGlMap.jsx b/src/components/map/MapboxGlMap.jsx index 1858e46..1eb59f6 100644 --- a/src/components/map/MapboxGlMap.jsx +++ b/src/components/map/MapboxGlMap.jsx @@ -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(, 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
this.container = x} - >
+ if(IS_SUPPORTED) { + return
this.container = x} + >
+ } + else { + return
+
+ Error: Cannot load MapboxGL, WebGL is either unsupported or disabled +
+
+ } } } diff --git a/src/components/map/OpenLayers3Map.jsx b/src/components/map/OpenLayers3Map.jsx index a5881de..fade6b2 100644 --- a/src/components/map/OpenLayers3Map.jsx +++ b/src/components/map/OpenLayers3Map.jsx @@ -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' diff --git a/src/components/modals/AddModal.jsx b/src/components/modals/AddModal.jsx index b42b736..0cdac51 100644 --- a/src/components/modals/AddModal.jsx +++ b/src/components/modals/AddModal.jsx @@ -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 { } - - -

-
- } - } - - render() { - return
- -
- - Public gist -
-
- - Include preview -
- {this.state.preview ? -
- - - - - - - Get your free access token -
- : null} - {this.renderLatestGist()} -
- } -} function stripAccessTokens(mapStyle) { const changedMetadata = { ...mapStyle.metadata } @@ -293,10 +96,6 @@ class ExportModal extends React.Component { -
-

Save style

- -
} } diff --git a/src/components/modals/LoadingModal.jsx b/src/components/modals/LoadingModal.jsx index 42b8eca..f4ef6bb 100644 --- a/src/components/modals/LoadingModal.jsx +++ b/src/components/modals/LoadingModal.jsx @@ -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(); diff --git a/src/components/modals/OpenModal.jsx b/src/components/modals/OpenModal.jsx index d9df74c..5d9901f 100644 --- a/src/components/modals/OpenModal.jsx +++ b/src/components/modals/OpenModal.jsx @@ -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 {

Upload Style

Upload a JSON style from your computer.

- +
@@ -182,7 +187,7 @@ class OpenModal extends React.Component {

this.styleUrlElement = input} className="maputnik-input" placeholder="Enter URL..."/>
- +
diff --git a/src/components/modals/SettingsModal.jsx b/src/components/modals/SettingsModal.jsx index b7da0a1..c89aa98 100644 --- a/src/components/modals/SettingsModal.jsx +++ b/src/components/modals/SettingsModal.jsx @@ -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')} diff --git a/src/components/modals/ShortcutsModal.jsx b/src/components/modals/ShortcutsModal.jsx index 6a17e4b..df8312e 100644 --- a/src/components/modals/ShortcutsModal.jsx +++ b/src/components/modals/ShortcutsModal.jsx @@ -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 = [ { diff --git a/src/components/modals/SurveyModal.jsx b/src/components/modals/SurveyModal.jsx index e1ae021..7f10bc3 100644 --- a/src/components/modals/SurveyModal.jsx +++ b/src/components/modals/SurveyModal.jsx @@ -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'); diff --git a/src/libs/accessibility.js b/src/libs/accessibility.js index 095e953..23c1339 100644 --- a/src/libs/accessibility.js +++ b/src/libs/accessibility.js @@ -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); diff --git a/src/libs/apistore.js b/src/libs/apistore.js index 91fa7e2..aa114b2 100644 --- a/src/libs/apistore.js +++ b/src/libs/apistore.js @@ -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 diff --git a/src/libs/mapbox-rtl.js b/src/libs/mapbox-rtl.js index f9dc199..84d9211 100644 --- a/src/libs/mapbox-rtl.js +++ b/src/libs/mapbox-rtl.js @@ -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" }); diff --git a/src/libs/metadata.js b/src/libs/metadata.js index deac199..2e1fb24 100644 --- a/src/libs/metadata.js +++ b/src/libs/metadata.js @@ -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) }) } diff --git a/src/libs/style.js b/src/libs/style.js index b867148..60598f5 100644 --- a/src/libs/style.js +++ b/src/libs/style.js @@ -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)) } } diff --git a/src/libs/stylestore.js b/src/libs/stylestore.js index c921d5c..17499bd 100644 --- a/src/libs/stylestore.js +++ b/src/libs/stylestore.js @@ -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' diff --git a/src/libs/urlopen.js b/src/libs/urlopen.js index 93c550a..20b7aa3 100644 --- a/src/libs/urlopen.js +++ b/src/libs/urlopen.js @@ -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) + }) } diff --git a/src/styles/_components.scss b/src/styles/_components.scss index e257211..302ec52 100644 --- a/src/styles/_components.scss +++ b/src/styles/_components.scss @@ -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 diff --git a/test/functional/history/index.js b/test/functional/history/index.js index 2cd5b0e..e788e7e 100644 --- a/test/functional/history/index.js +++ b/test/functional/history/index.js @@ -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() { diff --git a/test/functional/index.js b/test/functional/index.js index 5522706..3c9f5ec 100644 --- a/test/functional/index.js +++ b/test/functional/index.js @@ -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"); diff --git a/test/functional/map/index.js b/test/functional/map/index.js index 498c0a1..2df5393 100644 --- a/test/functional/map/index.js +++ b/test/functional/map/index.js @@ -1,5 +1,3 @@ -var assert = require('assert'); -var wd = require("../../wd-helper"); var config = require("../../config/specs"); var helper = require("../helper"); diff --git a/test/functional/screenshots/index.js b/test/functional/screenshots/index.js index 9c5634c..a6d6d2a 100644 --- a/test/functional/screenshots/index.js +++ b/test/functional/screenshots/index.js @@ -1,4 +1,3 @@ -var artifacts = require("../../artifacts"); var config = require("../../config/specs"); var helper = require("../helper"); var wd = require("../../wd-helper");