diff --git a/config/webpack.production.config.js b/config/webpack.production.config.js index d6dd670..e0d9a73 100644 --- a/config/webpack.production.config.js +++ b/config/webpack.production.config.js @@ -5,6 +5,7 @@ var loaders = require('./webpack.loaders'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var WebpackCleanupPlugin = require('webpack-cleanup-plugin'); +var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; var OUTPATH; if(process.env.CIRCLE_ARTIFACTS) { @@ -78,6 +79,14 @@ module.exports = { new HtmlWebpackPlugin({ template: './src/template.html', title: 'Maputnik' + }), + new BundleAnalyzerPlugin({ + analyzerMode: 'static', + defaultSizes: 'gzip', + openAnalyzer: false, + generateStatsFile: true, + reportFilename: 'bundle-stats.html', + statsFilename: 'bundle-stats.json', }) ] }; diff --git a/package-lock.json b/package-lock.json index a888670..5020cfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4431,6 +4431,12 @@ "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" }, + "filesize": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.5.11.tgz", + "integrity": "sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g==", + "dev": true + }, "fill-range": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", @@ -4878,6 +4884,15 @@ "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", "dev": true }, + "gzip-size": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", + "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=", + "dev": true, + "requires": { + "duplexer": "0.1.1" + } + }, "handle-thing": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", @@ -7869,6 +7884,12 @@ "mimic-fn": "1.1.0" } }, + "opener": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", + "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=", + "dev": true + }, "openlayers": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/openlayers/-/openlayers-4.4.2.tgz", @@ -12739,6 +12760,25 @@ } } }, + "webpack-bundle-analyzer": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.9.1.tgz", + "integrity": "sha512-a+UcvlsXvCmclNgfThT8PVyuJKd029By7CxkYEbNNCfs0Lqj9gagjkdv3S3MBvCIKBaUGYs8l4UpiVI0bFoh2Q==", + "dev": true, + "requires": { + "acorn": "5.2.1", + "chalk": "1.1.3", + "commander": "2.11.0", + "ejs": "2.5.7", + "express": "4.16.2", + "filesize": "3.5.11", + "gzip-size": "3.0.0", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "opener": "1.4.3", + "ws": "3.3.1" + } + }, "webpack-cleanup-plugin": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/webpack-cleanup-plugin/-/webpack-cleanup-plugin-0.5.1.tgz", diff --git a/package.json b/package.json index b81f7a3..06204f0 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "wdio-spec-reporter": "^0.1.2", "webdriverio": "^4.8.0", "webpack": "^3.8.1", + "webpack-bundle-analyzer": "^2.9.0", "webpack-cleanup-plugin": "^0.5.1", "webpack-dev-server": "^2.9.4" } diff --git a/src/components/App.jsx b/src/components/App.jsx index 2fb38d2..279f66d 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -67,12 +67,12 @@ export default class App extends React.Component { } this.layerWatcher = new LayerWatcher({ - onSourcesChange: v => this.setState({ sources: v }), onVectorLayersChange: v => this.setState({ vectorLayers: v }) }) } componentDidMount() { + this.fetchSources(); Mousetrap.bind(['ctrl+z'], this.onUndo.bind(this)); Mousetrap.bind(['ctrl+y'], this.onRedo.bind(this)); } @@ -126,6 +126,8 @@ export default class App extends React.Component { errors: errors.map(err => err.message) }) } + + this.fetchSources(); } onUndo() { @@ -182,11 +184,53 @@ export default class App extends React.Component { }) } + fetchSources() { + const sourceList = {}; + + for(let [key, val] of Object.entries(this.state.mapStyle.sources)) { + sourceList[key] = { + type: val.type, + layers: [] + }; + + if(val.type === "vector") { + const url = val.url; + fetch(url) + .then((response) => { + return response.json(); + }) + .then((json) => { + // Create new objects before setState + const sourceList = {...this.state.sources}; + sourceList[key] = {...sourceList[key]}; + + for(let layer of json.vector_layers) { + sourceList[key].layers.push(layer.id) + } + + this.setState({ + sources: sourceList + }); + }) + .catch((err) => { + console.error("Failed to process sources for '%s'", url, err); + }) + } + } + + // Note: Each source will be missing layers initially until the fetch is complete + this.setState({ + sources: sourceList + }) + + } + mapRenderer() { const mapProps = { mapStyle: style.replaceAccessToken(this.state.mapStyle), onDataChange: (e) => { this.layerWatcher.analyzeMap(e.map) + this.fetchSources(); }, } diff --git a/src/components/layers/LayerEditor.jsx b/src/components/layers/LayerEditor.jsx index 2184388..1d43bff 100644 --- a/src/components/layers/LayerEditor.jsx +++ b/src/components/layers/LayerEditor.jsx @@ -134,7 +134,7 @@ export default class LayerEditor extends React.Component { /> } {this.props.layer.type !== 'raster' && this.props.layer.type !== 'background' && this.changeProperty(null, 'source-layer', v)} /> diff --git a/src/components/modals/AddModal.jsx b/src/components/modals/AddModal.jsx index 24a4638..ab4a6de 100644 --- a/src/components/modals/AddModal.jsx +++ b/src/components/modals/AddModal.jsx @@ -56,18 +56,54 @@ class AddModal extends React.Component { } } - componentWillReceiveProps(nextProps) { - const sourceIds = Object.keys(nextProps.sources) - if(!this.state.source && sourceIds.length > 0) { + componentWillUpdate(nextProps, nextState) { + // Check if source is valid for new type + const availableSources = this.getSources(nextState.type); + if( + this.state.source !== "" + && availableSources.indexOf(this.state.source) < 0 + ) { this.setState({ - source: sourceIds[0], - 'source-layer': this.state['source-layer'] || (nextProps.sources[sourceIds[0]] || [])[0] - }) + source: "" + }); } } + getLayersForSource(source) { + const sourceObj = this.props.sources[source] || {}; + return sourceObj.layers || []; + } + + getSources(type) { + const sources = []; + + const types = { + vector: [ + "fill", + "line", + "symbol", + "circle", + "fill-extrusion" + ], + raster: [ + "raster" + ] + } + + for(let [key, val] of Object.entries(this.props.sources)) { + if(types[val.type].indexOf(type) > -1) { + sources.push(key); + } + } + + return sources; + } + render() { + const sources = this.getSources(this.state.type); + const layers = this.getLayersForSource(this.state.source); + return {this.state.type !== 'background' && this.setState({ source: v })} /> } {this.state.type !== 'background' && this.state.type !== 'raster' && this.setState({ 'source-layer': v })} />