diff --git a/.babelrc b/.babelrc index adae42de..27baf2a0 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/.travis.yml b/.travis.yml deleted file mode 100644 index 9d83eff8..00000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: node_js -matrix: - include: - - os: osx - node_js: "6" - - os: osx - node_js: "8" - - os: osx - node_js: "9" -install: - - npm install -script: - - mkdir public - - node --stack_size=100000 $(which npm) run build - - npm run lint - - npm run lint-styles -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.8 diff --git a/README.md b/README.md index d8630aa8..536b0802 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ targeted at developers and map designers. Mapbox has built one of the best and most amazing OSS ecosystems. A key component to ensure its longevity and independance is an OSS map designer. + +## Donations +If you or your organisation has seen value from Maputnik, please consider donating at + + ## Documentation The documentation can be found in the [Wiki](https://github.com/maputnik/editor/wiki). You are welcome to collaborate! @@ -157,6 +162,6 @@ Sina Martinelli, Nicholas Doiron, Neil Cawse, Urs42, Benedikt Groß, Manuel Roth Maputnik is [licensed under MIT](LICENSE) and is Copyright (c) Lukas Martinelli and contributors. -**Disclaimer** This project is not affiliated with Mapbox or Mapbox Studio. It is a independent style editor for the +**Disclaimer** This project is not affiliated with Mapbox or Mapbox Studio. It is an independent style editor for the open source technology in the Mapbox GL ecosystem. As contributor please take extra care of not violating any Mapbox trademarks. Do not get inspired by Mapbox Studio and make your own decisions for a good style editor. diff --git a/appveyor.yml b/appveyor.yml index 0b100cc9..9d469f89 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,9 @@ +image: Visual Studio 2017 environment: matrix: - - nodejs_version: "6" - nodejs_version: "8" - nodejs_version: "9" + - nodejs_version: "10" platform: - x86 - x64 diff --git a/config/webpack.production.config.js b/config/webpack.production.config.js index c015f4ad..a1e1e588 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 4667ddc9..320039f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "maputnik", - "version": "1.2.0", + "version": "1.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -281,25 +281,20 @@ "wgs84": "0.0.0" } }, - "@mapbox/gl-matrix": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@mapbox/gl-matrix/-/gl-matrix-0.0.1.tgz", - "integrity": "sha1-5RJqq01kw2uBx6l9CuDd3eV3PSs=" - }, "@mapbox/jsonlint-lines-primitives": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.1.tgz", "integrity": "sha512-LGegvJq+ks4UFnEAvtAhygFRZxaNg2vw7PsvLOAXY8ziJLv5aFDSdHDEMJA/Q8PG5dNhMioUvo0kNqb7U5dPoQ==" }, "@mapbox/mapbox-gl-rtl-text": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-rtl-text/-/mapbox-gl-rtl-text-0.1.2.tgz", - "integrity": "sha512-x5xSHNAD5MeuasbEpGyDBGy4zPQysDdvDUlnjDKMxic8KqmcxcCP3ojlIaqEWYFvMmq7FKyWIF7K8jO4bHavKQ==" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-rtl-text/-/mapbox-gl-rtl-text-0.2.0.tgz", + "integrity": "sha512-oijFgP0DTMRtLzEpZM5lCrlagoQaOXUhZOMKOXaCF56QohKdRO4uu8RUKcuvfpWz6ruQcyH2yikBl09aWh3Duw==" }, "@mapbox/mapbox-gl-style-spec": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-12.0.0.tgz", - "integrity": "sha1-dRW/rbs6hZ66HFbZn8E4P6YpJ0c=", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.1.0.tgz", + "integrity": "sha512-WoviOAN39bZ3Fm3jBycGZg64rAihZ1ykVOR/XAiJLPviyPGTSWo0kIzcW2FRg23LJ0FSysEXhbDWXtHADUFj+w==", "requires": { "@mapbox/jsonlint-lines-primitives": "2.0.1", "@mapbox/unitbezier": "0.0.0", @@ -309,11 +304,6 @@ "sort-object": "0.3.2" } }, - "@mapbox/mapbox-gl-supported": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.3.1.tgz", - "integrity": "sha512-tMdtbKjxVaA3WZubFTKIPjAHKNz8PCqUGdRgT7RIpqLHLxZi3eQWzixrnyYA5dglEnbfcIEPvtRQu/4yXPt2YQ==" - }, "@mapbox/point-geometry": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", @@ -6536,11 +6526,6 @@ } } }, - "geojson-vt": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.1.1.tgz", - "integrity": "sha512-Idr9VfV+EtycnleWLKgYbTFAthfE1V700eCqTlC6P2ODh715fTp4cCQYleYh3TPLzlzn2K4Gk514TstsoDue9w==" - }, "get-caller-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", @@ -6584,6 +6569,11 @@ "utf8": "2.1.2" } }, + "gl-matrix": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-2.7.1.tgz", + "integrity": "sha512-22I6q7aO2oKNahNV0+9JavVNUhQXRTvR5jP2s8U1l93TkjcQe8RK6MeMYpM7+66R0sCVUgSdO97BL439vePyzQ==" + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -8216,11 +8206,6 @@ } } }, - "kdbush": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-1.0.1.tgz", - "integrity": "sha1-PL0D6d6tnA9vZszblkUOXOzGQOA=" - }, "kew": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", @@ -8582,13 +8567,12 @@ } }, "mapbox-gl": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-0.45.0.tgz", - "integrity": "sha1-r3HMgk8NflHM1cUF6q5BG8CRDM0=", + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-0.47.0.tgz", + "integrity": "sha512-y1AlNYMAKaqEtaqni0zOMYj9gTc1gZ0lqLkxXK9iFg5+ZBITc5DL9AcrXhpEXNxUzXKFa7dZkSULyNaqXFQ8yQ==", "requires": { - "@mapbox/gl-matrix": "0.0.1", "@mapbox/jsonlint-lines-primitives": "2.0.1", - "@mapbox/mapbox-gl-supported": "1.3.1", + "@mapbox/mapbox-gl-supported": "1.4.0", "@mapbox/point-geometry": "0.1.0", "@mapbox/shelf-pack": "3.1.0", "@mapbox/tiny-sdf": "1.1.0", @@ -8599,7 +8583,8 @@ "csscolorparser": "1.0.3", "earcut": "2.1.3", "geojson-rewind": "0.3.1", - "geojson-vt": "3.1.1", + "geojson-vt": "3.2.0", + "gl-matrix": "2.7.1", "gray-matter": "3.1.1", "grid-index": "1.0.0", "minimist": "0.0.8", @@ -8608,10 +8593,35 @@ "rw": "1.3.3", "shuffle-seed": "1.1.6", "sort-object": "0.3.2", - "supercluster": "2.3.0", + "supercluster": "4.1.1", "through2": "2.0.3", "tinyqueue": "1.2.3", "vt-pbf": "3.1.0" + }, + "dependencies": { + "@mapbox/mapbox-gl-supported": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.4.0.tgz", + "integrity": "sha512-ZD0Io4XK+/vU/4zpANjOtdWfVszAgnaMPsGR6LKsWh4kLIEv9qoobTVmJPPuwuM+ZI2b3BlZ6DYw1XHVmv6YTA==" + }, + "geojson-vt": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.0.tgz", + "integrity": "sha512-qk7sEv7dMfuGzflwClsgtO1fWPut/TqCInWEEUJc/Ofn4tmqBGznnPv3eUdxtwMkulMaAwSL3osHiyN03XJd/w==" + }, + "kdbush": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-2.0.1.tgz", + "integrity": "sha512-9KqSdmWCkBIisFIGclT0FRagKhI7IVbMyUjsxCFG0Ly1Dg6whlxJ7b9lrq8ifk3X/fGeJzok1R75LQfZTfA5zQ==" + }, + "supercluster": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-4.1.1.tgz", + "integrity": "sha512-sF0FfUOPFp96DKzwWFLeQOEqqKu2PpcesxAFeFsknA/q7g7igVVn/p3NI2XHEghNSyDAqunKNKqAbqNO8+7NDQ==", + "requires": { + "kdbush": "2.0.1" + } + } } }, "mapbox-gl-inspect": { @@ -14176,14 +14186,6 @@ } } }, - "supercluster": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-2.3.0.tgz", - "integrity": "sha1-h6tWCBu+qaHXJN9TUe6ejDry9Is=", - "requires": { - "kdbush": "1.0.1" - } - }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", diff --git a/package.json b/package.json index 575c9abd..90d191be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maputnik", - "version": "1.2.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", @@ -21,44 +20,39 @@ "license": "MIT", "homepage": "https://github.com/maputnik/editor#readme", "dependencies": { - "@mapbox/mapbox-gl-rtl-text": "^0.1.2", - "@mapbox/mapbox-gl-style-spec": "^12.0.0", + "@mapbox/mapbox-gl-rtl-text": "^0.2.0", + "@mapbox/mapbox-gl-style-spec": "^13.1.0", "classnames": "^2.2.5", "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", "lodash.clonedeep": "^4.5.0", "lodash.isequal": "^4.5.0", "lodash.throttle": "^4.1.1", - "mapbox-gl": "^0.45.0", + "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 ed06070d..7cce8783 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 {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' @@ -18,13 +17,14 @@ import ExportModal from './modals/ExportModal' import SourcesModal from './modals/SourcesModal' import OpenModal from './modals/OpenModal' import ShortcutsModal from './modals/ShortcutsModal' +import SurveyModal from './modals/SurveyModal' import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata' import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec' -import style from '../libs/style.js' +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' @@ -34,7 +34,7 @@ import Debug from '../libs/debug' import queryUtil from '../libs/query-util' import MapboxGl from 'mapbox-gl' -import mapboxUtil from 'mapbox-gl/src/util/mapbox' +import { normalizeSourceURL } from 'mapbox-gl/src/util/mapbox' function updateRootSpec(spec, fieldName, newValues) { @@ -53,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) @@ -172,9 +174,11 @@ export default class App extends React.Component { open: false, shortcuts: false, export: false, + 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") }, } @@ -183,14 +187,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) { @@ -213,7 +234,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) { @@ -240,7 +261,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) @@ -250,7 +271,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) @@ -260,7 +281,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); @@ -278,7 +299,7 @@ export default class App extends React.Component { this.onLayersChange(layers); } - onLayersChange(changedLayers) { + onLayersChange = (changedLayers) => { const changedStyle = { ...this.state.mapStyle, layers: changedLayers @@ -286,7 +307,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) @@ -294,7 +315,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) @@ -305,7 +326,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) @@ -320,7 +341,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) @@ -332,7 +353,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 @@ -340,7 +361,7 @@ export default class App extends React.Component { this.onLayersChange(changedLayers) } - setMapState(newState) { + setMapState = (newState) => { this.setState({ mapState: newState }) @@ -362,12 +383,14 @@ export default class App extends React.Component { if(!this.state.sources.hasOwnProperty(key) && val.type === "vector" && val.hasOwnProperty("url")) { let url = val.url; try { - url = mapboxUtil.normalizeSourceURL(url, MapboxGl.accessToken); + url = normalizeSourceURL(url, MapboxGl.accessToken); } catch(err) { console.warn("Failed to normalizeSourceURL: ", err); } - fetch(url) + fetch(url, { + mode: 'cors', + }) .then((response) => { return response.json(); }) @@ -404,7 +427,7 @@ export default class App extends React.Component { mapRenderer() { const mapProps = { - mapStyle: style.replaceAccessToken(this.state.mapStyle, {allowFallback: true}), + mapStyle: style.replaceAccessTokens(this.state.mapStyle, {allowFallback: true}), options: this.state.mapOptions, onDataChange: (e) => { this.layerWatcher.analyzeMap(e.map) @@ -419,28 +442,29 @@ 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} /> } - let filterName = ""; + let filterName; if(this.state.mapState.match(/^filter-/)) { filterName = this.state.mapState.replace(/^filter-/, ""); } - const elementStyle = { - "filter": `url('#${filterName}')` + const elementStyle = {}; + if (filterName) { + elementStyle.filter = `url('color-accessibility.svg#${filterName}')`; }; - return
+ return
{mapElement}
} - onLayerSelect(layerId) { + onLayerSelect = (layerId) => { const idx = style.indexOfLayer(this.state.mapStyle.layers, layerId) this.setState({ selectedLayerIndex: idx }) } @@ -452,6 +476,10 @@ export default class App extends React.Component { [modalName]: !this.state.isOpen[modalName] } }) + + if(modalName === 'survey') { + localStorage.setItem('survey', ''); + } } render() { @@ -464,19 +492,19 @@ export default class App extends React.Component { mapStyle={this.state.mapStyle} inspectModeEnabled={this.state.mapState === "inspect"} sources={this.state.sources} - onStyleChanged={this.onStyleChanged.bind(this)} - onStyleOpen={this.onStyleChanged.bind(this)} - onSetMapState={this.setMapState.bind(this)} + onStyleChanged={this.onStyleChanged} + onStyleOpen={this.onStyleChanged} + onSetMapState={this.setMapState} onToggleModal={this.toggleModal.bind(this)} /> const layerList = : null const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? +
return + + {this.props.children} + + + } +} + class ToolbarAction extends React.Component { static propTypes = { children: PropTypes.node, @@ -89,16 +103,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, } } @@ -211,6 +222,10 @@ export default class Toolbar extends React.Component { Help + + + Take the Maputnik Survey + diff --git a/src/components/fields/ColorField.jsx b/src/components/fields/ColorField.jsx index 6e538c97..9a400900 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 9af6a860..65d7193b 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 981bb1c9..3067c1b4 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 5d3c72c1..efb567bb 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 { + if(!state.refs.hasOwnProperty(idx)) { + if(!newRefs) { + newRefs = {...state}; + } + newRefs[idx] = docUid("stop-"); + } + }) + } + + return newRefs; +} + + export default class ZoomProperty extends React.Component { static propTypes = { onChange: PropTypes.func, @@ -29,45 +53,13 @@ export default class ZoomProperty extends React.Component { ]), } - - constructor() { - super() - this.state = { - refs: {} - } + state = { + refs: {} } componentDidMount() { - this.setState({ - refs: this.setStopRefs(this.props) - }) - } + const newRefs = setStopRefs(this.props, this.state); - /** - * We cache a reference for each stop by its index. - * - * When the stops are reordered the references are also updated (see this.orderStops) this allows React to use the same key for the element and keep keyboard focus. - */ - setStopRefs(props) { - // This is initialsed below only if required to improved performance. - let newRefs; - - if(props.value && props.value.stops) { - props.value.stops.forEach((val, idx) => { - if(!this.state.refs.hasOwnProperty(idx)) { - if(!newRefs) { - newRefs = {...this.state.refs}; - } - newRefs[idx] = docUid("stop-"); - } - }) - } - - return newRefs; - } - - UNSAFE_componentWillReceiveProps(nextProps) { - const newRefs = this.setStopRefs(nextProps); if(newRefs) { this.setState({ refs: newRefs @@ -75,6 +67,16 @@ export default class ZoomProperty extends React.Component { } } + static getDerivedStateFromProps(props, state) { + const newRefs = setStopRefs(props, state); + if(newRefs) { + return { + refs: newRefs + }; + } + return null; + } + // Order the stops altering the refs to reflect their new position. orderStopsByZoom(stops) { const mappedWithRef = stops diff --git a/src/components/filter/FilterEditor.jsx b/src/components/filter/FilterEditor.jsx index abbad2a5..a4c25d86 100644 --- a/src/components/filter/FilterEditor.jsx +++ b/src/components/filter/FilterEditor.jsx @@ -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 { diff --git a/src/components/inputs/AutocompleteInput.jsx b/src/components/inputs/AutocompleteInput.jsx index d33bdbd8..2ba6ef86 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 5596e247..6e58d560 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 447fab80..75ed1485 100644 --- a/src/components/inputs/NumberInput.jsx +++ b/src/components/inputs/NumberInput.jsx @@ -17,8 +17,10 @@ class NumberInput extends React.Component { } } - UNSAFE_componentWillReceiveProps(nextProps) { - this.setState({ value: nextProps.value }) + static getDerivedStateFromProps(props, state) { + return { + value: props.value + }; } changeValue(newValue) { @@ -49,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) @@ -72,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/inputs/StringInput.jsx b/src/components/inputs/StringInput.jsx index b94a709f..b5f6bacf 100644 --- a/src/components/inputs/StringInput.jsx +++ b/src/components/inputs/StringInput.jsx @@ -18,8 +18,10 @@ class StringInput extends React.Component { } } - UNSAFE_componentWillReceiveProps(nextProps) { - this.setState({ value: nextProps.value || '' }) + static getDerivedStateFromProps(props, state) { + return { + value: props.value || '' + }; } render() { diff --git a/src/components/layers/JSONEditor.jsx b/src/components/layers/JSONEditor.jsx index 8cec16d0..22046230 100644 --- a/src/components/layers/JSONEditor.jsx +++ b/src/components/layers/JSONEditor.jsx @@ -29,10 +29,10 @@ class JSONEditor extends React.Component { } } - UNSAFE_componentWillReceiveProps(nextProps) { - this.setState({ - code: JSON.stringify(nextProps.layer, null, 2) - }) + static getDerivedStateFromProps(props, state) { + return { + code: JSON.stringify(props.layer, null, 2) + }; } shouldComponentUpdate(nextProps, nextState) { diff --git a/src/components/layers/LayerEditor.jsx b/src/components/layers/LayerEditor.jsx index 7d7291dc..913cef75 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. */ @@ -79,18 +76,18 @@ export default class LayerEditor extends React.Component { this.state = { editorGroups } } - UNSAFE_componentWillReceiveProps(nextProps) { - const additionalGroups = { ...this.state.editorGroups } + static getDerivedStateFromProps(props, state) { + const additionalGroups = { ...state.editorGroups } - layout[nextProps.layer.type].groups.forEach(group => { + layout[props.layer.type].groups.forEach(group => { if(!(group.title in additionalGroups)) { additionalGroups[group.title] = true } }) - this.setState({ + return { editorGroups: additionalGroups - }) + }; } getChildContext () { diff --git a/src/components/layers/LayerList.jsx b/src/components/layers/LayerList.jsx index 83ebe333..dcabe6b9 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 5c651880..2d4a1f68 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 @@ -116,7 +114,7 @@ class LayerListItem extends React.Component { /> this.props.onLayerVisibilityToggle(this.props.layerId)} /> diff --git a/src/components/layers/LayerSourceBlock.jsx b/src/components/layers/LayerSourceBlock.jsx index 3773f73d..9a6b9e2c 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 804e93f4..15b48b5a 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 5d14d01f..2d0eacb3 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 ebc65b7b..6236243b 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) @@ -66,6 +68,7 @@ export default class MapboxGlMap extends React.Component { onDataChange: () => {}, onLayerSelect: () => {}, mapboxAccessToken: tokens.mapbox, + options: {}, } constructor(props) { @@ -80,21 +83,27 @@ export default class MapboxGlMap extends React.Component { } } - UNSAFE_componentWillReceiveProps(nextProps) { + updateMapFromProps(props) { + if(!IS_SUPPORTED) return; + if(!this.state.map) return - const metadata = nextProps.mapStyle.metadata || {} + const metadata = props.mapStyle.metadata || {} MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox - if(!nextProps.inspectModeEnabled) { + if(!props.inspectModeEnabled) { //Mapbox GL now does diffing natively so we don't need to calculate //the necessary operations ourselves! - this.state.map.setStyle(nextProps.mapStyle, { diff: true}) + this.state.map.setStyle(props.mapStyle, { diff: true}) } } componentDidUpdate(prevProps) { + if(!IS_SUPPORTED) return; + const map = this.state.map; + this.updateMapFromProps(this.props); + if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) { this.state.inspect.toggleInspector() } @@ -102,10 +111,15 @@ export default class MapboxGlMap extends React.Component { this.state.inspect.render() } - map.showTileBoundaries = this.props.options.showTileBoundaries; + if (map) { + 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, @@ -116,6 +130,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'); @@ -161,9 +176,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 113781a5..fade6b23 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' @@ -29,10 +27,10 @@ class OpenLayers3Map extends React.Component { const styleFunc = olms.apply(this.map, newMapStyle) } - UNSAFE_componentWillReceiveProps(nextProps) { + componentDidUpdate() { require.ensure(["ol", "ol-mapbox-style"], () => { if(!this.map) return - this.updateStyle(nextProps.mapStyle) + this.updateStyle(this.props.mapStyle) }) } diff --git a/src/components/modals/AddModal.jsx b/src/components/modals/AddModal.jsx index b42b736f..0cdac515 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 } @@ -235,10 +37,24 @@ class ExportModal extends React.Component { } downloadStyle() { - const blob = new Blob([styleSpec.format(stripAccessTokens(this.props.mapStyle))], {type: "application/json;charset=utf-8"}); + const tokenStyle = styleSpec.format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle))); + + const blob = new Blob([tokenStyle], {type: "application/json;charset=utf-8"}); saveAs(blob, this.props.mapStyle.id + ".json"); } + changeMetadataProperty(property, value) { + const changedStyle = { + ...this.props.mapStyle, + metadata: { + ...this.props.mapStyle.metadata, + [property]: value + } + } + this.props.onStyleChanged(changedStyle) + } + + render() { return Download a JSON style to your computer.

+ +

+ + + + + + + + + +

+ -
-

Save style

- -
} } diff --git a/src/components/modals/LoadingModal.jsx b/src/components/modals/LoadingModal.jsx index 42b8ecaf..f4ef6bb3 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 d9df74c1..5d9901fa 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 3f8790dc..a79d8ee6 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, @@ -86,7 +82,7 @@ class SettingsModal extends React.Component { /> - + + + + + Add one of the publicly available sources to your style.

-
+
{tilesetOptions}
+

+ Note: Some of the tilesets are not optimised for online use, and as a result the file sizes of the tiles can be quite large (heavy) for online vector rendering. Please review any tilesets before use. +

diff --git a/src/components/modals/SurveyModal.jsx b/src/components/modals/SurveyModal.jsx new file mode 100644 index 00000000..7f10bc32 --- /dev/null +++ b/src/components/modals/SurveyModal.jsx @@ -0,0 +1,39 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import Button from '../Button' +import Modal from './Modal' + +import logoImage from 'maputnik-design/logos/logo-color.svg' + +class SurveyModal extends React.Component { + static propTypes = { + isOpen: PropTypes.bool.isRequired, + onOpenToggle: PropTypes.func.isRequired, + } + + onClick = () => { + window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank'); + + this.props.onOpenToggle(); + } + + render() { + return +
+ +

You + Maputnik = Maputnik better for you

+

We don’t track you, so we don’t know how you use Maputnik. Help us make Maputnik better for you by completing a 7–minute survey carried out by our contributing designer.

+ +

It takes 7 minutes, tops! Every question is optional.

+
+
+ } +} + +export default SurveyModal diff --git a/src/config/layout.json b/src/config/layout.json index b8c928c6..d5b3a0dc 100644 --- a/src/config/layout.json +++ b/src/config/layout.json @@ -193,7 +193,8 @@ "raster-brightness-max", "raster-saturation", "raster-contrast", - "raster-fade-duration" + "raster-fade-duration", + "raster-resampling" ] } ] diff --git a/src/config/styles.json b/src/config/styles.json index 1359febf..45f07ebc 100644 --- a/src/config/styles.json +++ b/src/config/styles.json @@ -26,31 +26,13 @@ { "id": "osm-liberty", "title": "OSM Liberty", - "url": "https://rawgit.com/lukasmartinelli/osm-liberty/gh-pages/style.json", - "thumbnail": "https://cdn.rawgit.com/lukasmartinelli/osm-liberty/gh-pages/thumbnail.png" + "url": "https://rawgit.com/maputnik/osm-liberty/gh-pages/style.json", + "thumbnail": "https://cdn.rawgit.com/maputnik/osm-liberty/gh-pages/thumbnail.png" }, { "id": "empty-style", "title": "Empty Style", "url": "https://rawgit.com/maputnik/editor/master/src/config/empty-style.json", "thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAQAAAAHDYbIAAAAEUlEQVR42mP8/58BDhiJ4wAA974H/U5Xe1oAAAAASUVORK5CYII=" - }, - { - "id": "mapbox-satellite", - "title": "Mapbox Satellite", - "url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/satellite-v9.json", - "thumbnail": "https://maputnik.github.io/thumbnails/mapbox-satellite.png" - }, - { - "id": "mapbox-bright", - "title": "Mapbox Bright", - "url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/bright-v9.json", - "thumbnail": "https://maputnik.github.io/thumbnails/mapbox-bright.png" - }, - { - "id": "mapbox-basic", - "title": "Mapbox Basic", - "url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/basic-v9.json", - "thumbnail": "https://maputnik.github.io/thumbnails/mapbox-basic.png" } ] diff --git a/src/config/tilesets.json b/src/config/tilesets.json index ee321ce5..11507c8e 100644 --- a/src/config/tilesets.json +++ b/src/config/tilesets.json @@ -1,12 +1,17 @@ { - "mapbox-streets": { - "type": "vector", - "url": "mapbox://mapbox.mapbox-streets-v7", - "title": "Mapbox Streets" - }, "openmaptiles": { "type": "vector", "url": "https://free.tilehosting.com/data/v3.json?key={key}", "title": "OpenMapTiles" + }, + "thunderforest_transport": { + "type": "vector", + "url": "https://tile.thunderforest.com/thunderforest.transport-v1.json?apikey={key}", + "title": "Thunderforest Transport (heavy)" + }, + "thunderforest_outdoors": { + "type": "vector", + "url": "https://tile.thunderforest.com/thunderforest.outdoors-v1.json?apikey={key}", + "title": "Thunderforest Outdoors (heavy)" } } diff --git a/src/config/tokens.json b/src/config/tokens.json index 9676c74e..d08e6383 100644 --- a/src/config/tokens.json +++ b/src/config/tokens.json @@ -1,4 +1,5 @@ { "mapbox": "pk.eyJ1IjoibW9yZ2Vua2FmZmVlIiwiYSI6ImNpeHJmNXNmZTAwNHIycXBid2NqdTJibjMifQ.Dv1-GDpTWi0NP6xW9Fct1w", - "openmaptiles": "Og58UhhtiiTaLVlPtPgs" + "openmaptiles": "Og58UhhtiiTaLVlPtPgs", + "thunderforest": "b71f7f0ba4064f5eb9e903859a9cf5c6" } diff --git a/src/libs/accessibility.js b/src/libs/accessibility.js index 095e9538..23c13396 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 91fa7e23..aa114b22 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 f9dc1997..84d9211b 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 deac1999..2e1fb243 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 0e786529..4caa3571 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,18 +53,28 @@ function indexOfLayer(layers, layerId) { return null } -function replaceAccessToken(mapStyle, opts={}) { - const omtSource = mapStyle.sources.openmaptiles - if(!omtSource) return mapStyle - if(!omtSource.hasOwnProperty("url")) return mapStyle +function getAccessToken(sourceName, mapStyle, opts) { + if(sourceName === "thunderforest_transport" || sourceName === "thunderforest_outdoors") { + sourceName = "thunderforest" + } const metadata = mapStyle.metadata || {} - let accessToken = metadata['maputnik:openmaptiles_access_token']; + let accessToken = metadata[`maputnik:${sourceName}_access_token`] if(opts.allowFallback && !accessToken) { - accessToken = tokens.openmaptiles; + accessToken = tokens[sourceName] } + return accessToken; +} + +function replaceSourceAccessToken(mapStyle, sourceName, opts={}) { + const source = mapStyle.sources[sourceName] + if(!source) return mapStyle + if(!source.hasOwnProperty("url")) return mapStyle + + const accessToken = getAccessToken(sourceName, mapStyle, opts) + if(!accessToken) { // Early exit. return mapStyle; @@ -73,16 +82,34 @@ function replaceAccessToken(mapStyle, opts={}) { const changedSources = { ...mapStyle.sources, - openmaptiles: { - ...omtSource, - url: omtSource.url.replace('{key}', accessToken) + [sourceName]: { + ...source, + url: source.url.replace('{key}', accessToken) } } const changedStyle = { ...mapStyle, - glyphs: mapStyle.glyphs ? mapStyle.glyphs.replace('{key}', accessToken) : mapStyle.glyphs, sources: changedSources } + return changedStyle +} + +function replaceAccessTokens(mapStyle, opts={}) { + let changedStyle = mapStyle + + Object.keys(mapStyle.sources).forEach((sourceName) => { + changedStyle = replaceSourceAccessToken(changedStyle, sourceName, opts); + }) + + if (mapStyle.glyphs && mapStyle.glyphs.match(/\.tilehosting\.com/)) { + const newAccessToken = getAccessToken("openmaptiles", mapStyle, opts); + if (newAccessToken) { + changedStyle = { + ...changedStyle, + glyphs: mapStyle.glyphs.replace('{key}', newAccessToken) + } + } + } return changedStyle } @@ -92,5 +119,5 @@ export default { emptyStyle, indexOfLayer, generateId, - replaceAccessToken, + replaceAccessTokens, } diff --git a/src/libs/stylestore.js b/src/libs/stylestore.js index c921d5c3..17499bde 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 93c550a9..20b7aa3b 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/_base.scss b/src/styles/_base.scss index ce411725..e35f6611 100644 --- a/src/styles/_base.scss +++ b/src/styles/_base.scss @@ -18,6 +18,11 @@ html { box-sizing: border-box; } +body { + // The UI is 100% height so prevent bounce scroll on OSX + overflow: hidden; +} + *, *::before, *::after { diff --git a/src/styles/_components.scss b/src/styles/_components.scss index 26a69223..64afc54b 100644 --- a/src/styles/_components.scss +++ b/src/styles/_components.scss @@ -1,11 +1,31 @@ // MAP -.maputnik-map { +.maputnik-map__container { + display: flex; position: fixed !important; top: $toolbar-height + $toolbar-offset; right: 0; bottom: 0; height: calc(100% - #{$toolbar-height + $toolbar-offset}); - width: 75%; + width: calc( + 100% + - 200px /* layer list */ + - 350px /* layer editor */ + ); + + &--error { + align-items: center; + justify-content: center; + } + + &__error-message { + margin: 16px; + text-align: center; + } +} + +.maputnik-map__map { + width: 100%; + height: 100%; } // DOC LABEL @@ -56,6 +76,7 @@ border-width: 0; border-radius: 2px; box-sizing: border-box; + text-decoration: none; &:hover { background-color: lighten($color-midgray, 12); @@ -70,6 +91,20 @@ font-size: $font-size-5; } +.maputnik-wide-button { + padding: $margin-2 $margin-3; +} + +.maputnik-green-button { + background-color: $color-green; + color: $color-black; +} + +.maputnik-white-button { + background-color: $color-white; + color: $color-black; +} + .maputnik-icon-button { background-color: transparent; diff --git a/src/styles/_layout.scss b/src/styles/_layout.scss index 4857e560..25299b27 100644 --- a/src/styles/_layout.scss +++ b/src/styles/_layout.scss @@ -1,6 +1,6 @@ //SCROLLING .maputnik-scroll-container { - overflow-x: visible; + overflow-x: hidden; overflow-y: scroll; bottom: 0; left: 0; diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index c54016ad..deada7f1 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -125,6 +125,10 @@ } //SOURCE MODAL +.maputnik-public-sources { + margin-bottom: 1.5%; +} + .maputnik-public-source { vertical-align: top; margin-top: 1.5%; @@ -150,6 +154,7 @@ .maputnik-public-source-id { font-weight: 400; + text-align: left; } .maputnik-active-source-type-editor { @@ -240,3 +245,21 @@ margin-bottom: 4px; } } + +.maputnik-modal-survey { + width: 372px; +} + +.maputnik-modal-survey__logo { + display: block; + margin: 0 auto; +} + +.maputnik-modal-survey__description { + line-height: 1.5; +} + +.maputnik-modal-survey__footnote { + color: $color-green; + margin-top: 16px; +} diff --git a/src/styles/_react-codemirror.scss b/src/styles/_react-codemirror.scss new file mode 100644 index 00000000..c054ca2f --- /dev/null +++ b/src/styles/_react-codemirror.scss @@ -0,0 +1,3 @@ +.react-codemirror2 { + max-width: 100%; +} diff --git a/src/styles/_react-collapse.scss b/src/styles/_react-collapse.scss index ab81c170..3ee0cae7 100644 --- a/src/styles/_react-collapse.scss +++ b/src/styles/_react-collapse.scss @@ -1,6 +1,7 @@ // See .react-collapse-container { display: flex; + max-width: 100%; > * { flex: 1; diff --git a/src/styles/_toolbar.scss b/src/styles/_toolbar.scss index 75993dd1..70ae9861 100644 --- a/src/styles/_toolbar.scss +++ b/src/styles/_toolbar.scss @@ -57,6 +57,29 @@ } } +.maputnik-toolbar-link--highlighted { + line-height: 1; + padding: $margin-2 $margin-3; + + .maputnik-toolbar-link-wrapper { + background-color: $color-white; + border-radius: 2px; + padding: $margin-2; + margin-top: $margin-1; + color: $color-black; + display: block; + } + + &:hover { + background-color: $color-black; + } + + &:hover .maputnik-toolbar-link-wrapper { + background-color: lighten($color-midgray, 12); + color: $color-white; + } +} + .maputnik-toolbar-version { font-size: 10px; margin-left: 4px; diff --git a/src/styles/index.scss b/src/styles/index.scss index d72c22ff..51766f13 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -4,6 +4,7 @@ $color-midgray: #36383e; $color-lowgray: #8e8e8e; $color-white: #f0f0f0; $color-red: #cf4a4a; +$color-green: #53b972; $margin-1: 3px; $margin-2: 5px; $margin-3: 10px; @@ -37,6 +38,7 @@ $toolbar-offset: 0; @import 'popup'; @import 'map'; @import 'react-collapse'; +@import 'react-codemirror'; /** * Hacks for webdriverio isVisibleWithinViewport diff --git a/test/functional/history/index.js b/test/functional/history/index.js index 2cd5b0e6..e788e7e4 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 7f9b5322..3c9f5ec6 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"); @@ -13,6 +11,9 @@ describe('maputnik', function() { "geojson:example", "raster:raster" ])); + browser.execute(function() { + localStorage.setItem("survey", true); + }); browser.waitForExist(".maputnik-toolbar-link"); browser.flushReactUpdates(); }); diff --git a/test/functional/map/index.js b/test/functional/map/index.js index 498c0a1d..2df53934 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/modals/index.js b/test/functional/modals/index.js index 9f262270..d0996aac 100644 --- a/test/functional/modals/index.js +++ b/test/functional/modals/index.js @@ -171,7 +171,7 @@ describe("modals", function() { assert.equal(styleObj.metadata["maputnik:openmaptiles_access_token"], apiKey); }) - it("style renderer", function() { + it.skip("style renderer", function() { var selector = wd.$("modal-settings.maputnik:renderer"); browser.selectByValue(selector, "ol3"); browser.click(wd.$("modal-settings.name")) diff --git a/test/functional/screenshots/index.js b/test/functional/screenshots/index.js index 9c5634c8..a6d6d2aa 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"); diff --git a/test/functional/util/webdriverio-ext.js b/test/functional/util/webdriverio-ext.js index ee35b486..67f54f5c 100644 --- a/test/functional/util/webdriverio-ext.js +++ b/test/functional/util/webdriverio-ext.js @@ -46,10 +46,10 @@ try { browser.addCommand('flushReactUpdates', function() { browser.executeAsync(function(done) { // For any events to propogate - setImmediate(function() { + setTimeout(function() { // For the DOM to be updated. - setImmediate(done); - }) + setTimeout(done, 0); + }, 0) }) })