diff --git a/CHANGELOG.md b/CHANGELOG.md index 554f76c19f..4c104be302 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,29 +14,58 @@ Each version should: Security to invite users to upgrade in case of vulnerabilities. Ref: http://keepachangelog.com/en/0.3.0/ --> + +## [2.3.1] - Aug 4 2020 + +- [Bug] fix tooltip config, add boolean formatter (#1216) +- [Enhancement] Geocoder interaction improvements (#1214) +- [Enhancement] add options.autoCreateLayers to addDataToMap (#1215) +- [Bug] Hide BottomWidgetContainer nothing to render (#1213) +- [Enhancement] Cleanup unused babel plugins (#1211) +- [Bug] fix file handler row parsing to support single geojson feature (#1212) +- [Enhancement] Add KeplerGl.onDeckInitialized callback (#1193) +- [Enhancement] Render geocode in readOnly mode (#1177) +- [Feat] pass initialUiState to prop (#1187) +- [Docs] Fix `replace-component` Readme (#1207) +- [Jupyter] Convert to gdf to a dataframe instead of a copy (#1201) +- New image export approach (#1199) +- Add prop to disable file extension checking (#1195) +- Load: extract extensions from loader objects (#1194) +- Add `visState.loaders` to let app inject a list of loaders.gl loaders. (#1192) +- Enable modal prop types (#1190) +- Enable modal types (#1189) +- Add types to top-level KeplerGl component (#1188) +- Add typescript types for upload modal and components (#1185) +- Add types for composer helpers (#1186) +- [Feat] add zoom to coordinate tooltip (#1179) +- [Enhancement] export more layer configurator components (#1176) +- [Bug/Enhancement] Pass PanelHeader props to the onClick handler of action items (#1181) +- [Bug] Fix import of the user guide link (#1182) +- [examples] update example version to 2.3.0 + ## [2.3.0] - July 6 2020 -[Enhancement] Improve animation sliders (#1157) -[Enhancement] speed control step to 0.001 (#1155) -[website] remove unused env, relax on package engines requirement (#1173) -[Feat] Pinned tooltip + Compare (#1132) -[Feat] Integration with loaders.gl 2.2 (#1156) -[Feat] Bump deck.gl and luma.gl to v8.2 (#1166) -[Chore] Bump websocket-extensions from 0.1.3 to 0.1.4 (#1138) -[Website] Add 2020 Survey (#1154) -[Bug] Tooltip formatting (#1129) -[Jupyter] Default centerMap to False so that zoom map state configurations are not (#1142) -[Enhancement] close modal when press escape key (#1134) -[Enhancement] Export time widget factories (#1133) -[Enhancement] filter invalid value when calculate trip layer domain (#1131) -[Feat] enable tooltip formatting in interaction config (#1102) -[Feat] Add type definition (#1116) -[RFC] table class RFC (#1109) -[Docs] adding missing bracket (#1094) -add side-panel inner class (#1113) -[Bug] add hexagon layer translation (#1114) -[Jupyter] fix gitignore add missing files (#1118) -[Jupyter] Publish keplergl jupyter 0.2.0 (#1110) -[Enhancement] fix attribution color, add kepler smaller font (#1092) +- [Enhancement] Improve animation sliders (#1157) +- [Enhancement] speed control step to 0.001 (#1155) +- [website] remove unused env, relax on package engines requirement (#1173) +- [Feat] Pinned tooltip + Compare (#1132) +- [Feat] Integration with loaders.gl 2.2 (#1156) +- [Feat] Bump deck.gl and luma.gl to v8.2 (#1166) +- [Chore] Bump websocket-extensions from 0.1.3 to 0.1.4 (#1138) +- [Website] Add 2020 Survey (#1154) +- [Bug] Tooltip formatting (#1129) +- [Jupyter] Default centerMap to False so that zoom map state configurations are not (#1142) +- [Enhancement] close modal when press escape key (#1134) +- [Enhancement] Export time widget factories (#1133) +- [Enhancement] filter invalid value when calculate trip layer domain (#1131) +- [Feat] enable tooltip formatting in interaction config (#1102) +- [Feat] Add type definition (#1116) +- [RFC] table class RFC (#1109) +- [Docs] adding missing bracket (#1094) +- add side-panel inner class (#1113) +- [Bug] add hexagon layer translation (#1114) +- [Jupyter] fix gitignore add missing files (#1118) +- [Jupyter] Publish keplergl jupyter 0.2.0 (#1110) +- [Enhancement] fix attribution color, add kepler smaller font (#1092) ## [2.2.0] - May 10 2020 - [Enhancement] Added Editor and FeatureActionPanel factories (#1093) diff --git a/README.md b/README.md index a65e03e344..cc6c2bc1ea 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,12 @@ const mapStyles = [ ]; ``` +#### `initialUiState` (object, optional) + +- Default: `undefined` + +Intial UI State applied to uiState reducer, value will be shallow merged with default [`INITIAL_UI_STATE`](https://docs.kepler.gl/docs/api-reference/reducers/ui-state#initial_ui_state) + ### 3. Dispatch custom actions to `keplerGl` reducer. One advantage of using the reducer over React component state to handle keplerGl state is the flexibility diff --git a/babel.config.js b/babel.config.js index 2c18a79590..56e72de59d 100644 --- a/babel.config.js +++ b/babel.config.js @@ -23,7 +23,6 @@ const KeplerPackage = require('./package'); const PRESETS = ['@babel/preset-env', '@babel/preset-react']; const PLUGINS = [ '@babel/plugin-transform-modules-commonjs', - ['@babel/plugin-proposal-decorators', {legacy: true}], '@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-export-namespace-from', [ diff --git a/bindings/kepler.gl-jupyter/keplergl/keplergl.py b/bindings/kepler.gl-jupyter/keplergl/keplergl.py index 12b3bfff07..fe1efa768b 100644 --- a/bindings/kepler.gl-jupyter/keplergl/keplergl.py +++ b/bindings/kepler.gl-jupyter/keplergl/keplergl.py @@ -34,11 +34,12 @@ def _gdf_to_dict(gdf): # will cause error if data frame has no geometry column name = gdf.geometry.name - copy = gdf.copy() - # convert it to wkt - copy[name] = copy.geometry.apply(lambda x: shapely.wkt.dumps(x)) + # convert geodataframe to dataframe + df = pd.DataFrame(gdf) + # convert geometry to wkt + df[name] = df.geometry.apply(lambda x: shapely.wkt.dumps(x)) - return _df_to_dict(copy) + return _df_to_dict(df) def _normalize_data(data): if isinstance(data, pd.DataFrame): diff --git a/examples/custom-reducer/.babelrc b/examples/custom-reducer/.babelrc index 46462e49b7..8d40400c0d 100644 --- a/examples/custom-reducer/.babelrc +++ b/examples/custom-reducer/.babelrc @@ -4,30 +4,6 @@ "@babel/preset-react" ], "plugins": [ - ["@babel/plugin-proposal-decorators", { "legacy": true }], - "@babel/plugin-proposal-class-properties", - ["@babel/transform-runtime", { - "regenerator": true - }], - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-json-strings", - "@babel/plugin-proposal-function-sent", - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - [ - "@babel/plugin-proposal-pipeline-operator", - { - "proposal": "minimal" - } - ], - "@babel/plugin-proposal-nullish-coalescing-operator", - "@babel/plugin-proposal-do-expressions", - "@babel/plugin-proposal-function-bind", - "@babel/plugin-transform-modules-commonjs" + "@babel/plugin-proposal-class-properties" ] } diff --git a/examples/custom-reducer/package.json b/examples/custom-reducer/package.json index c6a4c9af51..6908c5c594 100644 --- a/examples/custom-reducer/package.json +++ b/examples/custom-reducer/package.json @@ -5,7 +5,7 @@ }, "dependencies": { "global": "^4.3.0", - "kepler.gl": "^2.3.0", + "kepler.gl": "^2.3.1", "react": "^16.8.4", "react-dom": "^16.8.4", "react-palm": "^3.1.2", @@ -17,33 +17,13 @@ "devDependencies": { "@babel/core": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.3.0", - "@babel/plugin-proposal-decorators": "^7.3.0", - "@babel/plugin-proposal-do-expressions": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-export-namespace-from": "^7.2.0", - "@babel/plugin-proposal-function-bind": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-proposal-pipeline-operator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/plugin-transform-runtime": "^7.0.0", "@babel/preset-env": "^7.0.0", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.0", - "babel-plugin-module-resolver": "^3.0.0", - "babel-plugin-transform-builtin-extend": "^1.1.0", "webpack": "^4.29.0", "webpack-cli": "^3.2.1", "webpack-dev-middleware": "^3.5.1", "webpack-dev-server": "^3.1.14", - "webpack-hot-middleware": "^2.24.3", - "webpack-stats-plugin": "^0.2.1" + "webpack-hot-middleware": "^2.24.3" } } \ No newline at end of file diff --git a/examples/custom-reducer/src/store.js b/examples/custom-reducer/src/store.js index e1ac422352..1248c34922 100644 --- a/examples/custom-reducer/src/store.js +++ b/examples/custom-reducer/src/store.js @@ -19,7 +19,7 @@ // THE SOFTWARE. import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; -import keplerGlReducer from 'kepler.gl/reducers'; +import keplerGlReducer, {uiStateUpdaters} from 'kepler.gl/reducers'; import {enhanceReduxMiddleware} from 'kepler.gl/middleware'; import appReducer from './app-reducer'; import window from 'global/window'; @@ -32,6 +32,7 @@ const customizedKeplerGlReducer = keplerGlReducer // customize which map control button to show mapControls: { + ...uiStateUpdaters.DEFAULT_MAP_CONTROLS, visibleLayers: { show: false }, diff --git a/examples/custom-reducer/webpack.config.js b/examples/custom-reducer/webpack.config.js index b6b0aa5434..84245f8c8b 100644 --- a/examples/custom-reducer/webpack.config.js +++ b/examples/custom-reducer/webpack.config.js @@ -46,12 +46,6 @@ const CONFIG = { loader: 'babel-loader', include: join(__dirname, 'src'), exclude: [/node_modules/] - }, - { - // The example has some JSON data - test: /\.json$/, - loader: 'json-loader', - exclude: [/node_modules/] } ] }, diff --git a/examples/custom-theme/.babelrc b/examples/custom-theme/.babelrc index 46462e49b7..4f06b0cd37 100644 --- a/examples/custom-theme/.babelrc +++ b/examples/custom-theme/.babelrc @@ -2,32 +2,5 @@ "presets": [ "@babel/preset-env", "@babel/preset-react" - ], - "plugins": [ - ["@babel/plugin-proposal-decorators", { "legacy": true }], - "@babel/plugin-proposal-class-properties", - ["@babel/transform-runtime", { - "regenerator": true - }], - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-json-strings", - "@babel/plugin-proposal-function-sent", - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - [ - "@babel/plugin-proposal-pipeline-operator", - { - "proposal": "minimal" - } - ], - "@babel/plugin-proposal-nullish-coalescing-operator", - "@babel/plugin-proposal-do-expressions", - "@babel/plugin-proposal-function-bind", - "@babel/plugin-transform-modules-commonjs" ] } diff --git a/examples/custom-theme/package.json b/examples/custom-theme/package.json index 36836a281f..7ac6dce37f 100644 --- a/examples/custom-theme/package.json +++ b/examples/custom-theme/package.json @@ -5,7 +5,7 @@ }, "dependencies": { "global": "^4.3.0", - "kepler.gl": "^2.3.0", + "kepler.gl": "^2.3.1", "react": "^16.8.4", "react-dom": "^16.8.4", "react-palm": "^3.1.2", @@ -17,34 +17,13 @@ "devDependencies": { "@babel/core": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.3.0", - "@babel/plugin-proposal-decorators": "^7.3.0", - "@babel/plugin-proposal-do-expressions": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-export-namespace-from": "^7.2.0", - "@babel/plugin-proposal-function-bind": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-proposal-pipeline-operator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/plugin-transform-runtime": "^7.0.0", "@babel/preset-env": "^7.0.0", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.0", - "babel-plugin-inline-json-import": "^0.2.1", - "babel-plugin-module-resolver": "^3.0.0", - "babel-plugin-transform-builtin-extend": "^1.1.0", "webpack": "^4.29.0", "webpack-cli": "^3.2.1", "webpack-dev-middleware": "^3.5.1", "webpack-dev-server": "^3.1.14", - "webpack-hot-middleware": "^2.24.3", - "webpack-stats-plugin": "^0.2.1" + "webpack-hot-middleware": "^2.24.3" } } \ No newline at end of file diff --git a/examples/custom-theme/webpack.config.js b/examples/custom-theme/webpack.config.js index ec41099371..8fe27fc723 100644 --- a/examples/custom-theme/webpack.config.js +++ b/examples/custom-theme/webpack.config.js @@ -46,12 +46,6 @@ const CONFIG = { loader: 'babel-loader', include: join(__dirname, 'src'), exclude: [/node_modules/] - }, - { - // The example has some JSON data - test: /\.json$/, - loader: 'json-loader', - exclude: [/node_modules/] } ] }, @@ -60,13 +54,8 @@ const CONFIG = { fs: 'empty' }, - // to support browser history api and remove the '#' sign - devServer: { - historyApiFallback: true - }, - // Optional: Enables reading mapbox and dropbox client token from environment variable - plugins: [new webpack.EnvironmentPlugin(['MapboxAccessToken', 'DropboxClientId'])] + plugins: [new webpack.EnvironmentPlugin(['MapboxAccessToken'])] }; // This line enables bundling against src in this repo rather than installed deck.gl module diff --git a/examples/demo-app/.babelrc b/examples/demo-app/.babelrc index fba6c8f8e5..8d40400c0d 100644 --- a/examples/demo-app/.babelrc +++ b/examples/demo-app/.babelrc @@ -4,30 +4,6 @@ "@babel/preset-react" ], "plugins": [ - ["@babel/plugin-proposal-decorators", { "legacy": true }], - "@babel/plugin-proposal-class-properties", - ["@babel/transform-runtime", { - "regenerator": true - }], - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-json-strings", - "@babel/plugin-proposal-function-sent", - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - [ - "@babel/plugin-proposal-pipeline-operator", - { - "proposal": "minimal" - } - ], - "@babel/plugin-proposal-nullish-coalescing-operator", - "@babel/plugin-proposal-do-expressions", - "@babel/plugin-proposal-function-bind", - "@babel/plugin-transform-modules-commonjs", + "@babel/plugin-proposal-class-properties" ] } diff --git a/examples/demo-app/package.json b/examples/demo-app/package.json index 24ce8092a6..5cd78aa81c 100644 --- a/examples/demo-app/package.json +++ b/examples/demo-app/package.json @@ -14,7 +14,7 @@ "d3-request": "^1.0.6", "dropbox": "^4.0.12", "global": "^4.3.0", - "kepler.gl": "^2.3.0", + "kepler.gl": "^2.3.1", "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "react": "^16.8.4", @@ -34,33 +34,13 @@ "devDependencies": { "@babel/core": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.3.0", - "@babel/plugin-proposal-decorators": "^7.3.0", - "@babel/plugin-proposal-do-expressions": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-export-namespace-from": "^7.2.0", - "@babel/plugin-proposal-function-bind": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-proposal-pipeline-operator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/plugin-transform-runtime": "^7.0.0", "@babel/preset-env": "^7.0.0", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.0", - "babel-plugin-module-resolver": "^3.0.0", - "babel-plugin-transform-builtin-extend": "^1.1.0", "webpack": "^4.29.0", "webpack-cli": "^3.2.1", "webpack-dev-middleware": "^3.5.1", "webpack-dev-server": "^3.1.14", - "webpack-hot-middleware": "^2.24.3", - "webpack-stats-plugin": "^0.2.1" + "webpack-hot-middleware": "^2.24.3" } } \ No newline at end of file diff --git a/examples/demo-app/src/app.js b/examples/demo-app/src/app.js index 02aef9e127..e30a066cfc 100644 --- a/examples/demo-app/src/app.js +++ b/examples/demo-app/src/app.js @@ -34,8 +34,7 @@ import { loadRemoteMap, loadSampleConfigurations, onExportFileSuccess, - onLoadCloudMapSuccess, - onLoadCloudMapError + onLoadCloudMapSuccess } from './actions'; import {loadCloudMap} from 'kepler.gl/actions'; @@ -398,7 +397,6 @@ class App extends Component { cloudProviders={CLOUD_PROVIDERS} onExportToCloudSuccess={onExportFileSuccess} onLoadCloudMapSuccess={onLoadCloudMapSuccess} - onLoadCloudMapError={onLoadCloudMapError} /> )} diff --git a/examples/demo-app/src/components/map-control/map-control.js b/examples/demo-app/src/components/map-control/map-control.js index 039b827385..3458df6d40 100644 --- a/examples/demo-app/src/components/map-control/map-control.js +++ b/examples/demo-app/src/components/map-control/map-control.js @@ -27,7 +27,7 @@ const MapControl = MapControlFactory(); const StyledMapControlOverlay = styled.div` position: absolute; - top: 0px; + top: ${props => props.top}px; right: 0px; z-index: 1; `; @@ -177,9 +177,9 @@ function SampleMapPanel(props) { } const CustomMapControl = props => ( - + {!props.isExport && props.currentSample ? : null} - + ); diff --git a/examples/demo-app/webpack.config.js b/examples/demo-app/webpack.config.js index f527171943..47abc0643a 100644 --- a/examples/demo-app/webpack.config.js +++ b/examples/demo-app/webpack.config.js @@ -46,12 +46,6 @@ const CONFIG = { loader: 'babel-loader', include: [join(__dirname, 'src')], exclude: [/node_modules/] - }, - { - // The example has some JSON data - test: /\.json$/, - loader: 'json-loader', - exclude: [/node_modules/] } ] }, diff --git a/examples/node-app/.babelrc b/examples/node-app/.babelrc index 9ed9341f76..8d40400c0d 100644 --- a/examples/node-app/.babelrc +++ b/examples/node-app/.babelrc @@ -4,26 +4,6 @@ "@babel/preset-react" ], "plugins": [ - "@babel/plugin-proposal-class-properties", - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-json-strings", - "@babel/plugin-proposal-function-sent", - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - [ - "@babel/plugin-proposal-pipeline-operator", - { - "proposal": "minimal" - } - ], - "@babel/plugin-proposal-nullish-coalescing-operator", - "@babel/plugin-proposal-do-expressions", - "@babel/plugin-proposal-function-bind", - "@babel/plugin-transform-modules-commonjs" + "@babel/plugin-proposal-class-properties" ] } diff --git a/examples/node-app/package.json b/examples/node-app/package.json index 627fc406b8..d2c958a7b7 100644 --- a/examples/node-app/package.json +++ b/examples/node-app/package.json @@ -6,7 +6,7 @@ "dependencies": { "express": "^4.17.1", "global": "^4.3.0", - "kepler.gl": "^2.3.0", + "kepler.gl": "^2.3.1", "react": "^16.8.4", "react-dom": "^16.8.4", "react-palm": "^3.1.2", @@ -18,33 +18,13 @@ "devDependencies": { "@babel/core": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.3.0", - "@babel/plugin-proposal-decorators": "^7.3.0", - "@babel/plugin-proposal-do-expressions": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-export-namespace-from": "^7.2.0", - "@babel/plugin-proposal-function-bind": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-proposal-pipeline-operator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/plugin-transform-runtime": "^7.0.0", "@babel/preset-env": "^7.0.0", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.0", - "babel-plugin-module-resolver": "^3.0.0", - "babel-plugin-transform-builtin-extend": "^1.1.0", "webpack": "^4.29.0", "webpack-cli": "^3.2.1", "webpack-dev-middleware": "^3.7.0", "webpack-dev-server": "^3.1.14", - "webpack-hot-middleware": "^2.25.0", - "webpack-stats-plugin": "^0.2.1" + "webpack-hot-middleware": "^2.25.0" } } \ No newline at end of file diff --git a/examples/open-modal/.babelrc b/examples/open-modal/.babelrc index 46462e49b7..8d40400c0d 100644 --- a/examples/open-modal/.babelrc +++ b/examples/open-modal/.babelrc @@ -4,30 +4,6 @@ "@babel/preset-react" ], "plugins": [ - ["@babel/plugin-proposal-decorators", { "legacy": true }], - "@babel/plugin-proposal-class-properties", - ["@babel/transform-runtime", { - "regenerator": true - }], - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-json-strings", - "@babel/plugin-proposal-function-sent", - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - [ - "@babel/plugin-proposal-pipeline-operator", - { - "proposal": "minimal" - } - ], - "@babel/plugin-proposal-nullish-coalescing-operator", - "@babel/plugin-proposal-do-expressions", - "@babel/plugin-proposal-function-bind", - "@babel/plugin-transform-modules-commonjs" + "@babel/plugin-proposal-class-properties" ] } diff --git a/examples/open-modal/package.json b/examples/open-modal/package.json index 73a7e2760d..e2455bdcca 100644 --- a/examples/open-modal/package.json +++ b/examples/open-modal/package.json @@ -5,7 +5,7 @@ }, "dependencies": { "global": "^4.3.0", - "kepler.gl": "^2.3.0", + "kepler.gl": "^2.3.1", "react": "^16.8.4", "react-dom": "^16.8.4", "react-modal": "^3.1.10", @@ -18,33 +18,13 @@ "devDependencies": { "@babel/core": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.3.0", - "@babel/plugin-proposal-decorators": "^7.3.0", - "@babel/plugin-proposal-do-expressions": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-export-namespace-from": "^7.2.0", - "@babel/plugin-proposal-function-bind": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-proposal-pipeline-operator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/plugin-transform-runtime": "^7.0.0", "@babel/preset-env": "^7.0.0", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.0", - "babel-plugin-module-resolver": "^3.0.0", - "babel-plugin-transform-builtin-extend": "^1.1.0", "webpack": "^4.29.0", "webpack-cli": "^3.2.1", "webpack-dev-middleware": "^3.5.1", "webpack-dev-server": "^3.1.14", - "webpack-hot-middleware": "^2.24.3", - "webpack-stats-plugin": "^0.2.1" + "webpack-hot-middleware": "^2.24.3" } } \ No newline at end of file diff --git a/examples/open-modal/webpack.config.js b/examples/open-modal/webpack.config.js index b6b0aa5434..84245f8c8b 100644 --- a/examples/open-modal/webpack.config.js +++ b/examples/open-modal/webpack.config.js @@ -46,12 +46,6 @@ const CONFIG = { loader: 'babel-loader', include: join(__dirname, 'src'), exclude: [/node_modules/] - }, - { - // The example has some JSON data - test: /\.json$/, - loader: 'json-loader', - exclude: [/node_modules/] } ] }, diff --git a/examples/replace-component/.babelrc b/examples/replace-component/.babelrc index 46462e49b7..8d40400c0d 100644 --- a/examples/replace-component/.babelrc +++ b/examples/replace-component/.babelrc @@ -4,30 +4,6 @@ "@babel/preset-react" ], "plugins": [ - ["@babel/plugin-proposal-decorators", { "legacy": true }], - "@babel/plugin-proposal-class-properties", - ["@babel/transform-runtime", { - "regenerator": true - }], - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-json-strings", - "@babel/plugin-proposal-function-sent", - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - [ - "@babel/plugin-proposal-pipeline-operator", - { - "proposal": "minimal" - } - ], - "@babel/plugin-proposal-nullish-coalescing-operator", - "@babel/plugin-proposal-do-expressions", - "@babel/plugin-proposal-function-bind", - "@babel/plugin-transform-modules-commonjs" + "@babel/plugin-proposal-class-properties" ] } diff --git a/examples/replace-component/README.md b/examples/replace-component/README.md index 3f899ad1bc..49b5122b20 100644 --- a/examples/replace-component/README.md +++ b/examples/replace-component/README.md @@ -1,4 +1,4 @@ -# Open modal +# Replacing components Example showing how to replace kepler.gl default components using `injectComponents` method. diff --git a/examples/replace-component/package.json b/examples/replace-component/package.json index c6a4c9af51..6908c5c594 100644 --- a/examples/replace-component/package.json +++ b/examples/replace-component/package.json @@ -5,7 +5,7 @@ }, "dependencies": { "global": "^4.3.0", - "kepler.gl": "^2.3.0", + "kepler.gl": "^2.3.1", "react": "^16.8.4", "react-dom": "^16.8.4", "react-palm": "^3.1.2", @@ -17,33 +17,13 @@ "devDependencies": { "@babel/core": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.3.0", - "@babel/plugin-proposal-decorators": "^7.3.0", - "@babel/plugin-proposal-do-expressions": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-export-namespace-from": "^7.2.0", - "@babel/plugin-proposal-function-bind": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-proposal-pipeline-operator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/plugin-transform-runtime": "^7.0.0", "@babel/preset-env": "^7.0.0", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.0", - "babel-plugin-module-resolver": "^3.0.0", - "babel-plugin-transform-builtin-extend": "^1.1.0", "webpack": "^4.29.0", "webpack-cli": "^3.2.1", "webpack-dev-middleware": "^3.5.1", "webpack-dev-server": "^3.1.14", - "webpack-hot-middleware": "^2.24.3", - "webpack-stats-plugin": "^0.2.1" + "webpack-hot-middleware": "^2.24.3" } } \ No newline at end of file diff --git a/examples/replace-component/webpack.config.js b/examples/replace-component/webpack.config.js index 1a347651ad..84245f8c8b 100644 --- a/examples/replace-component/webpack.config.js +++ b/examples/replace-component/webpack.config.js @@ -39,11 +39,6 @@ const CONFIG = { devtool: 'source-map', - resolve: { - // Make src files outside of this dir resolve modules in our node_modules folder - modules: [resolve(__dirname, '.'), resolve(__dirname, 'node_modules'), 'node_modules'] - }, - module: { rules: [ { @@ -51,12 +46,6 @@ const CONFIG = { loader: 'babel-loader', include: join(__dirname, 'src'), exclude: [/node_modules/] - }, - { - // The example has some JSON data - test: /\.json$/, - loader: 'json-loader', - exclude: [/node_modules/] } ] }, diff --git a/examples/umd-client/index.html b/examples/umd-client/index.html index 8bed2d744a..3420de9f73 100644 --- a/examples/umd-client/index.html +++ b/examples/umd-client/index.html @@ -18,7 +18,7 @@ - + diff --git a/examples/webpack.config.local.js b/examples/webpack.config.local.js index f7f757e61c..dc81e5917a 100644 --- a/examples/webpack.config.local.js +++ b/examples/webpack.config.local.js @@ -168,34 +168,8 @@ function makeBabelRule(env, exampleDir) { options: { presets: ['@babel/preset-env', '@babel/preset-react'], plugins: [ - ['@babel/plugin-proposal-decorators', {legacy: true}], '@babel/plugin-proposal-class-properties', - [ - '@babel/transform-runtime', - { - regenerator: true - } - ], - '@babel/plugin-syntax-dynamic-import', - '@babel/plugin-syntax-import-meta', - '@babel/plugin-proposal-json-strings', - '@babel/plugin-proposal-function-sent', '@babel/plugin-proposal-export-namespace-from', - '@babel/plugin-proposal-numeric-separator', - '@babel/plugin-proposal-throw-expressions', - '@babel/plugin-proposal-export-default-from', - '@babel/plugin-proposal-logical-assignment-operators', - '@babel/plugin-proposal-optional-chaining', - [ - '@babel/plugin-proposal-pipeline-operator', - { - proposal: 'minimal' - } - ], - '@babel/plugin-proposal-nullish-coalescing-operator', - '@babel/plugin-proposal-do-expressions', - '@babel/plugin-proposal-function-bind', - '@babel/plugin-transform-modules-commonjs', [ 'module-resolver', { @@ -285,9 +259,7 @@ module.exports = (exampleConfig, exampleDir) => env => { 'probe.gl': results[3] })) .then(externals => { - let config = addLocalDevSettings(env, exampleConfig, exampleDir, externals); - config = addBabelSettings(env, config, exampleDir, externals); - - return config; + const config = addLocalDevSettings(env, exampleConfig, exampleDir, externals); + return addBabelSettings(env, config, exampleDir, externals); }); }; diff --git a/package.json b/package.json index 4a921f730e..71cff8196a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "kepler.gl", "author": "Shan He ", - "version": "2.3.0", + "version": "2.3.1", "description": "kepler.gl is a webgl based application to visualize large scale location data in the browser", "license": "MIT", "main": "dist/index.js", @@ -24,7 +24,7 @@ "fast-test": "npm run test-node && npm run test-browser", "test-browser": "NODE_ENV=test babel-tape-runner -r ./test/setup-browser-env.js ./test/browser/index.js | tap-spec", "test-node": "NODE_ENV=test babel-tape-runner ./test/node.js | tap-spec", - "test-browser-headless": "NODE_ENV=test node ./test/browser-drive.js", + "test-browser-drive": "NODE_ENV=test node ./test/browser-drive.js", "test-e2e": "NODE_ENV=test HEADLESS=true SLOWMO=true jest", "test": "npm run lint && npm run test-node && npm run test-browser", "cover": "nyc --reporter html --reporter cobertura --reporter json-summary npm test && nyc report", @@ -130,6 +130,7 @@ "lodash.uniqby": "^4.7.0", "lodash.xor": "^4.5.0", "long": "^4.0.0", + "mapbox": "^1.0.0-beta10", "mini-svg-data-uri": "^1.0.3", "moment": "^2.10.6", "pbf": "^3.1.0", @@ -141,7 +142,6 @@ "react-lifecycles-compat": "^3.0.4", "react-map-gl": "^5.0.3", "react-map-gl-draw": "0.14.8", - "react-mapbox-gl-geocoder": "^1.1.0", "react-markdown": "^4.0.6", "react-modal": "^3.8.1", "react-onclickoutside": "^6.7.1", @@ -168,22 +168,7 @@ "@babel/node": "^7.2.2", "@babel/parser": "^7.3.3", "@babel/plugin-proposal-class-properties": "^7.3.0", - "@babel/plugin-proposal-decorators": "^7.3.0", - "@babel/plugin-proposal-do-expressions": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", "@babel/plugin-proposal-export-namespace-from": "^7.2.0", - "@babel/plugin-proposal-function-bind": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-proposal-pipeline-operator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-namespace-from": "^7.2.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", "@babel/plugin-transform-modules-commonjs": "^7.2.0", "@babel/plugin-transform-runtime": "^7.0.0", "@babel/polyfill": "^7.0.0", diff --git a/src/actions/actions.d.ts b/src/actions/actions.d.ts index 0645d0b21d..ccc1df519e 100644 --- a/src/actions/actions.d.ts +++ b/src/actions/actions.d.ts @@ -23,6 +23,7 @@ import {ParsedConfig} from '../schemas'; import {RGBColor} from 'reducers/types'; import {Bounds} from 'reducers/map-state-updaters'; import {MapInfo} from 'reducers/vis-state-updaters'; +import {UiState} from 'reducers/ui-state-updaters'; /** * Input dataest parsed to addDataToMap @@ -51,6 +52,7 @@ export type AddDataToMapOptions = { centerMap?: boolean; readOnly?: boolean; keepExistingConfig?: boolean; + autoCreateLayers?: boolean; }; export type AddDataToMapPayload = { @@ -84,13 +86,10 @@ export type KeplerGlInitPayload = { mapboxApiAccessToken?: string; mapboxApiUrl?: string; mapStylesReplaceDefault?: boolean; + initialUiState?: Partial; }; -export function keplerGlInit(options?: { - mapboxApiAccessToken?: string; - mapboxApiUrl?: string; - mapStylesReplaceDefault?: boolean; -}): { +export function keplerGlInit(options?: KeplerGlInitPayload): { type: ActionTypes.INIT; payload: KeplerGlInitPayload; }; diff --git a/src/actions/actions.js b/src/actions/actions.js index 03d6d51d63..beb7683e6e 100644 --- a/src/actions/actions.js +++ b/src/actions/actions.js @@ -133,6 +133,7 @@ export const resetMapConfig = createAction(ActionTypes.RESET_MAP_CONFIG); * @param {boolean} options.readOnly `default: false` if `readOnly` is set to `true` * the left setting panel will be hidden * @param {boolean} options.keepExistingConfig whether to keep exiting layer filter and interaction config `default: false`. + * @param {boolean} options.autoCreateLayers whether to automatically create layers based on dataset columns `default: true`. * @public * @example * import {receiveMapConfig} from 'kepler.gl/actions'; @@ -153,17 +154,14 @@ export const receiveMapConfig = createAction(ActionTypes.RECEIVE_MAP_CONFIG, (co * @param payload.mapboxApiAccessToken - mapboxApiAccessToken to be saved to mapStyle reducer * @param payload.mapboxApiUrl - mapboxApiUrl to be saved to mapStyle reducer. * @param payload.mapStylesReplaceDefault - mapStylesReplaceDefault to be saved to mapStyle reducer + * @param payload.initialUiState - initial ui state * @type {typeof import('./actions').keplerGlInit} * @public */ export const keplerGlInit = createAction( ActionTypes.INIT, // @ts-ignore - ({mapboxApiAccessToken, mapboxApiUrl, mapStylesReplaceDefault} = {}) => ({ - mapboxApiAccessToken, - mapboxApiUrl, - mapStylesReplaceDefault - }) + payload => payload ); /** diff --git a/src/actions/identity-actions.d.ts b/src/actions/identity-actions.d.ts index 1d16c05380..5c0a625507 100644 --- a/src/actions/identity-actions.d.ts +++ b/src/actions/identity-actions.d.ts @@ -19,14 +19,16 @@ // THE SOFTWARE. import ActionTypes from 'constants/action-types'; +import {UiState} from 'reducers/ui-state-updaters'; export type RegisterEntryUpdaterAction = { payload: { id: string; - mint: boolean; - mapboxApiAccessToken: string; - mapboxApiUrl: string; - mapStylesReplaceDefault: boolean; + mint?: boolean; + mapboxApiAccessToken?: string; + mapboxApiUrl?: string; + mapStylesReplaceDefault?: boolean; + initialUiState?:Partial; }; }; diff --git a/src/actions/identity-actions.js b/src/actions/identity-actions.js index 2c6ba67a1e..cb45e4f8d9 100644 --- a/src/actions/identity-actions.js +++ b/src/actions/identity-actions.js @@ -27,25 +27,18 @@ import ActionTypes from 'constants/action-types'; * Note that if you dispatch actions such as adding data to a kepler.gl instance before the React component is mounted, the action will not be * performed. Instance reducer can only handle actions when it is instantiated. * @memberof rootActions - * @param {Object} payload - * @param {string} payload.id - ***required** The id of the instance - * @param {boolean} payload.mint - Whether to use a fresh empty state, when `mint: true` it will *always* load a fresh state when the component is re-mounted. + * @param payload + * @param payload.id - ***required** The id of the instance + * @param payload.mint - Whether to use a fresh empty state, when `mint: true` it will *always* load a fresh state when the component is re-mounted. * When `mint: false` it will register with existing instance state under the same `id`, when the component is unmounted then mounted again. Default: `true` - * @param {string} payload.mapboxApiAccessToken - mapboxApiAccessToken to be saved in `map-style` reducer. - * @param {string} payload.mapboxApiUrl - mapboxApiUrl to be saved in `map-style` reducer. - * @param {Boolean} payload.mapStylesReplaceDefault - mapStylesReplaceDefault to be saved in `map-style` reducer. + * @param payload.mapboxApiAccessToken - mapboxApiAccessToken to be saved in `map-style` reducer. + * @param payload.mapboxApiUrl - mapboxApiUrl to be saved in `map-style` reducer. + * @param payload.mapStylesReplaceDefault - mapStylesReplaceDefault to be saved in `map-style` reducer. + * @param payload.initialUiState - initial ui state + * @type {typeof import('./identity-actions').registerEntry} * @public */ -export const registerEntry = createAction( - ActionTypes.REGISTER_ENTRY, - ({id, mint, mapboxApiAccessToken, mapboxApiUrl, mapStylesReplaceDefault}) => ({ - id, - mint, - mapboxApiAccessToken, - mapboxApiUrl, - mapStylesReplaceDefault - }) -); +export const registerEntry = createAction(ActionTypes.REGISTER_ENTRY, payload => payload); /** * diff --git a/src/components/bottom-widget.js b/src/components/bottom-widget.js index 4a43612122..1b15d30fc7 100644 --- a/src/components/bottom-widget.js +++ b/src/components/bottom-widget.js @@ -84,19 +84,25 @@ export default function BottomWidgetFactory(TimeWidget, AnimationControl) { const readToAnimation = Array.isArray(animationConfig.domain) && animationConfig.currentTime; // if animation control is showing, hide time display in time slider const showFloatingTimeDisplay = !animatedLayer.length; + const showAnimationControl = animatedLayer.length && readToAnimation; + const showTimeWidget = enlargedFilterIdx > -1 && Object.keys(datasets).length > 0; + // The bottom widget can hide clickable elements so do not render it if not needed + if (!showAnimationControl && !showTimeWidget) { + return null; + } return ( - {animatedLayer.length && readToAnimation ? ( + {showAnimationControl ? ( ) : null} - {enlargedFilterIdx > -1 && Object.keys(datasets).length > 0 ? ( + {showTimeWidget ? ( { const FieldListItem = ({value, displayOption = defaultDisplayOption}) => ( -
+
{showToken ? ( @@ -113,16 +113,15 @@ class FieldSelector extends Component { showTokenSelector = props => props.showToken; selectedItemsSelector = createSelector(this.fieldsSelector, this.valueSelector, (fields, value) => - fields.filter(f => - Boolean( - toArray(value).find(d => { - if (!notNullorUndefined(d)) { - return false; - } - return d.name ? d.name === defaultDisplayOption(f) : d === defaultDisplayOption(f); - }) + toArray(value) + .map(d => + fields.find(f => + notNullorUndefined(d) && d.name + ? d.name === defaultDisplayOption(f) + : d === defaultDisplayOption(f) + ) ) - ) + .filter(d => d) ); fieldOptionsSelector = createSelector( @@ -146,7 +145,7 @@ class FieldSelector extends Component { getOptionValue={d => d} closeOnSelect={this.props.closeOnSelect} displayOption={defaultDisplayOption} - filterOption={'id'} + filterOption="name" fixedOptions={this.props.suggested} inputTheme={this.props.inputTheme} isError={this.props.error} diff --git a/src/components/common/item-selector/item-selector.js b/src/components/common/item-selector/item-selector.js index 7b5809c959..c78d4fb320 100644 --- a/src/components/common/item-selector/item-selector.js +++ b/src/components/common/item-selector/item-selector.js @@ -62,8 +62,10 @@ const DropdownWrapper = styled.div` z-index: ${props => props.theme.dropdownWrapperZ}; position: absolute; bottom: ${props => (props.placement === 'top' ? props.theme.inputBoxHeight : 'auto')}; - margin-top: ${props => (props.placement === 'bottom' ? '4px' : 'auto')}; - margin-bottom: ${props => (props.placement === 'top' ? '4px' : 'auto')}; + margin-top: ${props => + props.placement === 'bottom' ? `${props.theme.dropdownWapperMargin}px` : 'auto'}; + margin-bottom: ${props => + props.placement === 'top' ? `${props.theme.dropdownWapperMargin}px` : 'auto'}; `; class ItemSelector extends Component { @@ -172,7 +174,6 @@ class ItemSelector extends Component { if (this.props.multiSelect) { const items = uniqBy(previousSelected.concat(toArray(item)), getValue); - this.props.onChange(items); } else { this.props.onChange(getValue(item)); diff --git a/src/components/common/styled-components.js b/src/components/common/styled-components.js index 7074f58a13..b79e329173 100644 --- a/src/components/common/styled-components.js +++ b/src/components/common/styled-components.js @@ -462,9 +462,21 @@ export const StyledAttrbution = styled.div.attrs({ position: absolute; display: block; margin: 0 10px 2px; - z-index: 999; + z-index: 0; + + .attrition-logo { + display: flex; + font-size: 10px; + justify-content: flex-end; + align-items: center; + color: ${props => props.theme.labelColor}; + + a.mapboxgl-ctrl-logo { + width: 72px; + margin-left: 6px; + } + } a { - color: black; font-size: 10px; } `; diff --git a/src/components/container.js b/src/components/container.js index 283b17d300..a960535662 100644 --- a/src/components/container.js +++ b/src/components/container.js @@ -57,6 +57,7 @@ export function ContainerFactory(KeplerGl) { * @param {string} props.mapboxApiAccessToken - _required_ * @param {string} props.mapboxApiUrl - _optional_ * @param {Boolean} props.mapStylesReplaceDefault - _optional_ + * @param {object} props.initialUiState - _optional_ * You can create a free account at [www.mapbox.com](www.mapbox.com) and create a token at * [www.mapbox.com/account/access-tokens](www.mapbox.com/account/access-tokens) @@ -89,7 +90,15 @@ export function ContainerFactory(KeplerGl) { } componentDidMount() { - const {id, mint, mapboxApiAccessToken, mapboxApiUrl, mapStylesReplaceDefault} = this.props; + const { + id, + mint, + mapboxApiAccessToken, + mapboxApiUrl, + mapStylesReplaceDefault, + initialUiState + } = this.props; + // add a new entry to reducer this.props.dispatch( registerEntry({ @@ -97,7 +106,8 @@ export function ContainerFactory(KeplerGl) { mint, mapboxApiAccessToken, mapboxApiUrl, - mapStylesReplaceDefault + mapStylesReplaceDefault, + initialUiState }) ); } diff --git a/src/components/geocoder-panel.js b/src/components/geocoder-panel.js index c9e5a46348..c5db7afd3a 100644 --- a/src/components/geocoder-panel.js +++ b/src/components/geocoder-panel.js @@ -21,21 +21,26 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; -import {SidePanelSection, PanelLabel} from './common/styled-components'; -import {FormattedMessage} from 'react-intl'; -import Geocoder from 'react-mapbox-gl-geocoder'; import Processors from 'processors'; -import {fitBounds, addDataToMap, removeDataset} from 'actions'; +import {FlyToInterpolator} from '@deck.gl/core'; +import geoViewport from '@mapbox/geo-viewport'; +import KeplerGlSchema from 'schemas'; + +import Geocoder from './geocoder/geocoder'; +import { + GEOCODER_DATASET_NAME, + GEOCODER_LAYER_ID, + GEOCODER_GEO_OFFSET, + GEOCODER_ICON_COLOR, + GEOCODER_ICON_SIZE +} from 'constants/default-settings'; -const GEOCODER_DATASET_NAME = 'geocoder_dataset'; -const QUERY_PARAMS = {}; -const GEO_OFFSET = 0.1; const ICON_LAYER = { - id: 'geocoder_layer', + id: GEOCODER_LAYER_ID, type: 'icon', config: { label: 'Geocoder Layer', - color: [255, 0, 0], + color: GEOCODER_ICON_COLOR, dataId: GEOCODER_DATASET_NAME, columns: { lat: 'lt', @@ -46,68 +51,27 @@ const ICON_LAYER = { isVisible: true, hidden: true, visConfig: { - radius: 80 + radius: GEOCODER_ICON_SIZE } } }; -const GeocoderPanelContent = styled.div` - background-color: ${props => props.theme.panelBackground}; - padding: 0.5em 1em; - position: absolute; - top: 20px; - left: 50%; - margin-left: -20em; - width: 40em; - box-sizing: border-box; -`; - -const GeocoderWrapper = styled.div` - color: ${props => props.theme.textColor}; - font-size: ${props => props.theme.fontSize}; - - .react-geocoder { - position: relative; - } - - .react-geocoder input { - ${props => props.theme.secondaryInput}; - } - - .react-geocoder-results { - background-color: ${props => props.theme.panelBackground}; - position: absolute; - width: 43.5em; - } - - .react-geocoder-item { - ${props => props.theme.dropdownListItem}; - ${props => props.theme.textTruncate}; - } - - .remove-layer { - background: transparent; - border: none; - bottom: 28px; - color: ${props => props.theme.textColor}; - cursor: pointer; - display: inline; - font-size: 16px; - padding: 2px 8px; - position: absolute; - right: 16px; - - :hover, - :focus, - :active { - background: transparent !important; - border: none; - box-shadow: 0; - color: ${props => props.theme.textColor}; - opacity: 0.6; - outline: none; +const PARSED_CONFIG = KeplerGlSchema.parseSavedConfig({ + version: 'v1', + config: { + visState: { + layers: [ICON_LAYER] } } +}); + +const StyledGeocoderPanel = styled.div` + position: absolute; + top: ${props => props.theme.geocoderTop}px; + right: ${props => props.theme.geocoderRight}px; + width: ${props => (Number.isFinite(props.width) ? props.width : props.theme.geocoderWidth)}px; + box-shadow: ${props => props.theme.boxShadow}; + z-index: 100; `; function generateGeocoderDataset(lat, lon, text) { @@ -133,97 +97,88 @@ function isValid(key) { return /pk\..*\..*/.test(key); } -export class GeocoderPanel extends Component { - static propTypes = { - isGeocoderEnabled: PropTypes.bool.isRequired, - mapboxApiAccessToken: PropTypes.string.isRequired - }; - - state = { - selectedGeoItem: null - }; - - removeGeocoderDataset() { - this.props.dispatch(removeDataset(GEOCODER_DATASET_NAME)); - } +export default function GeocoderPanelFactory() { + class GeocoderPanel extends Component { + static propTypes = { + isGeocoderEnabled: PropTypes.bool.isRequired, + mapboxApiAccessToken: PropTypes.string.isRequired, + mapState: PropTypes.object.isRequired, + updateVisData: PropTypes.func.isRequired, + removeDataset: PropTypes.func.isRequired, + updateMap: PropTypes.func.isRequired, + + transitionDuration: PropTypes.number, + width: PropTypes.number + }; + + removeGeocoderDataset() { + this.props.removeDataset(GEOCODER_DATASET_NAME); + } - onSelected = (viewport = null, geoItem) => { - const { - center: [lon, lat], - text, - bbox - } = geoItem; - this.removeGeocoderDataset(); - this.props.dispatch( - addDataToMap({ - datasets: [generateGeocoderDataset(lat, lon, text)], - options: { + onSelected = (viewport = null, geoItem) => { + const { + center: [lon, lat], + text, + bbox + } = geoItem; + this.removeGeocoderDataset(); + this.props.updateVisData( + [generateGeocoderDataset(lat, lon, text)], + { keepExistingConfig: true }, - config: { - version: 'v1', - config: { - visState: { - layers: [ICON_LAYER] - } - } - } - }) - ); - this.props.dispatch( - fitBounds(bbox || [lon - GEO_OFFSET, lat - GEO_OFFSET, lon + GEO_OFFSET, lat + GEO_OFFSET]) - ); - this.setState({ - selectedGeoItem: geoItem - }); - }; + PARSED_CONFIG + ); + const bounds = bbox || [ + lon - GEOCODER_GEO_OFFSET, + lat - GEOCODER_GEO_OFFSET, + lon + GEOCODER_GEO_OFFSET, + lat + GEOCODER_GEO_OFFSET + ]; + const {center, zoom} = geoViewport.viewport(bounds, [ + this.props.mapState.width, + this.props.mapState.height + ]); + + this.props.updateMap({ + latitude: center[1], + longitude: center[0], + zoom, + pitch: 0, + bearing: 0, + transitionDuration: this.props.transitionDuration, + transitionInterpolator: new FlyToInterpolator() + }); + }; + + removeMarker = () => { + this.removeGeocoderDataset(); + }; + + render() { + const {isGeocoderEnabled, mapboxApiAccessToken, width} = this.props; + return ( + + {isValid(mapboxApiAccessToken) && ( + + )} + + ); + } + } - removeMarker = () => { - this.setState({ - selectedGeoItem: null - }); - this.removeGeocoderDataset(); + GeocoderPanel.defaultProps = { + transitionDuration: 3000 }; - render() { - const {isGeocoderEnabled, mapboxApiAccessToken} = this.props; - return ( - - - - - - - {isValid(mapboxApiAccessToken) && ( - - )} - {this.state.selectedGeoItem && ( - - )} - - - - ); - } -} - -export default function GeocoderPanelFactory() { return GeocoderPanel; } diff --git a/src/components/geocoder/geocoder.js b/src/components/geocoder/geocoder.js new file mode 100644 index 0000000000..d6a5c7ae11 --- /dev/null +++ b/src/components/geocoder/geocoder.js @@ -0,0 +1,235 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import React, {useCallback, useMemo, useState} from 'react'; +import styled from 'styled-components'; +import classnames from 'classnames'; +import MapboxClient from 'mapbox'; +import {injectIntl} from 'react-intl'; +import {WebMercatorViewport} from 'viewport-mercator-project'; +import KeyEvent from 'constants/keyevent'; +import {Input} from 'components/common/styled-components'; +import {Search, Delete} from 'components/common/icons'; + +let debounceTimeout = null; + +const StyledContainer = styled.div` + position: relative; + color: ${props => props.theme.textColor}; + + .geocoder-input { + box-shadow: ${props => props.theme.boxShadow}; + + .geocoder-input__search { + position: absolute; + height: ${props => props.theme.geocoderInputHeight}px; + width: 30px; + padding-left: 6px; + display: flex; + align-items: center; + justify-content: center; + color: ${props => props.theme.subtextColor}; + } + + input { + padding: 4px 36px; + height: ${props => props.theme.geocoderInputHeight}px; + } + } + + .geocoder-results { + box-shadow: ${props => props.theme.boxShadow}; + background-color: ${props => props.theme.panelBackground}; + position: absolute; + width: ${props => (Number.isFinite(props.width) ? props.width : props.theme.geocoderWidth)}px; + margin-top: ${props => props.theme.dropdownWapperMargin}px; + } + + .geocoder-item { + ${props => props.theme.dropdownListItem}; + ${props => props.theme.textTruncate}; + + &.active { + background-color: ${props => props.theme.dropdownListHighlightBg}; + } + } + + .remove-result { + position: absolute; + right: 16px; + top: 0px; + height: ${props => props.theme.geocoderInputHeight}px; + display: flex; + align-items: center; + + :hover { + cursor: pointer; + color: ${props => props.theme.textColorHl}; + } + } +`; + +const PLACEHOLDER = 'Enter an Address'; + +const GeoCoder = ({ + mapboxApiAccessToken, + className = '', + initialInputValue = '', + limit = 5, + timeout = 300, + formatItem = item => item.place_name, + viewport, + onSelected, + onDeleteMarker, + transitionDuration, + pointZoom, + width, + intl +}) => { + const [inputValue, setInputValue] = useState(initialInputValue); + const [showResults, setShowResults] = useState(false); + const [showDelete, setShowDelete] = useState(false); + const [results, setResults] = useState([]); + const [selectedIndex, setSelectedIndex] = useState(0); + + const client = useMemo(() => new MapboxClient(mapboxApiAccessToken), [mapboxApiAccessToken]); + + const onChange = useCallback( + event => { + const queryString = event.target.value; + setInputValue(queryString); + clearTimeout(debounceTimeout); + + debounceTimeout = setTimeout(async () => { + if (limit > 0 && Boolean(queryString)) { + const response = await client.geocodeForward(queryString, {limit}); + setShowResults(true); + setResults(response.entity.features); + } + }, timeout); + }, + [client, results, setResults, setShowResults] + ); + + const onBlur = useCallback(() => { + setTimeout(() => { + setShowResults(false); + }, timeout); + }, [setShowResults]); + + const onFocus = useCallback(() => setShowResults(true), [setShowResults]); + + const onItemSelected = useCallback( + item => { + let newViewport = new WebMercatorViewport(viewport); + const {bbox, center} = item; + + newViewport = bbox + ? newViewport.fitBounds([ + [bbox[0], bbox[1]], + [bbox[2], bbox[3]] + ]) + : { + longitude: center[0], + latitude: center[1], + zoom: pointZoom + }; + + const {longitude, latitude, zoom} = newViewport; + + onSelected({...viewport, ...{longitude, latitude, zoom, transitionDuration}}, item); + + setShowResults(false); + setInputValue(formatItem(item)); + setShowDelete(true); + }, + [viewport, onSelected, transitionDuration, pointZoom, formatItem] + ); + + const onMarkDeleted = useCallback(() => { + setShowDelete(false); + setInputValue(''); + onDeleteMarker(); + }, [onDeleteMarker]); + + const onKeyDown = useCallback( + e => { + switch (e.keyCode) { + case KeyEvent.DOM_VK_UP: + setSelectedIndex(selectedIndex > 0 ? selectedIndex - 1 : selectedIndex); + break; + case KeyEvent.DOM_VK_DOWN: + setSelectedIndex(selectedIndex < results.length - 1 ? selectedIndex + 1 : selectedIndex); + break; + case KeyEvent.DOM_VK_ENTER: + case KeyEvent.DOM_VK_RETURN: + onItemSelected(results[selectedIndex]); + break; + default: + break; + } + }, + [results, selectedIndex, setSelectedIndex] + ); + + return ( + +
+
+ +
+ + {showDelete ? ( +
+ +
+ ) : null} +
+ + {showResults ? ( +
+ {results.map((item, index) => ( +
onItemSelected(item)} + > + {formatItem(item)} +
+ ))} +
+ ) : null} +
+ ); +}; + +export default injectIntl(GeoCoder); diff --git a/src/components/index.js b/src/components/index.js index c6b9a5a5ff..51379ec47e 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -133,10 +133,12 @@ export {default as ProgressBar} from './common/progress-bar'; export {default as FileUploadProgress} from './common/file-uploader/file-upload-progress'; export {default as Slider} from './common/slider/slider'; export {default as DatasetSquare} from './common/styled-components'; +export {default as ActionPanel, ActionPanelItem} from 'components/common/action-panel'; // side pane components export {default as LayerConfigGroup} from './side-panel/layer-panel/layer-config-group'; export {default as LayerTypeSelector} from './side-panel/layer-panel/layer-type-selector'; +export {ConfigGroupCollapsibleContent} from './side-panel/layer-panel/layer-config-group'; export { ChannelByValueSelector, diff --git a/src/components/kepler-gl.js b/src/components/kepler-gl.js index 01d42e14b8..4cce45e0f9 100644 --- a/src/components/kepler-gl.js +++ b/src/components/kepler-gl.js @@ -85,6 +85,10 @@ const GlobalStyle = styled.div` text-decoration: none; color: ${props => props.theme.labelColor}; } + + .mapboxgl-ctrl .mapboxgl-ctrl-logo { + display: none; + } `; KeplerGlFactory.deps = [ @@ -119,7 +123,8 @@ function KeplerGlFactory( version: KEPLER_GL_VERSION, sidePanelWidth: DIMENSIONS.sidePanel.width, theme: {}, - cloudProviders: [] + cloudProviders: [], + readOnly: false }; componentDidMount() { @@ -220,11 +225,13 @@ function KeplerGlFactory( appWebsite, onSaveMap, onViewStateChange, + onDeckInitialized, width, height, mapboxApiAccessToken, mapboxApiUrl, getMapboxRef, + deckGlProps, // redux state mapStyle, @@ -239,7 +246,9 @@ function KeplerGlFactory( mapStyleActions, uiStateActions, providerActions, - dispatch + + // readOnly override + readOnly } = this.props; const availableProviders = this.availableProviders(this.props); @@ -309,11 +318,13 @@ function KeplerGlFactory( clicked, mousePos, readOnly: uiState.readOnly, + onDeckInitialized, onViewStateChange, uiStateActions, visStateActions, mapStateActions, - animationConfig + animationConfig, + deckGlProps }; const isSplit = splitMaps && splitMaps.length > 1; @@ -346,7 +357,7 @@ function KeplerGlFactory( ref={this.root} > - {!uiState.readOnly && } + {(!uiState.readOnly && !readOnly) && }
{mapContainers}
@@ -363,11 +374,14 @@ function KeplerGlFactory( splitMaps={splitMaps} /> )} - {!uiState.readOnly && interactionConfig.geocoder.enabled && ( + {interactionConfig.geocoder.enabled && ( )} ( - - © kepler.gl |{' '} - - - © Mapbox |{' '} - - - © OpenStreetMap |{' '} - - - Improve this map - +
+ Basemap by: + +
+
); @@ -152,7 +164,9 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { (accu, layer, idx) => ({ ...accu, [layer.id]: - layer.shouldRenderLayer(layerData[idx]) && this._isVisibleMapLayer(layer, mapLayers) + layer.id !== GEOCODER_LAYER_ID && + layer.shouldRenderLayer(layerData[idx]) && + this._isVisibleMapLayer(layer, mapLayers) }), {} ) @@ -227,6 +241,12 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { } }; + _onDeckInitialized(gl) { + if (this.props.onDeckInitialized) { + this.props.onDeckInitialized(this._deck, gl); + } + } + _onBeforeRender = ({gl}) => { setLayerBlending(gl, this.props.layerBlending); }; @@ -414,6 +434,7 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { this._deck = comp.deck; } }} + onWebGLInitialized={gl => this._onDeckInitialized(gl)} /> ); } @@ -463,6 +484,7 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { uiState, uiStateActions, visStateActions, + interactionConfig, editor, index } = this.props; @@ -484,6 +506,8 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { }; const isEdit = uiState.mapControls.mapDraw.active; + const hasGeocoderLayer = layers.find(l => l.id === GEOCODER_LAYER_ID); + return ( @@ -498,7 +522,7 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { mapControls={mapControls} readOnly={this.props.readOnly} scale={mapState.scale || 1} - top={0} + top={interactionConfig.geocoder && interactionConfig.geocoder.enabled ? 52 : 0} editor={editor} locale={uiState.locale} onTogglePerspective={mapStateActions.togglePerspective} @@ -546,11 +570,13 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { }} /> - {mapStyle.topMapStyle && ( + {mapStyle.topMapStyle || hasGeocoderLayer ? (
- + + {this._renderDeckOverlay({[GEOCODER_LAYER_ID]: true})} +
- )} + ) : null} {this._renderMapPopover(layersToRender)}
diff --git a/src/components/map/map-control.js b/src/components/map/map-control.js index a13f128927..c0a18c91f4 100644 --- a/src/components/map/map-control.js +++ b/src/components/map/map-control.js @@ -53,7 +53,7 @@ const StyledMapControl = styled.div` width: ${props => props.theme.mapControl.width}px; padding: ${props => props.theme.mapControl.padding}px; z-index: 10; - top: ${props => props.top}px; + margin-top: ${props => props.top || 0}px; position: absolute; `; @@ -72,14 +72,18 @@ const StyledMapControlPanel = styled.div` } `; -const StyledMapControlPanelContent = styled.div` +const StyledMapControlPanelContent = styled.div.attrs({ + className: 'map-control__panel-content' +})` ${props => props.theme.dropdownScrollBar}; max-height: 500px; min-height: 100px; overflow: auto; `; -const StyledMapControlPanelHeader = styled.div` +const StyledMapControlPanelHeader = styled.div.attrs({ + className: 'map-control__panel-header' +})` display: flex; justify-content: space-between; background-color: ${props => props.theme.mapPanelHeaderBackgroundColor}; @@ -414,7 +418,8 @@ const MapControlFactory = () => { editor, scale, readOnly, - locale + locale, + top } = this.props; const { @@ -427,7 +432,7 @@ const MapControlFactory = () => { } = mapControls; return ( - + {/* Split Map */} {splitMap.show && readOnly !== true ? ( diff --git a/src/components/plot-container.js b/src/components/plot-container.js index 6fcd739c3a..6ccd7281b1 100644 --- a/src/components/plot-container.js +++ b/src/components/plot-container.js @@ -33,7 +33,8 @@ import {getScaleFromImageSize} from 'utils/export-utils'; import {findMapBounds} from 'utils/data-utils'; import geoViewport from '@mapbox/geo-viewport'; -const DOM_FILTER_FUNC = node => node.className !== 'mapboxgl-control-container'; +const CLASS_FILTER = ['mapboxgl-control-container', 'attrition-logo'] +const DOM_FILTER_FUNC = node => !CLASS_FILTER.includes(node.className); const OUT_OF_SCREEN_POSITION = -9999; const propTypes = { diff --git a/src/components/side-panel/interaction-panel/tooltip-config.js b/src/components/side-panel/interaction-panel/tooltip-config.js index a81754705f..549c9bb706 100644 --- a/src/components/side-panel/interaction-panel/tooltip-config.js +++ b/src/components/side-panel/interaction-panel/tooltip-config.js @@ -101,12 +101,21 @@ function TooltipConfigFactory(DatasetTag) { { + onSelect={selected => { const newConfig = { ...config, fieldsToShow: { ...config.fieldsToShow, - [dataId]: fieldsToShow + [dataId]: selected.map( + f => + config.fieldsToShow[dataId].find( + tooltipField => tooltipField.name === f.name + ) || { + name: f.name, + // default initial tooltip is null + format: null + } + ) } }; onChange(newConfig); diff --git a/src/components/side-panel/interaction-panel/tooltip-config/tooltip-chicklet.js b/src/components/side-panel/interaction-panel/tooltip-config/tooltip-chicklet.js index 9c41e9df5d..d3069b54ba 100644 --- a/src/components/side-panel/interaction-panel/tooltip-config/tooltip-chicklet.js +++ b/src/components/side-panel/interaction-panel/tooltip-config/tooltip-chicklet.js @@ -71,7 +71,9 @@ const hashStyles = { ACTIVE: 'ACTIVE' }; -const IconDiv = styled.div` +const IconDiv = styled.div.attrs({ + className: 'tooltip-chicklet__icon' +})` color: ${props => props.status === hashStyles.SHOW ? props.theme.subtextColorActive @@ -79,6 +81,18 @@ const IconDiv = styled.div` ? props.theme.activeColor : props.theme.textColor}; `; + +function getFormatTooltip(formatLabels, format) { + if (!format) { + return null; + } + const formatLabel = formatLabels.find(fl => getValue(fl) === format); + if (formatLabel) { + return formatLabel.label; + } + return typeof format === 'object' ? JSON.stringify(format, null, 2) : String(format); +} + function TooltipChickletFactory(dataId, config, onChange, fields) { class TooltipChicklet extends Component { state = { @@ -103,11 +117,13 @@ function TooltipChickletFactory(dataId, config, onChange, fields) { render() { const {disabled, name, remove} = this.props; const {show} = this.state; - const field = config.fieldsToShow[dataId].find(fieldToShow => fieldToShow.name === name); - const formatLabels = getFormatLabels(fields, field.name); - let selectionIndex = formatLabels.findIndex(fl => getValue(fl) === field.format); - if (selectionIndex < 0) selectionIndex = 0; - const hashStyle = show ? hashStyles.SHOW : selectionIndex ? hashStyles.ACTIVE : null; + const tooltipField = config.fieldsToShow[dataId].find( + fieldToShow => fieldToShow.name === name + ); + const formatLabels = getFormatLabels(fields, tooltipField.name); + const hasFormat = Boolean(tooltipField.format); + const selectionIndex = formatLabels.findIndex(fl => getValue(fl) === tooltipField.format); + const hashStyle = show ? hashStyles.SHOW : hasFormat ? hashStyles.ACTIVE : null; return ( (this.node = node)}> @@ -126,7 +142,9 @@ function TooltipChickletFactory(dataId, config, onChange, fields) { - {(selectionIndex && formatLabels[selectionIndex]).label || ( + {hasFormat ? ( + getFormatTooltip(formatLabels, tooltipField.format) + ) : ( )} diff --git a/src/components/side-panel/panel-header.js b/src/components/side-panel/panel-header.js index 42f8964dc2..85b155cf2a 100644 --- a/src/components/side-panel/panel-header.js +++ b/src/components/side-panel/panel-header.js @@ -90,7 +90,7 @@ export const PanelAction = ({item, onClick}) => ( {item.label ?

{item.label}

: null} - + {item.tooltip ? ( diff --git a/src/constants/default-settings.js b/src/constants/default-settings.js index 8bc323ace7..1a0fbb1d5c 100644 --- a/src/constants/default-settings.js +++ b/src/constants/default-settings.js @@ -535,7 +535,11 @@ export const FIELD_OPTS = { }, format: { legend: d => d, - tooltip: [TOOLTIP_FORMAT_TYPES.DECIMAL, TOOLTIP_FORMAT_TYPES.PERCENTAGE] + tooltip: [ + TOOLTIP_FORMAT_TYPES.NONE, + TOOLTIP_FORMAT_TYPES.DECIMAL, + TOOLTIP_FORMAT_TYPES.PERCENTAGE + ] } }, timestamp: { @@ -546,7 +550,11 @@ export const FIELD_OPTS = { }, format: { legend: d => d, - tooltip: [TOOLTIP_FORMAT_TYPES.DATE, TOOLTIP_FORMAT_TYPES.DATE_TIME] + tooltip: [ + TOOLTIP_FORMAT_TYPES.NONE, + TOOLTIP_FORMAT_TYPES.DATE, + TOOLTIP_FORMAT_TYPES.DATE_TIME + ] } }, integer: { @@ -557,7 +565,11 @@ export const FIELD_OPTS = { }, format: { legend: d => d, - tooltip: [TOOLTIP_FORMAT_TYPES.DECIMAL, TOOLTIP_FORMAT_TYPES.PERCENTAGE] + tooltip: [ + TOOLTIP_FORMAT_TYPES.NONE, + TOOLTIP_FORMAT_TYPES.DECIMAL, + TOOLTIP_FORMAT_TYPES.PERCENTAGE + ] } }, boolean: { @@ -568,7 +580,7 @@ export const FIELD_OPTS = { }, format: { legend: d => d, - tooltip: [] + tooltip: [TOOLTIP_FORMAT_TYPES.NONE, TOOLTIP_FORMAT_TYPES.BOOLEAN] } }, date: { @@ -578,7 +590,7 @@ export const FIELD_OPTS = { }, format: { legend: d => d, - tooltip: [TOOLTIP_FORMAT_TYPES.DATE] + tooltip: [TOOLTIP_FORMAT_TYPES.NONE, TOOLTIP_FORMAT_TYPES.DATE] } }, geojson: { @@ -802,6 +814,13 @@ export const DEFAULT_TIME_FORMAT = 'MM/DD/YY HH:mm:ssa'; export const SPEED_CONTROL_RANGE = [0, 10]; export const SPEED_CONTROL_STEP = 0.001; +// Geocoder +export const GEOCODER_DATASET_NAME = 'geocoder_dataset'; +export const GEOCODER_LAYER_ID = 'geocoder_layer'; +export const GEOCODER_GEO_OFFSET = 0.05; +export const GEOCODER_ICON_COLOR = [255, 0, 0]; +export const GEOCODER_ICON_SIZE = 80; + // We could use directly react-map-gl-draw EditorMode but this would // create a direct dependency with react-map-gl-draw // Created this map to be independent from react-map-gl-draw diff --git a/src/constants/tooltip.js b/src/constants/tooltip.js index 8d2a138f58..163642c6ae 100644 --- a/src/constants/tooltip.js +++ b/src/constants/tooltip.js @@ -19,10 +19,12 @@ // THE SOFTWARE. export const TOOLTIP_FORMAT_TYPES = { + NONE: 'none', DATE: 'date', DATE_TIME: 'date_time', DECIMAL: 'decimal', - PERCENTAGE: 'percentage' + PERCENTAGE: 'percentage', + BOOLEAN: 'boolean' }; export const TOOLTIP_KEY = 'format'; @@ -30,7 +32,9 @@ export const TOOLTIP_KEY = 'format'; export const TOOLTIP_FORMATS = { NONE: { id: 'NONE', - label: 'None' + label: 'None', + format: null, + type: TOOLTIP_FORMAT_TYPES.NONE }, DECIMAL_SHORT: { id: 'DECIMAL_SHORT', @@ -163,10 +167,22 @@ export const TOOLTIP_FORMATS = { }, DATE_TIME_LTS: { // 12:00:00 AM - id: 'DATE_TIME', + id: 'DATE_TIME_LTS', label: '', format: 'LTS', type: TOOLTIP_FORMAT_TYPES.DATE_TIME + }, + BOOLEAN_NUM: { + id: 'BOOLEAN_NUM', + label: '0 | 1', + format: '01', + type: TOOLTIP_FORMAT_TYPES.BOOLEAN + }, + BOOLEAN_Y_N: { + id: 'BOOLEAN_Y_N', + label: 'yes | no', + format: 'yn', + type: TOOLTIP_FORMAT_TYPES.BOOLEAN } }; diff --git a/src/layers/h3-hexagon-layer/h3-hexagon-layer.js b/src/layers/h3-hexagon-layer/h3-hexagon-layer.js index b59c11aa38..a6d9875a2b 100644 --- a/src/layers/h3-hexagon-layer/h3-hexagon-layer.js +++ b/src/layers/h3-hexagon-layer/h3-hexagon-layer.js @@ -22,7 +22,7 @@ import Layer from '../base-layer'; import {GeoJsonLayer} from '@deck.gl/layers'; import {H3HexagonLayer} from '@deck.gl/geo-layers'; import EnhancedColumnLayer from 'deckgl-layers/column-layer/enhanced-column-layer'; -import {getCentroid, idToPolygonGeo, h3IsValid} from './h3-utils'; +import {getCentroid, idToPolygonGeo, h3IsValid, getHexFields} from './h3-utils'; import H3HexagonLayerIcon from './h3-hexagon-layer-icon'; import {CHANNEL_SCALES, HIGHLIGH_COLOR_3D} from 'constants/default-settings'; import {hexToRgb} from 'utils/color-utils'; @@ -91,18 +91,32 @@ export default class HexagonIdLayer extends Layer { }; } - static findDefaultLayerProps({fields = []}) { + static findDefaultLayerProps({fields = [], allData = []}) { const foundColumns = this.findDefaultColumnField(HEXAGON_ID_FIELDS, fields); - if (!foundColumns || !foundColumns.length) { + const hexFields = getHexFields(fields, allData); + if ((!foundColumns || !foundColumns.length) && !hexFields.length) { return {props: []}; } return { - props: foundColumns.map(columns => ({ - isVisible: true, - label: 'H3 Hexagon', - columns - })) + props: (foundColumns || []) + .map(columns => ({ + isVisible: true, + label: 'H3 Hexagon', + columns + })) + .concat( + (hexFields || []).map(f => ({ + isVisible: true, + label: f.name, + columns: { + hex_id: { + value: f.name, + fieldIdx: fields.findIndex(fid => fid.name === f.name) + } + } + })) + ) }; } diff --git a/src/layers/h3-hexagon-layer/h3-utils.js b/src/layers/h3-hexagon-layer/h3-utils.js index 5bce7b7423..faa2c7e755 100644 --- a/src/layers/h3-hexagon-layer/h3-utils.js +++ b/src/layers/h3-hexagon-layer/h3-utils.js @@ -19,6 +19,9 @@ // THE SOFTWARE. import {h3GetResolution, h3IsValid, h3ToGeo, h3ToGeoBoundary, geoToH3} from 'h3-js'; +import {ALL_FIELD_TYPES} from 'constants/default-settings'; +import {notNullorUndefined} from 'utils/data-utils'; + export {h3GetResolution, h3IsValid}; // get vertices should return [lon, lat] @@ -170,3 +173,13 @@ function getDistortions(vts, origs) { return distortions; } + +export const isHexField = (field, fieldIdx, allData) => { + if (!field.type === ALL_FIELD_TYPES.string) { + return false; + } + const firstDP = allData.find(d => notNullorUndefined(d[fieldIdx])); + return firstDP && h3IsValid(firstDP[fieldIdx]); +}; + +export const getHexFields = (fields, allData) => fields.filter((f, i) => isHexField(f, i, allData)); diff --git a/src/localization/en.js b/src/localization/en.js index be7f5af319..1a4a5cda8a 100644 --- a/src/localization/en.js +++ b/src/localization/en.js @@ -435,7 +435,7 @@ export default { or: 'or' }, geocoder: { - title: 'Geocoder' + title: 'Enter an Address' }, fieldSelector: { clearAll: 'Clear All', diff --git a/src/processors/file-handler.js b/src/processors/file-handler.js index c851e0efe4..5eb565a74f 100644 --- a/src/processors/file-handler.js +++ b/src/processors/file-handler.js @@ -103,6 +103,7 @@ export async function* readBatch(asyncIterator, fileName) { for await (const batch of asyncIterator) { // Last batch will have this special type and will provide all the root // properties of the parsed document. + // Only json parse will have `FINAL_RESULT` if (batch.batchType === BATCH_TYPE.FINAL_RESULT) { if (batch.container) { result = {...batch.container}; @@ -112,8 +113,9 @@ export async function* readBatch(asyncIterator, fileName) { if (batch.jsonpath && batch.jsonpath.length > 1) { const streamingPath = new _JSONPath(batch.jsonpath); streamingPath.setFieldAtPath(result, batches); - } else { - // The streamed object is a ROW JSON-batch + } else if (batch.jsonpath && batch.jsonpath.length === 1) { + // The streamed object is a ROW JSON-batch (jsonpath = '$') + // row objects result = batches; } } else { diff --git a/src/reducers/combined-updaters.js b/src/reducers/combined-updaters.js index bf47a02419..48077b382b 100644 --- a/src/reducers/combined-updaters.js +++ b/src/reducers/combined-updaters.js @@ -88,7 +88,8 @@ export const isValidConfig = config => export const defaultAddDataToMapOptions = { centerMap: true, - keepExistingConfig: false + keepExistingConfig: false, + autoCreateLayers: true }; /** diff --git a/src/reducers/map-style-updaters.js b/src/reducers/map-style-updaters.js index e6de74538c..d272e0e698 100644 --- a/src/reducers/map-style-updaters.js +++ b/src/reducers/map-style-updaters.js @@ -223,13 +223,13 @@ function getLayerGroupsFromStyle(style) { * @type {typeof import('./map-style-updaters').initMapStyleUpdater} * @public */ -export const initMapStyleUpdater = (state, action) => ({ +export const initMapStyleUpdater = (state, {payload = {}}) => ({ ...state, // save mapbox access token to map style state - mapboxApiAccessToken: (action.payload || {}).mapboxApiAccessToken, - mapboxApiUrl: (action.payload || {}).mapboxApiUrl || state.mapboxApiUrl, - mapStyles: action.payload && !action.payload.mapStylesReplaceDefault ? state.mapStyles : {}, - mapStylesReplaceDefault: action.payload.mapStylesReplaceDefault || false + mapboxApiAccessToken: payload.mapboxApiAccessToken || state.mapboxApiAccessToken, + mapboxApiUrl: payload.mapboxApiUrl || state.mapboxApiUrl, + mapStyles: !payload.mapStylesReplaceDefault ? state.mapStyles : {}, + mapStylesReplaceDefault: payload.mapStylesReplaceDefault || false }); // }); diff --git a/src/reducers/root.js b/src/reducers/root.js index 03eb5a702b..2bc14485d7 100644 --- a/src/reducers/root.js +++ b/src/reducers/root.js @@ -33,7 +33,16 @@ export function provideInitialState(initialState) { const handleRegisterEntry = ( state, - {payload: {id, mint, mapboxApiAccessToken, mapboxApiUrl, mapStylesReplaceDefault}} + { + payload: { + id, + mint, + mapboxApiAccessToken, + mapboxApiUrl, + mapStylesReplaceDefault, + initialUiState + } + } ) => { // by default, always create a mint state even if the same id already exist // if state.id exist and mint=false, keep the existing state @@ -44,7 +53,7 @@ export function provideInitialState(initialState) { ...state, [id]: coreReducer( previousState, - keplerGlInit({mapboxApiAccessToken, mapboxApiUrl, mapStylesReplaceDefault}) + keplerGlInit({mapboxApiAccessToken, mapboxApiUrl, mapStylesReplaceDefault, initialUiState}) ) }; }; diff --git a/src/reducers/ui-state-updaters.d.ts b/src/reducers/ui-state-updaters.d.ts index 2d6de56cd1..c96f389652 100644 --- a/src/reducers/ui-state-updaters.d.ts +++ b/src/reducers/ui-state-updaters.d.ts @@ -187,3 +187,7 @@ export function loadFilesUpdater(state: UiState, action: LoadFilesUpdaterAction) export function loadFilesErrUpdater(state: UiState, action: LoadFilesErrUpdaterAction): UiState; export function loadFilesSuccessUpdater(state: UiState): UiState; export function toggleSplitMapUpdater(state: UiState, action: ToggleSplitMapUpdaterAction): UiState; +export function initUiStateUpdater(state: UiState, action: { + type?: ActionTypes.INIT; + payload: KeplerGlInitPayload; +}): UiState; diff --git a/src/reducers/ui-state-updaters.js b/src/reducers/ui-state-updaters.js index f8a9190c1e..af6add373e 100644 --- a/src/reducers/ui-state-updaters.js +++ b/src/reducers/ui-state-updaters.js @@ -260,6 +260,15 @@ export const INITIAL_UI_STATE = { }; /* Updaters */ +/** + * @memberof uiStateUpdaters + + */ +export const initUiStateUpdater = (state, action) => ({ + ...state, + ...(action.payload || {}).initialUiState +}); + /** * Toggle active side panel * @memberof uiStateUpdaters diff --git a/src/reducers/ui-state.js b/src/reducers/ui-state.js index fe9b2e3f8e..d400e0a5b7 100644 --- a/src/reducers/ui-state.js +++ b/src/reducers/ui-state.js @@ -27,6 +27,7 @@ import * as uiStateUpdaters from './ui-state-updaters'; * It is used to generate documentation */ const actionHandler = { + [ActionTypes.INIT]: uiStateUpdaters.initUiStateUpdater, [ActionTypes.TOGGLE_SIDE_PANEL]: uiStateUpdaters.toggleSidePanelUpdater, [ActionTypes.TOGGLE_MODAL]: uiStateUpdaters.toggleModalUpdater, [ActionTypes.SHOW_EXPORT_DROPDOWN]: uiStateUpdaters.showExportDropdownUpdater, diff --git a/src/reducers/vis-state-updaters.js b/src/reducers/vis-state-updaters.js index 0dbaf21b31..58bf891db4 100644 --- a/src/reducers/vis-state-updaters.js +++ b/src/reducers/vis-state-updaters.js @@ -1168,7 +1168,7 @@ export const updateVisDataUpdater = (state, action) => { let newLayers = mergedState.layers.filter(l => l.config.dataId in newDataEntries); - if (!newLayers.length) { + if (!newLayers.length && (options || {}).autoCreateLayers !== false) { // no layer merged, find defaults const result = addDefaultLayers(mergedState, newDataEntries); mergedState = result.state; diff --git a/src/styles/base.js b/src/styles/base.js index 8eb66c0e1a..10dc080a7f 100644 --- a/src/styles/base.js +++ b/src/styles/base.js @@ -144,6 +144,7 @@ export const dropdownListBgdLT = '#FFFFFF'; export const dropdownListBorderTop = '#242730'; export const dropdownListBorderTopLT = '#D3D8E0'; export const dropdownWrapperZ = 100; +export const dropdownWapperMargin = 4; // Switch export const switchWidth = 24; export const switchHeight = 12; @@ -253,6 +254,12 @@ export const sliderInputWidth = 56; export const sliderMarginTopIsTime = -12; export const sliderMarginTop = 12; +// Geocoder +export const geocoderWidth = 360; +export const geocoderTop = 20; +export const geocoderRight = 12; +export const geocoderInputHeight = 36; + // Plot export const rangeBrushBgd = '#3A414C'; export const histogramFillInRange = activeColor; @@ -366,7 +373,7 @@ const input = css` opacity: ${props => (props.disabled ? 0.5 : 1)}; :hover { - cursor: ${props => (props.type === 'number' ? 'text' : 'pointer')}; + cursor: ${props => (props.type === 'number' || props.type === 'text' ? 'text' : 'pointer')}; background-color: ${props => props.active ? props.theme.inputBgdActive : props.theme.inputBgdHover}; border-color: ${props => @@ -868,6 +875,7 @@ export const theme = { dropdownListSection, dropdownListShadow, dropdownWrapperZ, + dropdownWapperMargin, modalScrollBar, scrollBar, sidePanelScrollBar, @@ -1099,6 +1107,12 @@ export const theme = { sliderMarginTopIsTime, sliderMarginTop, + // Geocoder + geocoderWidth, + geocoderTop, + geocoderRight, + geocoderInputHeight, + // Plot rangeBrushBgd, histogramFillInRange, diff --git a/src/utils/data-utils.js b/src/utils/data-utils.js index d4a32c0f42..96ba4a9545 100644 --- a/src/utils/data-utils.js +++ b/src/utils/data-utils.js @@ -368,11 +368,23 @@ export function applyDefaultFormat(tooltipFormat) { return v => moment.utc(v).format(tooltipFormat.format); case TOOLTIP_FORMAT_TYPES.PERCENTAGE: return v => `${d3Format(TOOLTIP_FORMATS.DECIMAL_DECIMAL_FIXED_2.format)(v)}%`; + case TOOLTIP_FORMAT_TYPES.BOOLEAN: + return getBooleanFormatter(tooltipFormat.format); default: return defaultFormatter; } } +export function getBooleanFormatter(format) { + switch (format) { + case '01': + return v => (v ? '1' : '0'); + case 'yn': + return v => (v ? 'yes' : 'no'); + default: + return defaultFormatter; + } +} // Allow user to specify custom tooltip format via config export function applyCustomFormat(format, field) { switch (field.type) { diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000000..e80d2d6d15 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,24 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export {maybeToDate, roundValToStep} from './data-utils'; +export {updateAllLayerDomainData} from '../reducers/vis-state-updaters'; +export {findPointFieldPairs} from '../utils/dataset-utils'; +export {getHexFields} from '../layers/h3-hexagon-layer/h3-utils'; diff --git a/src/utils/layer-utils.js b/src/utils/layer-utils.js index 6371ba27af..42c972b1ff 100644 --- a/src/utils/layer-utils.js +++ b/src/utils/layer-utils.js @@ -47,7 +47,7 @@ export function findDefaultLayer(dataset, layerClasses = {}) { // go through all layerProps to create layer return layerProps.map(props => { const layer = new layerClasses[props.type](props); - return typeof layer.setInitialLayerConfig === 'function' + return typeof layer.setInitialLayerConfig === 'function' && Array.isArray(dataset.allData) ? layer.setInitialLayerConfig(dataset.allData) : layer; }); diff --git a/test/browser/components/bottom-widget-test.js b/test/browser/components/bottom-widget-test.js new file mode 100644 index 0000000000..b2ce78c8f9 --- /dev/null +++ b/test/browser/components/bottom-widget-test.js @@ -0,0 +1,62 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import React from 'react'; +import test from 'tape'; +import {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils'; +import BottomWidgetFactory from 'components/bottom-widget'; +import {appInjector} from 'components/container'; + +const BottomWidget = appInjector.get(BottomWidgetFactory); + +import * as VisStateActions from 'actions/vis-state-actions'; + +// mock state +import {InitialState} from 'test/helpers/mock-state'; + +// default props from initial state +const defaultProps = { + datasets: InitialState.visState.datasets, + filters: InitialState.visState.filters, + layers: InitialState.visState.layers, + animationConfig: InitialState.visState.animationConfig, + uiState: InitialState.uiState, + containerW: 900, + sidePanelWidth: 300, + visStateActions: VisStateActions +}; + +test('Components -> BottomWidget.mount -> initial state', t => { + let wrapper; + + t.doesNotThrow(() => { + wrapper = mountWithTheme( + + + + ); + }, 'BottomWidget should not fail without props'); + t.equal( + wrapper.find('div').length, + 0, + 'should not render anything when layers and filters are empty' + ); + t.end(); +}); diff --git a/test/browser/components/container-test.js b/test/browser/components/container-test.js index 80c48eaefc..4bd150d4e3 100644 --- a/test/browser/components/container-test.js +++ b/test/browser/components/container-test.js @@ -80,7 +80,8 @@ test('Components -> Container -> Mount with mint:true', t => { mint: true, mapboxApiAccessToken: undefined, mapboxApiUrl: undefined, - mapStylesReplaceDefault: undefined + mapStylesReplaceDefault: undefined, + initialUiState: undefined } }; @@ -130,7 +131,8 @@ test('Components -> Container -> Mount with mint:true', t => { mint: true, mapboxApiAccessToken: 'pk.smoothie', mapboxApiUrl: undefined, - mapStylesReplaceDefault: undefined + mapStylesReplaceDefault: undefined, + initialUiState: undefined } }; @@ -206,7 +208,8 @@ test('Components -> Container -> Mount with mint:false', t => { mint: false, mapboxApiAccessToken: 'hello.world', mapboxApiUrl: undefined, - mapStylesReplaceDefault: undefined + mapStylesReplaceDefault: undefined, + initialUiState: undefined } }; @@ -273,7 +276,8 @@ test('Components -> Container -> Mount then rename', t => { mint: true, mapboxApiAccessToken: 'hello.world', mapboxApiUrl: undefined, - mapStylesReplaceDefault: undefined + mapStylesReplaceDefault: undefined, + initialUiState: undefined } }; diff --git a/test/browser/components/geocoder-panel-test.js b/test/browser/components/geocoder-panel-test.js index 27bc1cc833..32c64c450f 100644 --- a/test/browser/components/geocoder-panel-test.js +++ b/test/browser/components/geocoder-panel-test.js @@ -18,28 +18,43 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +/* eslint-disable max-statements */ import React from 'react'; import sinon from 'sinon'; import test from 'tape'; import {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils'; -import {GeocoderPanel} from 'components/geocoder-panel'; +import GeocoderPanelFactory from 'components/geocoder-panel'; +import {appInjector} from 'components/container'; + +const GeocoderPanel = appInjector.get(GeocoderPanelFactory); +const MAPBOX_TOKEN = process.env.MapboxAccessToken; test('GeocoderPanel - render', t => { const enabled = true; - const dispatch = sinon.spy(); + const updateVisData = sinon.spy(); + const removeDataset = sinon.spy(); + const updateMap = sinon.spy(); + const mockMapState = { + latitude: 33, + longitude: 127, + zoom: 8, + width: 800, + height: 800 + }; + const mockGeoItem = { - center: [0, 0], + center: [1, 55], text: 'mock', - bbox: [0, 0, 0, 0] + bbox: [-1, 50, 4, 65] }; const mockGeoItemWithOutBbox = { - center: [0, 0], + center: [1, 55], text: 'mock' }; - const mockPayload = { - datasets: [ + const mockPayload = [ + [ { data: { fields: [ @@ -72,7 +87,7 @@ test('GeocoderPanel - render', t => { analyzerType: 'STRING' } ], - rows: [[0, 0, 'place', 'mock']] + rows: [[55, 1, 'place', 'mock']] }, id: 'geocoder_dataset', info: { @@ -82,41 +97,37 @@ test('GeocoderPanel - render', t => { } } ], - options: { + { keepExistingConfig: true }, - config: { - version: 'v1', - config: { - visState: { - layers: [ - { - id: 'geocoder_layer', - type: 'icon', - config: { - label: 'Geocoder Layer', - color: [255, 0, 0], - dataId: 'geocoder_dataset', - columns: { - lat: 'lt', - lng: 'ln', - icon: 'icon', - label: 'text' - }, - isVisible: true, - hidden: true, - visConfig: { - radius: 80 - } + { + visState: { + layers: [ + { + id: 'geocoder_layer', + type: 'icon', + config: { + label: 'Geocoder Layer', + color: [255, 0, 0], + dataId: 'geocoder_dataset', + columns: { + lat: 'lt', + lng: 'ln', + icon: 'icon', + label: 'text' + }, + isVisible: true, + hidden: true, + visConfig: { + radius: 80 } } - ] - } + } + ] } } - }; + ]; - // const onLayerClick = sinon.spy(); let wrapper; t.doesNotThrow(() => { @@ -124,8 +135,11 @@ test('GeocoderPanel - render', t => { ); @@ -138,39 +152,41 @@ test('GeocoderPanel - render', t => { instance.onSelected(null, mockGeoItem); t.deepEqual( - dispatch.getCall(0).args, - [{type: '@@kepler.gl/REMOVE_DATASET', dataId: 'geocoder_dataset'}], - 'Should be dispatching removeDataset action on onSelected' - ); - t.deepEqual( - dispatch.getCall(1).args, - [{type: '@@kepler.gl/ADD_DATA_TO_MAP', payload: mockPayload}], - 'Should be dispatching addDataToMap action on onSelected' + removeDataset.args, + [['geocoder_dataset']], + 'Should call removeDataset on onSelected' ); + t.deepEqual(updateVisData.args, [mockPayload], 'Should call updateVisData onSelected'); + const newVP = updateMap.args[0][0]; + t.deepEqual( - dispatch.getCall(2).args, - [{type: '@@kepler.gl/FIT_BOUNDS', payload: mockGeoItem.bbox}], - 'Should be dispatching fitBounds action on onSelected w/ bbox' + {latitude: newVP.latitude, longitude: newVP.longitude, zoom: newVP.zoom}, + {latitude: 57.5, longitude: 1.5, zoom: 5}, + 'Should call updateMap action on onSelected w/ new viewport' ); + t.ok(newVP.transitionInterpolator, 'Should call updateMap action with transitionInterpolator'); + instance.onSelected(null, mockGeoItemWithOutBbox); + + const newVP2 = updateMap.args[1][0]; t.deepEqual( - dispatch.getCall(5).args, - [{type: '@@kepler.gl/FIT_BOUNDS', payload: [-0.1, -0.1, 0.1, 0.1]}], - 'Should be dispatching fitBounds action on onSelected w/o bbox' + {latitude: newVP2.latitude, longitude: newVP2.longitude, zoom: newVP2.zoom}, + {latitude: 55, longitude: 1, zoom: 12}, + 'Should call updateMapaction on onSelected w/o bbox' ); instance.removeMarker(); t.deepEqual( - dispatch.getCall(6).args, - [{type: '@@kepler.gl/REMOVE_DATASET', dataId: 'geocoder_dataset'}], + removeDataset.args[1], + ['geocoder_dataset'], 'Should be dispatching removeDataset action on removeMarker' ); instance.removeGeocoderDataset(); t.deepEqual( - dispatch.getCall(7).args, - [{type: '@@kepler.gl/REMOVE_DATASET', dataId: 'geocoder_dataset'}], + removeDataset.args[2], + ['geocoder_dataset'], 'Should be dispatching removeDataset action on removeGeocoderDataset' ); diff --git a/test/browser/components/index.js b/test/browser/components/index.js index 61c6d0d251..7412f6cd70 100644 --- a/test/browser/components/index.js +++ b/test/browser/components/index.js @@ -18,11 +18,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -// required by enzymev3 -const configure = require('enzyme').configure; -const Adapter = require('enzyme-adapter-react-16'); -configure({adapter: new Adapter()}); - import './injector-test'; import './container-test'; import './kepler-gl-test'; @@ -37,3 +32,4 @@ import './editor'; import './map-container-test'; import './geocoder-panel-test'; import './tooltip-config-test'; +import './bottom-widget-test'; diff --git a/test/browser/components/tooltip-config-test.js b/test/browser/components/tooltip-config-test.js index e23a3b0b9a..dcd65a842e 100644 --- a/test/browser/components/tooltip-config-test.js +++ b/test/browser/components/tooltip-config-test.js @@ -21,44 +21,233 @@ import React from 'react'; import sinon from 'sinon'; import test from 'tape'; +import uniq from 'lodash.uniq'; + import {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils'; import TooltipConfigFactory from 'components/side-panel/interaction-panel/tooltip-config'; -import {StateWTrips} from 'test/helpers/mock-state'; +import DatasetTagFactory from 'components/side-panel/common/dataset-tag'; +import FieldSelector from 'components/common/field-selector'; +import ChickletedInput from 'components/common/item-selector/chickleted-input'; +import {ChickletButton} from 'components/common/item-selector/chickleted-input'; +import DropdownList from 'components/common/item-selector/dropdown-list'; +import Typeahead from 'components/common/item-selector/typeahead'; + +import {Hash, Delete} from 'components/common/icons'; +import {StateWFiles} from 'test/helpers/mock-state'; +import {appInjector} from 'components/container'; + +const TooltipConfig = appInjector.get(TooltipConfigFactory); +const DatasetTag = appInjector.get(DatasetTagFactory); + +// const tooltipConfig = { +// fieldsToShow: { +// '190vdll3di': [ +// {name: 'gps_data.utc_timestamp', format: null}, +// {name: 'gps_data.types', format: null}, +// {name: 'epoch', format: null}, +// {name: 'has_result', format: null}, +// {name: 'id', format: null} +// ], +// ieukmgne: [ +// {name: 'OBJECTID', format: null}, +// {name: 'ZIP_CODE', format: null}, +// {name: 'ID', format: null}, +// {name: 'TRIPS', format: null}, +// {name: 'RATE', format: null} +// ] +// }, +// compareMode: false, +// compareType: 'absolute' +// }; test('TooltipConfig - render', t => { - const DataSetTag = () =>
; - const TooltipConfig = TooltipConfigFactory(DataSetTag); - const datasets = StateWTrips.visState.datasets; - const config = { + const datasets = StateWFiles.visState.datasets; + const tooltipConfig = StateWFiles.visState.interactionConfig.tooltip.config; + + const onChange = sinon.spy(); + let wrapper; + + t.doesNotThrow(() => { + wrapper = mountWithTheme( + + + + ); + }, 'Should render'); + + t.equal(wrapper.find(TooltipConfig).length, 1, 'Should render 1 TooltipConfig'); + t.equal(wrapper.find(DatasetTag).length, 2, 'Should render 2 DatasetTag'); + t.equal(wrapper.find(FieldSelector).length, 2, 'Should render 2 FieldSelector'); + t.equal(wrapper.find(ChickletedInput).length, 2, 'Should render 2 ChickletedInput'); + + // tooltip chicklets + const tooltipButtons = wrapper + .find(ChickletedInput) + .at(0) + .find(ChickletButton); + + t.equal(tooltipButtons.length, 5, 'should render 6 tooltip buttons'); + t.equal( + tooltipButtons + .at(0) + .find('span') + .at(0) + .text(), + 'gps_data.utc_timestamp' + ); + + t.end(); +}); + +test('TooltipConfig - render -> onSelect', t => { + const datasets = StateWFiles.visState.datasets; + const tooltipConfig = StateWFiles.visState.interactionConfig.tooltip.config; + + const onChange = sinon.spy(); + let wrapper; + + t.doesNotThrow(() => { + wrapper = mountWithTheme( + + + + ); + }, 'Should render'); + + t.equal(wrapper.find(ChickletedInput).length, 2, 'Should render 2 ChickletedInput'); + + // click chicklet input to open dropdown + wrapper + .find(ChickletedInput) + .at(0) + .simulate('click'); + + const dropdownSelect = wrapper.find(Typeahead); + t.equal(dropdownSelect.length, 1, 'should render 1 Typeahead'); + + const listItems = dropdownSelect.find('.field-selector_list-item'); + + t.deepEqual( + dropdownSelect.find('.list__item__anchor').map(item => item.text()), + ['gps_data.lat', 'gps_data.lng', 'time', 'begintrip_ts_utc', 'begintrip_ts_local', 'date'], + 'should filter out selected tooltip items' + ); + + // click 1 item to add + listItems.at(0).simulate('click'); + + const expectedArgs0 = { + ...tooltipConfig, fieldsToShow: { - test_trip_data: [ - { - name: 'tpep_pickup_datetime', - format: null - }, - { - name: 'tpep_dropoff_datetime', - format: null - }, - { - name: 'fare_amount', - format: null - } + ...tooltipConfig.fieldsToShow, + '190vdll3di': [ + ...tooltipConfig.fieldsToShow['190vdll3di'], + {name: 'gps_data.lat', format: null} ] - }, - enabled: true + } + }; + + t.deepEqual(onChange.args[0], [expectedArgs0], 'should call onchange with new tooltip appended'); + + // delete 1 item + const tooltipButtons = wrapper + .find(ChickletedInput) + .at(0) + .find(ChickletButton); + + tooltipButtons + .at(0) + .find(Delete) + .simulate('click'); + const expectedArgs1 = { + ...tooltipConfig, + fieldsToShow: { + ...tooltipConfig.fieldsToShow, + '190vdll3di': tooltipConfig.fieldsToShow['190vdll3di'].slice( + 1, + tooltipConfig.fieldsToShow['190vdll3di'].length + ) + } + }; + t.deepEqual(onChange.args[1], [expectedArgs1], 'should call onchange with 1 item removed'); + + // clear All + t.equal(wrapper.find('.button.clear-all').length, 2, 'should render 2 clea all buttons'); + + // click to clear all + wrapper + .find('.button.clear-all') + .at(0) + .simulate('click'); + const expectedArgs2 = { + ...tooltipConfig, + fieldsToShow: { + ...tooltipConfig.fieldsToShow, + '190vdll3di': [] + } }; + t.deepEqual(onChange.args[2], [expectedArgs2], 'should call onchange to clear all tooltips'); + + t.end(); +}); + +test('TooltipConfig - render -> tooltip format', t => { + const datasets = StateWFiles.visState.datasets; + const tooltipConfig = StateWFiles.visState.interactionConfig.tooltip.config; + const onChange = sinon.spy(); let wrapper; t.doesNotThrow(() => { wrapper = mountWithTheme( - + ); }, 'Should render'); - t.equal(wrapper.find(TooltipConfig).length, 1, 'Should display 1 TooltipConfig'); + const tooltipButtons = wrapper + .find(ChickletedInput) + .at(0) + .find(ChickletButton); + + // click on hash + tooltipButtons + .at(0) + .find(Hash) + .simulate('click'); + + const formatDropdown = wrapper.find(DropdownList); + t.equal(formatDropdown.length, 1, 'should render 1 format dropdown'); + + const options = formatDropdown.at(0).props().options; + + t.deepEqual( + uniq(options.map(op => op.type)), + ['none', 'date', 'date_time'], + 'should render date type formats' + ); + + const option1 = options[1].format; + + // click option1 + formatDropdown + .find('.list__item') + .at(1) + .simulate('click'); + const expectedArgs0 = { + ...tooltipConfig, + fieldsToShow: { + ...tooltipConfig.fieldsToShow, + '190vdll3di': [ + {name: 'gps_data.utc_timestamp', format: option1}, + ...tooltipConfig.fieldsToShow['190vdll3di'].slice( + 1, + tooltipConfig.fieldsToShow['190vdll3di'].length + ) + ] + } + }; + t.deepEqual(onChange.args[0], [expectedArgs0], 'should call onchange to set format'); t.end(); }); diff --git a/test/browser/file-handler.js b/test/browser/file-handler.js index 41ef13b410..1b092182cf 100644 --- a/test/browser/file-handler.js +++ b/test/browser/file-handler.js @@ -24,6 +24,10 @@ import {processFileData, readFileInBatches} from 'processors/file-handler'; import {csvWithNull} from '../node/processors/file-handler-fixtures'; import {dataWithNulls, testFields, parsedDataWithNulls} from 'test/fixtures/test-csv-data'; import geojsonString, { + featureString, + processedFeature, + processedFeatureRows, + processedFeatureFields, processedFields as geojsonFields, processedRows as geojsonRows } from 'test/fixtures/geojson-style'; @@ -144,7 +148,7 @@ test('#file-handler -> readFileInBatches.csv -> processFileData', async t => { t.end(); }); -test('#file-handler -> readFileInBatches.geoJson -> processFileData', async t => { +test('#file-handler -> readFileInBatches.GeoJSON FeatureCollection -> processFileData', async t => { const geojsonFile = new File([geojsonString], 'text-data-1.geojson', {type: ''}); const gen = await readFileInBatches({file: geojsonFile, fileCache: []}); @@ -266,6 +270,100 @@ test('#file-handler -> readFileInBatches.geoJson -> processFileData', async t => t.end(); }); +test('#file-handler -> readFileInBatches.GeoJSON Single Feature -> processFileData', async t => { + const geojsonFile = new File([featureString], 'text-data-1.geojson', {type: ''}); + const gen = await readFileInBatches({file: geojsonFile, fileCache: []}); + + // metadata batch + const batch1 = await gen.next(); + + const expected1 = { + value: { + batchType: 'metadata', + metadata: {_loader: {}, _context: {}}, + data: [], + bytesUsed: 0, + progress: {rowCount: 0, rowCountInBatch: 0, percent: 0}, + fileName: 'text-data-1.geojson' + }, + done: false + }; + + t.deepEqual( + Object.keys(batch1.value).sort(), + Object.keys(expected1.value).sort(), + 'value should have same keyss' + ); + + t.equal(batch1.value.batchType, expected1.value.batchType, 'batch1.batchType should be the same'); + t.equal(batch1.value.fileName, expected1.value.fileName, 'batch1.fileName should be the same'); + t.deepEqual(batch1.value.data, expected1.value.data, 'batch1.data should be the same'); + t.deepEqual( + batch1.value.progress, + expected1.value.progress, + 'batch1.progress should be the same' + ); + + // final result batch + const batch2 = await gen.next(); + const expected2 = { + value: { + batchType: 'final-result', + container: processedFeature, + jsonpath: null, + data: processedFeature, + schema: null, + progress: {rowCount: 0, rowCountInBatch: 0}, + fileName: 'text-data-1.geojson' + }, + done: false + }; + + t.deepEqual( + batch2.value.batchType, + expected2.value.batchType, + 'batch2 batchType should be a final-result' + ); + t.deepEqual( + batch2.value.data, + expected2.value.data, + 'batch2 data should be a single geojson feature' + ); + t.deepEqual( + batch2.value.container, + expected2.value.container, + 'batch2 container should be a single geojson feature' + ); + t.equal(batch2.value.jsonpath, expected2.value.jsonpath, 'batch2 jsonpath should be null'); + t.deepEqual(batch2.value.progress, expected2.value.progress, 'batch2 progress should be correct'); + + const batch3 = await gen.next(); + t.deepEqual(batch3, {value: undefined, done: true}, 'batch3 should be done'); + + // process geojson data received + const processed = await processFileData({content: batch2.value, fileCache: []}); + const expectedInfo = {label: 'text-data-1.geojson', format: 'geojson'}; + + t.equal(processed.length, 1, 'processFileData should return 1 result'); + t.ok(processed[0].info, 'processFileData should have info'); + t.ok(processed[0].data, 'processFileData should have data'); + t.deepEqual(processed[0].info, expectedInfo, 'info should be correct'); + const {fields, rows} = processed[0].data; + + fields.forEach((f, i) => { + t.deepEqual( + f, + processedFeatureFields[i], + `should process correct geojson field ${processedFeatureFields[i].name}` + ); + }); + rows.forEach((r, i) => { + t.deepEqual(r, processedFeatureRows[i], `should process geojson row ${i} correctly`); + }); + + t.end(); +}); + test('#file-handler -> readFileInBatches.row -> processFileData', async t => { const fileName = 'row-data.json'; const rowFile = new File([rowDataString], fileName, {type: ''}); diff --git a/test/browser/index.js b/test/browser/index.js index fdae1730d7..50f3bd3be8 100644 --- a/test/browser/index.js +++ b/test/browser/index.js @@ -18,14 +18,18 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +const configure = require('enzyme').configure; +const Adapter = require('enzyme-adapter-react-16'); +configure({adapter: new Adapter()}); + // component tests -import './components'; +require('./components'); // test layers -import './layer-tests'; +require('./layer-tests'); // test reducers -import './reducers'; +require('./reducers'); // test processors -import './file-handler'; +require('./file-handler'); diff --git a/test/fixtures/geojson-style.js b/test/fixtures/geojson-style.js index d8a95161a4..cf3bf2a10f 100644 --- a/test/fixtures/geojson-style.js +++ b/test/fixtures/geojson-style.js @@ -21,6 +21,54 @@ const geojsonString = `{"type":"FeatureCollection","features":[{"geometry":{"type":"Point","coordinates":[-105.15471672508885,39.98626910199207,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"radius":1,"id":"a1398a11-d1ce-421c-bf66-a456ff525de9"}},{"geometry":{"type":"Point","coordinates":[-105.1549804351595,39.98397605319894,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"94ed2c0f-27ce-416e-b3b4-6c00954b41f0"}},{"geometry":{"type":"Point","coordinates":[-105.15478982047146,39.98296589543148,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"radius":3,"id":"1f59be4c-82e8-4644-b3cf-4c1c0510cbb2"}},{"geometry":{"type":"Point","coordinates":[-105.15449343321012,39.98437157892626,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"radius":5,"id":"de9210a0-c48c-41bf-8463-4b0734d402c0"}},{"geometry":{"type":"Point","coordinates":[-105.15484576666667,39.98312416666666,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"9438eea5-dde0-4e5e-bdf5-7420eedbc419"}},{"geometry":{"type":"Point","coordinates":[-105.15494596666667,39.98422703333333,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"radius":0.5,"id":"9ecefa7c-4fc6-46b7-936c-580a6084ff47"}},{"geometry":{"type":"Point","coordinates":[-105.15522212737501,39.98433057912913,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"696a33f6-3ff2-45a9-8a11-ab541aa2152f"}},{"geometry":{"type":"Point","coordinates":[-105.15447046666667,39.9834028,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"a5b27f57-37a4-4576-ac4e-24dfb57730c3"}},{"geometry":{"type":"Point","coordinates":[-105.15487273333333,39.98379046666667,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"7a640dbe-e0ca-4c50-a43a-b65ec33305b5"}},{"geometry":{"type":"Point","coordinates":[-105.154786,39.986418799999996,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"154cc349-c8b3-4b41-a008-364df1e6d83d"}},{"geometry":{"type":"Point","coordinates":[-105.15456956666667,39.984760566666665,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"932d45a2-bc4b-4825-9b9f-70b2415753b5"}},{"geometry":{"type":"Point","coordinates":[-105.15503543214001,39.983469561355626,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"a178d24c-1ea3-46cd-8762-3669d7b9d722"}},{"geometry":{"type":"Point","coordinates":[-105.15462816666667,39.984541766666666,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"41f8b94e-266c-450a-a7dc-60c159370ddb"}},{"geometry":{"type":"Point","coordinates":[-105.15494077274344,39.98493993797259,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"99ee26ca-13e1-4bef-bcd2-12cfc290d6ae"}},{"geometry":{"type":"Point","coordinates":[-105.15451193333332,39.98379226666666,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"de5103bb-2808-46b4-8012-76ceb5511fa9"}},{"geometry":{"type":"Point","coordinates":[-105.1547839,39.98589206666667,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"c5caef3d-abc9-4044-bb14-2f5999979d8d"}},{"geometry":{"type":"Point","coordinates":[-105.15513263333332,39.98269536666667,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"e0162a54-e2b2-4076-be24-4f550f0ac651"}},{"geometry":{"type":"Point","coordinates":[-105.1546226,39.98516726666667,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"e53ab272-7f6a-49b8-93f1-693bb57aa9f8"}},{"geometry":{"type":"Point","coordinates":[-105.1545185,39.98417893333333,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"fdc3fff3-5c35-430b-8bb4-9517473a8dc1"}},{"geometry":{"type":"Point","coordinates":[-105.15476356666666,39.98555806666667,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"f342e7bd-771d-4bf1-ad57-40d435f54fa0"}},{"geometry":{"type":"Point","coordinates":[-105.15468952992738,39.985742351897976,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"dbda33a4-b194-40e8-88a6-5e8816a97377"}},{"geometry":{"type":"Point","coordinates":[-105.1545091,39.983995666666665,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"5564b2fb-8368-4f81-a8b4-bebb30cbaaed"}},{"geometry":{"type":"Point","coordinates":[-105.15508416666667,39.985163,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"835f956f-6a86-4a7c-b1d6-3f32e4955a31"}},{"geometry":{"type":"Point","coordinates":[-105.15513636666667,39.983126066666664,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"8f8effff-f89d-417c-8e44-c8561f9f1eec"}},{"geometry":{"type":"Point","coordinates":[-105.15478573333333,39.98606776666667,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"59552665-bdd6-4c0c-bd0d-5ee79ea4848b"}},{"geometry":{"type":"Point","coordinates":[-105.1547872,39.9866052,0]},"type":"Feature","properties":{"fillColor":[77,193,156],"id":"32df5401-38d1-4e96-8d1e-a29d2c7c3955"}}]}`; export default geojsonString; +export const featureString = `{"type":"Feature","geometry":{"type":"LineString","coordinates":[[41.8817441,-87.6335398],[41.88237441,-87.6331298]]},"properties":{"lat":41.8817441,"lng":-87.6335398,"icon":"place"}}`; + +export const processedFeature = { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [ + [41.8817441, -87.6335398], + [41.88237441, -87.6331298] + ] + }, + properties: { + lat: 41.8817441, + lng: -87.6335398, + icon: 'place' + } +}; + +export const processedFeatureFields = [ + {name: '_geojson', format: '', tableFieldIndex: 1, type: 'geojson', analyzerType: 'GEOMETRY'}, + {name: 'lat', format: '', tableFieldIndex: 2, type: 'real', analyzerType: 'FLOAT'}, + {name: 'lng', format: '', tableFieldIndex: 3, type: 'real', analyzerType: 'FLOAT'}, + {name: 'icon', format: '', tableFieldIndex: 4, type: 'string', analyzerType: 'STRING'} +]; + +export const processedFeatureRows = [ + [ + { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [ + [41.8817441, -87.6335398], + [41.88237441, -87.6331298] + ] + }, + properties: { + lat: 41.8817441, + lng: -87.6335398, + icon: 'place' + } + }, + 41.8817441, + -87.6335398, + 'place' + ] +]; + export const processedFields = [ {name: '_geojson', format: '', tableFieldIndex: 1, type: 'geojson', analyzerType: 'GEOMETRY'}, {name: 'fillColor', format: '', tableFieldIndex: 2, type: 'geojson', analyzerType: 'ARRAY'}, diff --git a/test/node/reducers/composer-state-test.js b/test/node/reducers/composer-state-test.js index da8f88ea49..57bfcc2966 100644 --- a/test/node/reducers/composer-state-test.js +++ b/test/node/reducers/composer-state-test.js @@ -338,3 +338,26 @@ test('#composerStateReducer - addDataToMapUpdater: readOnly', t => { t.equal(nextState2.uiState.readOnly, false, 'should set readonly to be false'); t.end(); }); + +test('#composerStateReducer - addDataToMapUpdater: autoCreateLayers', t => { + const datasets = { + data: processCsvData(testCsvData), + info: { + id: sampleConfig.dataId + } + }; + const state = keplerGlReducer({}, registerEntry({id: 'test'})).test; + + // old state contain splitMaps + const nextState = addDataToMapUpdater(state, { + payload: { + datasets, + options: { + autoCreateLayers: false + } + } + }); + t.equal(nextState.visState.layers.length, 0, 'should not create layers'); + + t.end(); +}); diff --git a/test/node/reducers/map-style-test.js b/test/node/reducers/map-style-test.js index a610615711..bfde0c9b8a 100644 --- a/test/node/reducers/map-style-test.js +++ b/test/node/reducers/map-style-test.js @@ -52,6 +52,16 @@ test('#mapStyleReducer', t => { }); test('#mapStyleReducer -> INIT', t => { + const initialState = reducer(InitialMapStyle, keplerGlInit()); + t.deepEqual( + initialState, + { + ...INITIAL_MAP_STYLE, + initialState: {} + }, + 'initialize map style with no argument' + ); + const newState = reducer( InitialMapStyle, keplerGlInit({ diff --git a/test/node/reducers/ui-state-test.js b/test/node/reducers/ui-state-test.js index 5a4a442253..985abaa3e5 100644 --- a/test/node/reducers/ui-state-test.js +++ b/test/node/reducers/ui-state-test.js @@ -34,6 +34,7 @@ import { startSaveStorage } from 'actions/ui-state-actions'; import {loadFiles, loadFilesErr} from 'actions/vis-state-actions'; +import {keplerGlInit} from 'actions/actions'; import reducer, {uiStateReducerFactory} from 'reducers/ui-state'; import {INITIAL_UI_STATE} from 'reducers/ui-state-updaters'; import { @@ -55,6 +56,23 @@ test('#uiStateReducer', t => { t.end(); }); +test('#uiStateReducer -> INIT', t => { + const uiStateReducer = uiStateReducerFactory(); + + const newState = reducer( + uiStateReducer(undefined, {}), + keplerGlInit({ + initialUiState: {readOnly: true} + }) + ); + t.deepEqual( + newState, + {...INITIAL_UI_STATE, readOnly: true, initialState: {}}, + 'should apply initialUiState' + ); + t.end(); +}); + test('#uiStateReducerFactory', t => { const uiStateReducer = uiStateReducerFactory({readOnly: true}); diff --git a/test/node/utils/data-utils-test.js b/test/node/utils/data-utils-test.js index c4a3dffa57..cc01976e94 100644 --- a/test/node/utils/data-utils-test.js +++ b/test/node/utils/data-utils-test.js @@ -151,6 +151,22 @@ test('dataUtils -> getFormatter', t => { } ], assert: [4223, '4,200'] + }, + { + input: ['01'], + assert: [true, '1'] + }, + { + input: ['01'], + assert: [false, '0'] + }, + { + input: ['yn'], + assert: [false, 'no'] + }, + { + input: ['yn'], + assert: [true, 'yes'] } ]; diff --git a/test/webpack.config.js b/test/webpack.config.js index 988d388da7..e6c31de974 100644 --- a/test/webpack.config.js +++ b/test/webpack.config.js @@ -54,34 +54,8 @@ const COMMON_CONFIG = { rootMode: 'upward', presets: ['@babel/preset-env', '@babel/preset-react'], plugins: [ - ['@babel/plugin-proposal-decorators', {legacy: true}], '@babel/plugin-proposal-class-properties', - [ - '@babel/transform-runtime', - { - regenerator: true - } - ], - '@babel/plugin-syntax-dynamic-import', - '@babel/plugin-syntax-import-meta', - '@babel/plugin-proposal-json-strings', - '@babel/plugin-proposal-function-sent', '@babel/plugin-proposal-export-namespace-from', - '@babel/plugin-proposal-numeric-separator', - '@babel/plugin-proposal-throw-expressions', - '@babel/plugin-proposal-export-default-from', - '@babel/plugin-proposal-logical-assignment-operators', - '@babel/plugin-proposal-optional-chaining', - [ - '@babel/plugin-proposal-pipeline-operator', - { - proposal: 'minimal' - } - ], - '@babel/plugin-proposal-nullish-coalescing-operator', - '@babel/plugin-proposal-do-expressions', - '@babel/plugin-proposal-function-bind', - '@babel/plugin-transform-modules-commonjs', [ 'module-resolver', { diff --git a/website/.babelrc b/website/.babelrc index f1f74a6367..c5cb6c30f3 100644 --- a/website/.babelrc +++ b/website/.babelrc @@ -4,31 +4,8 @@ "@babel/preset-react" ], "plugins": [ - ["@babel/plugin-proposal-decorators", { "legacy": true }], "@babel/plugin-proposal-class-properties", - ["@babel/transform-runtime", { - "regenerator": true - }], - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-syntax-import-meta", - "@babel/plugin-proposal-json-strings", - "@babel/plugin-proposal-function-sent", "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-logical-assignment-operators", - "@babel/plugin-proposal-optional-chaining", - [ - "@babel/plugin-proposal-pipeline-operator", - { - "proposal": "minimal" - } - ], - "@babel/plugin-proposal-nullish-coalescing-operator", - "@babel/plugin-proposal-do-expressions", - "@babel/plugin-proposal-function-bind", - "@babel/plugin-transform-modules-commonjs", [ "module-resolver", { diff --git a/website/package.json b/website/package.json index 0cf26dcd1a..94bb72ecc4 100644 --- a/website/package.json +++ b/website/package.json @@ -39,30 +39,12 @@ "devDependencies": { "@babel/core": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-decorators": "^7.0.0", - "@babel/plugin-proposal-do-expressions": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-export-namespace-from": "^7.0.0", - "@babel/plugin-proposal-function-bind": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-proposal-pipeline-operator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-proposal-export-namespace-from": "^7.2.0", "@babel/preset-env": "^7.0.0", "@babel/preset-react": "^7.0.0", "babel-eslint": "^9.0.0", "babel-loader": "^8.0.0", - "babel-plugin-inline-json-import": "^0.3.2", "babel-plugin-module-resolver": "^3.0.0", - "babel-plugin-transform-builtin-extend": "^1.1.0", "eslint": "^3.0.0", "eslint-config-prettier": "^2.4.0", "eslint-config-uber-es2015": "^3.0.0", @@ -78,8 +60,7 @@ "webpack-cli": "^3.2.1", "webpack-dev-middleware": "^3.5.1", "webpack-dev-server": "^3.1.14", - "webpack-hot-middleware": "^2.24.3", - "webpack-stats-plugin": "^0.2.1" + "webpack-hot-middleware": "^2.24.3" }, "peerDependencies": { "react": "0.14.x - 16.x", diff --git a/website/webpack.config.js b/website/webpack.config.js index 631d8da04e..bb24d0d8da 100644 --- a/website/webpack.config.js +++ b/website/webpack.config.js @@ -31,35 +31,8 @@ const console = require('global/console'); const BABEL_CONFIG = { presets: ['@babel/preset-env', '@babel/preset-react'], plugins: [ - ['@babel/plugin-proposal-decorators', {legacy: true}], '@babel/plugin-proposal-class-properties', - [ - '@babel/transform-runtime', - { - regenerator: true - } - ], - '@babel/plugin-syntax-dynamic-import', - '@babel/plugin-syntax-import-meta', - '@babel/plugin-proposal-json-strings', - '@babel/plugin-proposal-function-sent', '@babel/plugin-proposal-export-namespace-from', - '@babel/plugin-proposal-numeric-separator', - '@babel/plugin-proposal-throw-expressions', - '@babel/plugin-proposal-export-default-from', - '@babel/plugin-proposal-logical-assignment-operators', - '@babel/plugin-proposal-optional-chaining', - [ - '@babel/plugin-proposal-pipeline-operator', - { - proposal: 'minimal' - } - ], - '@babel/plugin-proposal-nullish-coalescing-operator', - '@babel/plugin-proposal-do-expressions', - '@babel/plugin-proposal-function-bind', - '@babel/plugin-transform-modules-commonjs', - ['inline-json-import', {}], [ 'module-resolver', { diff --git a/yarn.lock b/yarn.lock index 60727feff4..b9a59c47f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -363,7 +363,7 @@ "@babel/helper-create-class-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-proposal-decorators@^7.1.2", "@babel/plugin-proposal-decorators@^7.3.0": +"@babel/plugin-proposal-decorators@^7.1.2": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz#2156860ab65c5abf068c3f67042184041066543e" integrity sha512-e3RvdvS4qPJVTe288DlXjwKflpfy1hr0j5dz5WpIYYeP7vQZg2WfAEIp8k5/Lwis/m5REXEteIz6rrcDtXXG7w== @@ -412,7 +412,7 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-function-bind" "^7.8.3" -"@babel/plugin-proposal-function-sent@^7.0.0", "@babel/plugin-proposal-function-sent@^7.1.0": +"@babel/plugin-proposal-function-sent@^7.1.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-function-sent/-/plugin-proposal-function-sent-7.8.3.tgz#341fd532b7eadbbbdd8bcb715150f279a779f14f" integrity sha512-lu9wQjLnXd6Zy6eBKr0gE175xfD+da1rv2wOWEnZlD5KIxl894Tg34ppZ7ANR0jzQJMn+7pGuzSdy6JK4zGtKg== @@ -543,7 +543,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-export-namespace-from@^7.2.0", "@babel/plugin-syntax-export-namespace-from@^7.8.3": +"@babel/plugin-syntax-export-namespace-from@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== @@ -13204,14 +13204,6 @@ react-map-gl@^5.0.3: react-virtualized-auto-sizer "^1.0.2" viewport-mercator-project "^6.2.3 || ^7.0.1" -react-mapbox-gl-geocoder@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/react-mapbox-gl-geocoder/-/react-mapbox-gl-geocoder-1.1.0.tgz#f07e27a1329db711ec5843fbf1b78dbdc5930c9a" - integrity sha512-U8fVUlj7Sb19KOgkKvfr3MPuqiGZ+ldHwwfo4BxBv7cyr397PQ/0o2XiC/Nz4PvN7dbIFHz7JfuVaq/MFZ5oHw== - dependencies: - mapbox "^1.0.0-beta10" - viewport-mercator-project "^6.0.0" - react-markdown@^4.0.6: version "4.3.1" resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-4.3.1.tgz#39f0633b94a027445b86c9811142d05381300f2f"