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.json b/package.json index 317bfc2..4aae8d0 100644 --- a/package.json +++ b/package.json @@ -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": { @@ -135,7 +129,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 +141,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 e317f92..c1f3a9a 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' @@ -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) @@ -187,14 +188,33 @@ export default class App extends React.Component { }) } + handleKeyPress(e) { + if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) { + if(e.metaKey && e.shiftKey && e.keyCode === 90) { + console.log("redo"); + this.onRedo(e); + } + else if(e.metaKey && e.keyCode === 90) { + console.log("undo"); + 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) { @@ -371,7 +391,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(); }) @@ -423,12 +445,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 = {}; diff --git a/src/components/modals/ExportModal.jsx b/src/components/modals/ExportModal.jsx index f7d997a..394b168 100644 --- a/src/components/modals/ExportModal.jsx +++ b/src/components/modals/ExportModal.jsx @@ -11,206 +11,8 @@ import Modal from './Modal' import MdFileDownload from 'react-icons/lib/md/file-download' import TiClipboard from 'react-icons/lib/ti/clipboard' import style from '../../libs/style' -import GitHub from 'github-api' -import { CopyToClipboard } from 'react-copy-to-clipboard' -class Gist extends React.Component { - static propTypes = { - mapStyle: PropTypes.object.isRequired, - onStyleChanged: PropTypes.func.isRequired, - } - - constructor(props) { - super(props); - this.state = { - preview: false, - public: false, - saving: false, - latestGist: null, - } - } - - UNSAFE_componentWillReceiveProps(nextProps) { - this.setState({ - ...this.state, - preview: !!(nextProps.mapStyle.metadata || {})['maputnik:openmaptiles_access_token'] - }) - } - - onSave() { - this.setState({ - ...this.state, - saving: true - }); - - const preview = this.state.preview; - - const mapboxToken = (this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']; - - const mapStyleStr = preview ? - styleSpec.format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle))) : - styleSpec.format(stripAccessTokens(this.props.mapStyle)); - const styleTitle = this.props.mapStyle.name || 'Style'; - const htmlStr = ` - - - - - - `+styleTitle+` Preview - - - - - -
- - - -` - const files = { - "style.json": { - content: mapStyleStr - } - } - if(preview) { - files["index.html"] = { - content: htmlStr - } - } - const gh = new GitHub(); - let gist = gh.getGist(); // not a gist yet - gist.create({ - public: this.state.public, - description: styleTitle, - files: files - }).then(function({data}) { - return gist.read(); - }).then(function({data}) { - this.setState({ - ...this.state, - latestGist: data, - saving: false, - }); - }.bind(this)); - } - - onPreviewChange(value) { - this.setState({ - ...this.state, - preview: value - }) - } - - onPublicChange(value) { - this.setState({ - ...this.state, - public: value - }) - } - - changeMetadataProperty(property, value) { - const changedStyle = { - ...this.props.mapStyle, - metadata: { - ...this.props.mapStyle.metadata, - [property]: value - } - } - this.props.onStyleChanged(changedStyle) - } - - renderPreviewLink() { - const gist = this.state.latestGist; - const user = gist.user || 'anonymous'; - const preview = !!gist.files['index.html']; - if(preview) { - return Preview,{' '} - } - return null; - } - - renderLatestGist() { - const gist = this.state.latestGist; - const saving = this.state.saving; - if(saving) { - return

Saving...

- } else if(gist) { - const user = gist.user || 'anonymous'; - const rawGistLink = "https://gist.githubusercontent.com/" + user + "/" + gist.id + "/raw/" + gist.history[0].version + "/style.json" - const maputnikStyleLink = "https://maputnik.github.io/editor/?style=" + rawGistLink - return
-

- Latest saved gist:{' '} - {this.renderPreviewLink(this)} - Source -

-

- - Share this style: - - -

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

Save style

- -
} } diff --git a/src/components/modals/OpenModal.jsx b/src/components/modals/OpenModal.jsx index d9df74c..68d1576 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' @@ -77,30 +76,36 @@ class OpenModal extends React.Component { 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 }) } diff --git a/src/components/modals/SettingsModal.jsx b/src/components/modals/SettingsModal.jsx index b7da0a1..f43d435 100644 --- a/src/components/modals/SettingsModal.jsx +++ b/src/components/modals/SettingsModal.jsx @@ -107,7 +107,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/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/stylestore.js b/src/libs/stylestore.js index c921d5c..b23c1e1 100644 --- a/src/libs/stylestore.js +++ b/src/libs/stylestore.js @@ -2,7 +2,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) + }) }