diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index ced64ee724..69cd20cb79 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -7,15 +7,22 @@ on: jobs: publish-npm: runs-on: ubuntu-latest + + env: + NPM_AUTH_TOKEN: ${{secrets.npm_token}} + steps: - - uses: actions/checkout@v2.1.1 - - uses: actions/setup-node@v3 - with: - node-version: 18 - registry-url: https://registry.npmjs.org/ - - run: npm install -g yarn + - uses: actions/checkout@v4 + + # use Volta to manage yarn/node versions + - uses: volta-cli/action@v4 + + - run: yarn install - run: yarn bootstrap - run: npm i -g npm@8.19.2 - - run: npm --version && npm publish --workspaces --access public - env: - NODE_AUTH_TOKEN: ${{secrets.npm_token}} + + - name: Login to NPM + run: npm config set "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" + + - name: Publish + run: npm publish --workspaces --access public diff --git a/CHANGELOG.md b/CHANGELOG.md index a45889756f..7d17902c9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,121 @@ Each version should: Ref: http://keepachangelog.com/en/0.3.0/ --> +## [3.1.0-alpha.0] - November 26 2024 + +- 5b4f6537 [feat] create new dataset action (#2778) +- a253cae1 [chore] Update the keplergl processors update (#2776) +- 931e2c6b [fix] Update the path to relative path in utils (#2775) +- ac469c13 [chore] Updated imports for Kepler GL Reducers in docs (#2774) +- 13b469d8 [chore] common-utils module (#2773) +- 6fd4f884 [Feat] Kepler.gl AI Assistant [1] (#2735) +- ab9e2530 [fix] Time Sync fixes and tests (#2771) +- 1689ed68 [fix] Custom color scale fixes (#2770) +- d0c9a3b9 [feat] Support custom breaks in color scale (#2739) +- 3f645002 [fix] restore arc and line layers in non-geoarrow modes (#2732) +- 966ee4c6 [Chore] Custom Initial State and Forward Actions Docs update (#2731) +- e88577de [chore] Docs action page import updates (#2729) +- d783b43c [feat] experimental support for ARROW:extension:point; support for arrrow:wkb for geojson layer (#2716) +- 26687575 [chore] Update Code examples in API Reference Get Started page (#2727) +- 8ea1cabe [fix] Fixed synced filter domain and interval calculation (#2725) +- 695861b2 [Bug] fix yaxis chat doesn't update (#2724) +- 8c37afaa [fix] time sync bugfixes (#2723) +- 4c2a6b3c [Improvement] Improved radius legend number formatting (#2726) +- c9658214 [Doc] Improve keplergl-jupyter documentation (#2697) +- 934f8e89 [feat] Improve timeline sync filer UI (#2722) +- d6f68379 [fix] Time Sync bugfixes (#2721) +- 40f82127 [feat] Sync filter with layer timeline (#2718) +- 0b6f320a [Enhancement] Synced filter small tuneup to synced filter panel (#2715) +- caf6e485 [fix] filter fields based on timestamp (#2714) +- c17dacf3 [feat] Layer animation (#2713) +- 0507bd60 [faat] deckgl-arrow-layers module (#2680) +- 8e4d723b [feat] Allow function return type of getData in getFilterValueAccessor (#2708) +- e20d5e82 [BUG] fix gpu filter update trigger attribute update in every render (#2707) +- 2d8161e3 [Feat] add color picker to single color selector (#2699) +- b258e8a9 [Bug] Fix synced time filter loaded value not saved (#892) (#2706) +- e5fe97be [feat] Updated time filter sync style (#2705) +- cb705c63 [fix] Prevent bottom time widget crash (#895) (#2703) +- ef2ac8f0 [chore] Add runGpuFilterForPlot to export, ts changes to KeplerTableModel (#2702) +- ee695327 [fix] remove duplicate "https:" in example (#2711) +- a743a276 [fix] add map control buttons back (#2709) +- 97df4c94 [Feat] Replaced filter enlarged with view: side | enlarged | minified - part 2 (#2537) +- 1c0ef9a9 [feat] add deck.gl onFilteredItemsChange callback to DeckGl overlays (#2691) +- d6082fe6 [feat] Time filter syncing (#2690) +- b28a263e [feat] Implemented ability to invert time series trend colors (#2692) +- ecb5ed41 [feat] Edit color legend value (#2681) +- 9c82daae [Enhancement] Add billboard and fadeTrail toggles (#2684) +- 69fc6c65 [Feat] Dynamic map lib config (#2678) +- 5764b069 [Chore] Remove default props and react-onclickoutside in react functional components (#2679) +- 09e19f86 [Fix] Tooltip not working in exported HTML map (#2556) +- a24ba5ec [Feat] Support radius legend (#2677) +- 1e7415a3 [Enhancement] call layer methods to validate visconfig when switching dataset (#2676) +- 25a5b60d [Chore] Adding application config (#2658) +- a9135ac6 [Feat] add geojson column mode for point layer (#2666) +- b6ac6540 [Feat] Add neighbor column mode to arc layer, support arc from hex (#2665) +- 2bc59371 [Feat] support create geojson path from point csv in polygon layer (#2664) +- 4c489940 [chore] Split out column mode config into separate component (#2663) +- add6192b [feat] Layer Column Mode (#2662) +- ef32f711 [fix] Fixed disappearing animation time control (#2625) +- c70ae07e [chore] Update @loaders to 4.1.1 (#2638) +- ad94d703 [Fix] legend wasn't interactive in shadow DOM (#2630) +- 6ffb1dcb [chore] Move create or update filter action (#2636) +- 16a3ac26 [fix] Improved map bounds calculation and handled latitude issues (#2632) +- 7e3ea28b [fix] prevent second shadow effect (#2631) +- f8e7b417 [fix] Upgrade react-router from 3.2.5 to 3.2.6 (#2637) +- 56c9c3ed [fix] Updated type data-utils getColumnFormatter method (#2640) +- 5d77b7ab [chore] Add className for LayerManager (#2629) +- 6f45f1f0 [feat] add autoFocus prop in TypeHead (#2646) +- 406b9787 [fix] Reset default values when DropdownList component unmounts (#2648) +- cf39ab20 [fix] Map controls tooltips break drag event positioning (#2649) +- e7deb4c6 [chore] Exporting missing types for PlaybackControls (#2650) +- edd1fd98 [fix] Making sure animated spinner has border width CSS prop set (#2651) +- b92b9707 [fix] Disable polygon filter menu for non-polygon features (#2652) +- e40d9b6e [feat] Call get after inject to create full cache (#2647) +- f15be57f [fix] Fixed effect panel width (#2644) +- 04280b33 [fix] Hiding legend scrollbar when in image export (#2643) +- 73704019 [chore] Update modal with test id (#2642) +- 4f9d261c [fix] data table right margin in header (#2641) +- 66b7fbdf [chore] Replaced deprecated "assert" with "with". (#2654) +- fb7fd817 [fix] build_and_publish fix (#2645) +- 9dbc80f1 [chore] migrate from webpack to esbuild to build demo-app locally (#2616) +- 7b512cfa [chore]: Upgrade to yarn 4 (#2610) +- a06d03c5 [chore] Bump setuptools from 69.5.1 to 70.0.0 in /bindings/kepler.gl-jupyter (#2587) +- f977b4f2 [chore] Bump elliptic from 6.5.6 to 6.5.7 (#2608) +- 40005446 [chore] Fix cover script generate cover report (#2609) +- affc5b65 [Chore] Upgrade to eslint 8.53.0 and prettier 2.8.8, fix lint and type errors (#2607) +- bc90b0e2 [Chore] fix tests (#2602) +- e5111dad [Bug] Fixes a number of issues preventing Kepler from building on fresh checkout (#2596) +- 9341911e [Bug] Fix custom map style input (#2564) +- 89180277 [chore] update deps; update doc; update version (#2568) +- ff52dda6 [fix] jupyter widget: don't take over (#1723) +- 739aed86 [deps] Bump ip from 1.1.5 to 1.1.9 (#2527) +- 44526ebc [Feat] Kepler-Jupyter 0.3.4 with kepler v3 (#2565) +- 6667a966 [Docs] Update node.js version in docs to v18 (#2558) +- 4932e76a [Feat] use fixed height in geojson layer (#2533) +- 400120f3 [Enhancement] call layer methods to validate visconfig when switching dataset (#2532) +- 1f9757b8 [feat] Pass in custom transformRequest function (#2534) +- b644f203 [Fix] layer popover mapIndex (#2535) +- 4b3c950f [fix] Fix sample maps (#2529) +- 55fb2426 [chore]] Update COC to OpenJS (#2496) +- 0959de6a [Feat]Support Zoom to layer in layer panel (#2516) +- ac0d3575 [Chore] docs: Add GeoArrow to supported formats (#2503) +- 084d807f [Chore] Bump path-parse from 1.0.6 to 1.0.7 (#1569) +- 46086e88 [Chore] Bump cached-path-relative from 1.0.2 to 1.1.0 (#1687) +- b8e5f865 [Chore] Bump ssri from 6.0.1 to 6.0.2 (#1866) +- 48e5839f [Chore] Bump postcss from 7.0.35 to 7.0.39 (#1691) +- 03d844c4 [Chore] Bump url-parse from 1.5.1 to 1.5.10 (#1724) +- f5d3be2c [Chore] Bump async from 2.6.3 to 2.6.4 (#1810) +- 012e9d7e [Chore] Bump shell-quote from 1.7.2 to 1.7.3 (#1847) +- 3222fa11 [Chore] Bump minimist from 1.2.3 to 1.2.6 (#2520) +- 248a759d [Chore] Bump hosted-git-info from 2.8.8 to 2.8.9 (#1865) +- 8659d4c9 [Chore] Bump decode-uri-component from 0.2.0 to 0.2.2 (#2053) +- 354fb8d2 [Chore] Bump browserify-sign from 4.2.1 to 4.2.2 (#2421) +- 59d81ef8 [Chore] Bump @adobe/css-tools from 4.3.1 to 4.3.2 (#2464) +- 776f11bc [Chore] Update docs to MapLibre and react-map-gl v7 (#2497) +- 0ad17b50 [Chore] Bump follow-redirects from 1.15.1 to 1.15.4 (#2507) +- b3be6c9e [Fix] fix example node-app arrow errors (#2508) +- 24acc1a0 [Chore] Update Uber References (#2495) + ## [3.0.0] - December 21 2023 - 21a445fd [chore] update readme, fix examples, show effects button (#2492) diff --git a/docs/api-reference/advanced-usages/reducer-plugin.md b/docs/api-reference/advanced-usages/reducer-plugin.md index 12c3b0ae69..4c15abb40e 100644 --- a/docs/api-reference/advanced-usages/reducer-plugin.md +++ b/docs/api-reference/advanced-usages/reducer-plugin.md @@ -6,7 +6,7 @@ For advanced users, who want to add additional action handler to kepler.gl reduc ```js import {combineReducers} from 'redux'; -import keplerGlReducer from 'kepler.gl/reducers'; +import keplerGlReducer from '@kepler.gl/reducers'; const customizedKeplerGlReducer = keplerGlReducer .plugin({ diff --git a/docs/api-reference/advanced-usages/replace-ui-component.md b/docs/api-reference/advanced-usages/replace-ui-component.md index 9f67f12ca3..ed7084244d 100644 --- a/docs/api-reference/advanced-usages/replace-ui-component.md +++ b/docs/api-reference/advanced-usages/replace-ui-component.md @@ -39,7 +39,7 @@ In kepler.gl, we create the app injector by calling provide with an array of def Here is an example of how to use `injectComponents` to replace default `PanelHeader`. ```js -import {injectComponents, PanelHeaderFactory} from 'kepler.gl/components'; +import {injectComponents, PanelHeaderFactory} from '@kepler.gl/components'; // define custom header const CustomHeader = () => (<div>My kepler.gl app</div>); @@ -68,8 +68,8 @@ const MapContainer = () => <KeplerGl id="foo"/>; Here is an example of using `withState` helper to add reducer state and actions to customized component as additional props. ```js -import {withState, injectComponents, PanelHeaderFactory} from 'kepler.gl/components'; -import {visStateLens} from 'kepler.gl/reducers'; +import {withState, injectComponents, PanelHeaderFactory} from '@kepler.gl/components'; +import {visStateLens} from '@kepler.gl/reducers'; // custom action wrap to mounted instance const addTodo = (text) => ({ diff --git a/docs/api-reference/advanced-usages/using-updaters.md b/docs/api-reference/advanced-usages/using-updaters.md index 649d49787b..4cc5b6309c 100644 --- a/docs/api-reference/advanced-usages/using-updaters.md +++ b/docs/api-reference/advanced-usages/using-updaters.md @@ -21,7 +21,7 @@ const MapContainer = ({dispatch}) => ( or import the corresponding updater `mapStateUpdaters.togglePerspectiveUpdater` and call it inside the root reducer. The example below demos how to add a button outside kepler.gl component, and update the map perspective when click it. ```js -import keplerGlReducer, {mapStateUpdaters} from 'kepler.gl/reducers'; +import keplerGlReducer, {mapStateUpdaters} from '@kepler.gl/reducers'; // Root Reducer const reducers = combineReducers({ diff --git a/docs/api-reference/localization/README.md b/docs/api-reference/localization/README.md index 217381eae4..c6cce02547 100644 --- a/docs/api-reference/localization/README.md +++ b/docs/api-reference/localization/README.md @@ -1,7 +1,7 @@ # Localization Kepler.gl supports localization through [react-intl]. Locale is determined by `uiState.locale` value. -Current supported languages are: +Current supported languages are: | locale code | Language | Default? | |-------------|------------|----------| @@ -20,8 +20,8 @@ By default the first language is English `en`. The default language can be chang ```js import {combineReducers} from 'redux'; -import keplerGlReducer from 'kepler.gl/reducers'; -import {LOCALE_CODES} from 'kepler.gl/localization'; +import keplerGlReducer from '@kepler.gl/reducers'; +import {LOCALE_CODES} from '@kepler.gl/localization'; const customizedKeplerGlReducer = keplerGlReducer.initialState({ uiState: { @@ -55,7 +55,7 @@ Let's say we want to add the Swedish language to kepler.gl. Easiest way to add t ``` ## Modify default translation or add new translation -the `localeMessages` prop of `KeplerGl` takes additional translations and merge with default translation. +the `localeMessages` prop of `KeplerGl` takes additional translations and merge with default translation. #### Example 1. Update default translation To update the english translation of `layerManager.addData`, pass `localeMessages` like this. @@ -68,7 +68,7 @@ const localeMessages = { }; const App = () => ( - <KeplerGl + <KeplerGl id="map" localeMessages={messages} mapboxApiAccessToken={Token} @@ -86,7 +86,7 @@ const localeMessages = { }; const App = () => ( - <KeplerGl + <KeplerGl id="map" localeMessages={messages} mapboxApiAccessToken={Token} diff --git a/docs/api-reference/processors/processors.md b/docs/api-reference/processors/processors.md index 10ac22b1b4..b978d62dba 100644 --- a/docs/api-reference/processors/processors.md +++ b/docs/api-reference/processors/processors.md @@ -2,11 +2,11 @@ ### Table of Contents -- [getFieldsFromData][1] -- [processCsvData][4] -- [processGeojson][7] -- [processKeplerglJSON][10] -- [processRowObject][13] +- [getFieldsFromData](#getfieldsfromdata) +- [processCsvData](#processcsvdata) +- [processGeojson](#processgeojson) +- [processKeplerglJSON](#processkeplergljson) +- [processRowObject](#processrowobject) ## getFieldsFromData @@ -21,7 +21,7 @@ Assign `type`, `tableFieldIndex` and `format` (timestamp only) to each field **Examples** ```javascript -import {getFieldsFromData} from 'kepler.gl/processors'; +import {getFieldsFromData} from '@kepler.gl/processors'; const data = [{ time: '2016-09-17 00:09:55', value: '4', @@ -66,7 +66,7 @@ The data object can be wrapped in a `dataset` and pass to [`addDataToMap`][18] **Examples** ```javascript -import {processCsvData} from 'kepler.gl/processors'; +import {processCsvData} from '@kepler.gl/processors'; const testData = `gps_data.utc_timestamp,gps_data.lat,gps_data.lng,gps_data.types,epoch,has_result,id,time,begintrip_ts_utc,begintrip_ts_local,date 2016-09-17 00:09:55,29.9900937,31.2590542,driver_analytics,1472688000000,False,1,2016-09-23T00:00:00.000Z,2016-10-01 09:41:39+00:00,2016-10-01 09:41:39+00:00,2016-09-23 @@ -100,8 +100,8 @@ The data object can be wrapped in a `dataset` and pass to [`addDataToMap`][18] **Examples** ```javascript -import {addDataToMap} from 'kepler.gl/actions'; -import {processGeojson} from 'kepler.gl/processors'; +import {addDataToMap} from '@kepler.gl/actions'; +import {processGeojson} from '@kepler.gl/processors'; const geojson = { "type" : "FeatureCollection", @@ -138,15 +138,15 @@ The json object should contain `datasets` and `config`. **Parameters** -- `rawData` **[Object][17]** - - `rawData.datasets` **[Array][16]** - - `rawData.config` **[Object][17]** +- `rawData` **[Object][17]** + - `rawData.datasets` **[Array][16]** + - `rawData.config` **[Object][17]** **Examples** ```javascript -import {addDataToMap} from 'kepler.gl/actions'; -import {processKeplerglJSON} from 'kepler.gl/processors'; +import {addDataToMap} from '@kepler.gl/actions'; +import {processKeplerglJSON} from '@kepler.gl/processors'; dispatch(addDataToMap(processKeplerglJSON(keplerGlJson))); ``` @@ -164,8 +164,8 @@ Process data where each row is an object, output can be passed to [`addDataToMap **Examples** ```javascript -import {addDataToMap} from 'kepler.gl/actions'; -import {processRowObject} from 'kepler.gl/processors'; +import {addDataToMap} from '@kepler.gl/actions'; +import {processRowObject} from '@kepler.gl/processors'; const data = [ {lat: 31.27, lng: 127.56, value: 3}, diff --git a/docs/api-reference/reducers/README.md b/docs/api-reference/reducers/README.md index 56cf408f30..5fcc2b5bd6 100644 --- a/docs/api-reference/reducers/README.md +++ b/docs/api-reference/reducers/README.md @@ -14,14 +14,14 @@ It is immportant to understand the relationship between __kepler.gl reducer__, _ ## KeplerGl Reducer To connect kepler.gl components to your Redux app you'll need the following pieces from the kepler.gl package: -- Redux Reducer: `keplerGlReducer` imported from `kepler.gl/reducers` -- React Component: `KeplerGl` imported from `kepler.gl` +- Redux Reducer: `keplerGlReducer` imported from `@kepler.gl/reducers` +- React Component: `KeplerGl` imported from `@kepler.gl/components` These are the only 2 pieces you need to get kepler.gl up and running in your app. When you mount kepler.gl reducer in your app reducer (with `combineReducers`), it will then managers __ALL__ KeplerGl component instances that you add to your app. Each kepler.gl instance state is stored in a instance reduccer. For instance, if you have 2 kepler.gl components in your App: ```js -import KeplerGl from 'kepler.gl'; +import KeplerGl from '@kepler.gl/components'; const MapApp = () => ( <div> @@ -73,7 +73,7 @@ User can import a specific action handler in their root reducer and use it to di Here is an example how you can listen to an app action `QUERY_SUCCESS` and call `updateVisDataUpdater` to load data into kepler.gl. ```js -import keplerGlReducer, {visStateUpdaters} from 'kepler.gl/reducers'; +import keplerGlReducer, {visStateUpdaters} from '@kepler.gl/reducers'; // Root Reducer const reducers = combineReducers({ diff --git a/docs/api-reference/reducers/combine.md b/docs/api-reference/reducers/combine.md index 48dd1889d7..46c8537f4f 100644 --- a/docs/api-reference/reducers/combine.md +++ b/docs/api-reference/reducers/combine.md @@ -2,8 +2,8 @@ ### Table of Contents -- [combinedUpdaters][1] - - [addDataToMapUpdater][3] +- [combinedUpdaters](#combinedupdaters) + - [addDataToMapUpdater](#adddatatomapupdater) ## combinedUpdaters @@ -14,7 +14,7 @@ as the first argument. Read more about [Using updaters][5] **Examples** ```javascript -import keplerGlReducer, {combinedUpdaters} from 'kepler.gl/reducers'; +import keplerGlReducer, {combinedUpdaters} from '@kepler.gl/reducers'; // Root Reducer const reducers = combineReducers({ keplerGl: keplerGlReducer, @@ -58,7 +58,7 @@ Combine data and full configuration update in a single action **Parameters** - `state` **[Object][7]** kepler.gl instance state, containing all subreducer state -- `action` **[Object][7]** +- `action` **[Object][7]** - `action.payload` **[Object][7]** `{datasets, options, config}` - `action.payload.datasets` **([Array][8]<[Object][7]> | [Object][7])** **\*required** datasets can be a dataset or an array of datasets Each dataset object needs to have `info` and `data` property. diff --git a/docs/api-reference/reducers/map-state.md b/docs/api-reference/reducers/map-state.md index 2ce9ccf1c0..53b601ce8f 100644 --- a/docs/api-reference/reducers/map-state.md +++ b/docs/api-reference/reducers/map-state.md @@ -2,14 +2,15 @@ ### Table of Contents -- [mapStateUpdaters][1] - - [fitBoundsUpdater][3] - - [INITIAL_MAP_STATE][5] - - [receiveMapConfigUpdater][7] - - [resetMapConfigUpdater][9] - - [togglePerspectiveUpdater][11] - - [toggleSplitMapUpdater][13] - - [updateMapUpdater][15] +- [mapStateUpdaters](#mapstateupdaters) + - [fitBoundsUpdater](#fitboundsupdater) + - [INITIAL\_MAP\_STATE](#initial_map_state) + - [Properties](#properties) + - [receiveMapConfigUpdater](#receivemapconfigupdater) + - [resetMapConfigUpdater](#resetmapconfigupdater) + - [togglePerspectiveUpdater](#toggleperspectiveupdater) + - [toggleSplitMapUpdater](#togglesplitmapupdater) + - [updateMapUpdater](#updatemapupdater) ## mapStateUpdaters @@ -19,7 +20,7 @@ Read more about [Using updaters][17] **Examples** ```javascript -import keplerGlReducer, {mapStateUpdaters} from 'kepler.gl/reducers'; +import keplerGlReducer, {mapStateUpdaters} from '@kepler.gl/reducers'; // Root Reducer const reducers = combineReducers({ keplerGl: keplerGlReducer, @@ -57,8 +58,8 @@ Fit map viewport to bounds **Parameters** -- `state` **[Object][19]** -- `action` **[Object][19]** +- `state` **[Object][19]** +- `action` **[Object][19]** - `action.payload` **[Array][20]<[number][21]>** bounds as `[lngMin, latMin, lngMax, latMax]` Returns **[Object][19]** nextState @@ -87,8 +88,8 @@ Update `mapState` to propagate a new config **Parameters** -- `state` **[Object][19]** -- `action` **[Object][19]** +- `state` **[Object][19]** +- `action` **[Object][19]** - `action.payload` **[Object][19]** saved map config - `action.payload.config` (optional, default `{}`) - `action.payload.options` (optional, default `{}`) @@ -116,7 +117,7 @@ Toggle between 3d and 2d map. **Parameters** -- `state` **[Object][19]** +- `state` **[Object][19]** Returns **[Object][19]** nextState @@ -128,7 +129,7 @@ Toggle between one or split maps **Parameters** -- `state` **[Object][19]** +- `state` **[Object][19]** Returns **[Object][19]** nextState @@ -140,8 +141,8 @@ Update map viewport **Parameters** -- `state` **[Object][19]** -- `action` **[Object][19]** +- `state` **[Object][19]** +- `action` **[Object][19]** - `action.payload` **[Object][19]** viewport Returns **[Object][19]** nextState diff --git a/docs/api-reference/reducers/map-style.md b/docs/api-reference/reducers/map-style.md index 1c5db37494..5e71853898 100644 --- a/docs/api-reference/reducers/map-style.md +++ b/docs/api-reference/reducers/map-style.md @@ -2,16 +2,17 @@ ### Table of Contents -- [mapStyleUpdaters][1] - - [INITIAL_MAP_STYLE][3] - - [initMapStyleUpdater][5] - - [inputMapStyleUpdater][7] - - [loadCustomMapStyleUpdater][9] - - [loadMapStyleErrUpdater][11] - - [loadMapStylesUpdater][13] - - [mapConfigChangeUpdater][15] - - [mapStyleChangeUpdater][17] - - [resetMapConfigMapStyleUpdater][19] +- [mapStyleUpdaters](#mapstyleupdaters) + - [INITIAL\_MAP\_STYLE](#initial_map_style) + - [Properties](#properties) + - [initMapStyleUpdater](#initmapstyleupdater) + - [inputMapStyleUpdater](#inputmapstyleupdater) + - [loadCustomMapStyleUpdater](#loadcustommapstyleupdater) + - [loadMapStyleErrUpdater](#loadmapstyleerrupdater) + - [loadMapStylesUpdater](#loadmapstylesupdater) + - [mapConfigChangeUpdater](#mapconfigchangeupdater) + - [mapStyleChangeUpdater](#mapstylechangeupdater) + - [resetMapConfigMapStyleUpdater](#resetmapconfigmapstyleupdater) ## mapStyleUpdaters @@ -21,7 +22,7 @@ Read more about [Using updaters][21] **Examples** ```javascript -import keplerGlReducer, {mapStyleUpdaters} from 'kepler.gl/reducers'; +import keplerGlReducer, {mapStyleUpdaters} from '@kepler.gl/reducers'; // Root Reducer const reducers = combineReducers({ keplerGl: keplerGlReducer, @@ -76,10 +77,10 @@ populate mapStyles. **Parameters** -- `state` **[Object][23]** -- `action` **[Object][23]** - - `action.payload` **[Object][23]** - - `action.payload.mapboxApiAccessToken` **[string][22]** +- `state` **[Object][23]** +- `action` **[Object][23]** + - `action.payload` **[Object][23]** + - `action.payload.mapboxApiAccessToken` **[string][22]** Returns **[Object][23]** nextState @@ -100,8 +101,8 @@ Input a custom map style object - `action.payload.name` **[string][22]** style name - `action.payload.layerGroups` **[Object][23]** layer groups that can be used to set map layer visibility - `action.payload.icon` **[Object][23]** icon image data url - - `action.payload.inputStyle` - - `action.payload.mapState` + - `action.payload.inputStyle` + - `action.payload.mapState` Returns **[Object][23]** nextState @@ -114,14 +115,14 @@ Callback when a custom map style object is received **Parameters** - `state` **[Object][23]** `mapStyle` -- `action` **[Object][23]** - - `action.payload` **[Object][23]** - - `action.payload.icon` **[string][22]** - - `action.payload.style` **[Object][23]** - - `action.payload.error` **any** - - `action.payload.icon` - - `action.payload.style` - - `action.payload.error` +- `action` **[Object][23]** + - `action.payload` **[Object][23]** + - `action.payload.icon` **[string][22]** + - `action.payload.style` **[Object][23]** + - `action.payload.error` **any** + - `action.payload.icon` + - `action.payload.style` + - `action.payload.error` Returns **[Object][23]** nextState @@ -134,7 +135,7 @@ Callback when load map style error **Parameters** - `state` **[Object][23]** `mapStyle` -- `action` **[Object][23]** +- `action` **[Object][23]** - `action.payload` **any** error Returns **[Object][23]** nextState @@ -148,7 +149,7 @@ Callback when load map style success **Parameters** - `state` **[Object][23]** `mapStyle` -- `action` **[Object][23]** +- `action` **[Object][23]** - `action.payload` **[Object][23]** a `{[id]: style}` mapping Returns **[Object][23]** nextState @@ -162,7 +163,7 @@ Update `visibleLayerGroups`to change layer group visibility **Parameters** - `state` **[Object][23]** `mapStyle` -- `action` **[Object][23]** +- `action` **[Object][23]** - `action.payload` **[Object][23]** new config `{visibleLayerGroups: {label: false, road: true, background: true}}` Returns **[Object][23]** nextState @@ -176,8 +177,8 @@ Change to another map style. The selected style should already been loaded into **Parameters** - `state` **[Object][23]** `mapStyle` -- `action` **[Object][23]** - - `action.payload` **[string][22]** +- `action` **[Object][23]** + - `action.payload` **[string][22]** Returns **[Object][23]** nextState diff --git a/docs/api-reference/reducers/reducers.md b/docs/api-reference/reducers/reducers.md index 54055fdcce..46eb2d2d36 100644 --- a/docs/api-reference/reducers/reducers.md +++ b/docs/api-reference/reducers/reducers.md @@ -2,14 +2,14 @@ ### Table of Contents -- [keplerGlReducer][1] - - [keplerGlReducer.initialState][3] - - [keplerGlReducer.plugin][6] -- [mapStateLens][9] -- [mapStyleLens][11] -- [providerStateLens][13] -- [uiStateLens][15] -- [visStateLens][17] +- [keplerGlReducer](#keplerglreducer) + - [keplerGlReducer.initialState](#keplerglreducerinitialstate) + - [keplerGlReducer.plugin](#keplerglreducerplugin) +- [mapStateLens](#mapstatelens) +- [mapStyleLens](#mapstylelens) +- [providerStateLens](#providerstatelens) +- [uiStateLens](#uistatelens) +- [visStateLens](#visstatelens) ## keplerGlReducer @@ -19,7 +19,7 @@ to mount it at another address e.g. `foo` you will need to specify it when you m **Examples** ```javascript -import keplerGlReducer from 'kepler.gl/reducers'; +import keplerGlReducer from '@kepler.gl/reducers'; import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; import {taskMiddleware} from 'react-palm/tasks'; @@ -109,7 +109,7 @@ Connect subreducer `mapState`, used with `injectComponents`. Learn more at **Parameters** -- `reduxState` **any** +- `reduxState` **any** ## mapStyleLens @@ -118,7 +118,7 @@ Connect subreducer `mapStyle`, used with `injectComponents`. Learn more at **Parameters** -- `reduxState` **any** +- `reduxState` **any** ## providerStateLens @@ -127,7 +127,7 @@ Connect subreducer `providerState`, used with `injectComponents`. Learn more at **Parameters** -- `reduxState` **any** +- `reduxState` **any** ## uiStateLens @@ -136,7 +136,7 @@ Connect subreducer `uiState`, used with `injectComponents`. Learn more at **Parameters** -- `reduxState` **any** +- `reduxState` **any** ## visStateLens @@ -145,7 +145,7 @@ Connect subreducer `visState`, used with `injectComponents`. Learn more at **Parameters** -- `reduxState` **any** +- `reduxState` **any** [1]: #keplerglreducer diff --git a/docs/api-reference/reducers/ui-state.md b/docs/api-reference/reducers/ui-state.md index adb525131e..ba90d510aa 100644 --- a/docs/api-reference/reducers/ui-state.md +++ b/docs/api-reference/reducers/ui-state.md @@ -2,32 +2,37 @@ ### Table of Contents -- [uiStateUpdaters][1] - - [addNotificationUpdater][3] - - [cleanupExportImage][5] - - [DEFAULT_EXPORT_DATA][7] - - [DEFAULT_EXPORT_IMAGE][9] - - [DEFAULT_MAP_CONTROLS_FEATURES][11] - - [hideExportDropdownUpdater][13] - - [INITIAL_UI_STATE][15] - - [loadFilesErrUpdater][17] - - [loadFilesUpdater][19] - - [openDeleteModalUpdater][21] - - [removeNotificationUpdater][23] - - [setExportDataTypeUpdater][25] - - [setExportDataUpdater][27] - - [setExportFilteredUpdater][29] - - [setExportImageDataUri][31] - - [setExportImageSetting][33] - - [setExportSelectedDatasetUpdater][35] - - [showExportDropdownUpdater][37] - - [startExportingImage][39] - - [toggleMapControlUpdater][41] - - [toggleModalUpdater][43] - - [toggleSidePanelUpdater][45] - - [toggleSplitMapUpdater][47] -- [DEFAULT_EXPORT_HTML][49] -- [setUserMapboxAccessTokenUpdater][51] +- [uiStateUpdaters](#uistateupdaters) + - [addNotificationUpdater](#addnotificationupdater) + - [cleanupExportImage](#cleanupexportimage) + - [DEFAULT\_EXPORT\_DATA](#default_export_data) + - [Properties](#properties) + - [DEFAULT\_EXPORT\_IMAGE](#default_export_image) + - [Properties](#properties-1) + - [DEFAULT\_MAP\_CONTROLS\_FEATURES](#default_map_controls_features) + - [Properties](#properties-2) + - [hideExportDropdownUpdater](#hideexportdropdownupdater) + - [INITIAL\_UI\_STATE](#initial_ui_state) + - [Properties](#properties-3) + - [loadFilesErrUpdater](#loadfileserrupdater) + - [loadFilesUpdater](#loadfilesupdater) + - [openDeleteModalUpdater](#opendeletemodalupdater) + - [removeNotificationUpdater](#removenotificationupdater) + - [setExportDataTypeUpdater](#setexportdatatypeupdater) + - [setExportDataUpdater](#setexportdataupdater) + - [setExportFilteredUpdater](#setexportfilteredupdater) + - [setExportImageDataUri](#setexportimagedatauri) + - [setExportImageSetting](#setexportimagesetting) + - [setExportSelectedDatasetUpdater](#setexportselecteddatasetupdater) + - [showExportDropdownUpdater](#showexportdropdownupdater) + - [startExportingImage](#startexportingimage) + - [toggleMapControlUpdater](#togglemapcontrolupdater) + - [toggleModalUpdater](#togglemodalupdater) + - [toggleSidePanelUpdater](#togglesidepanelupdater) + - [toggleSplitMapUpdater](#togglesplitmapupdater) +- [DEFAULT\_EXPORT\_HTML](#default_export_html) + - [Properties](#properties-4) +- [setUserMapboxAccessTokenUpdater](#setusermapboxaccesstokenupdater) ## uiStateUpdaters @@ -37,7 +42,7 @@ Read more about [Using updaters][53] **Examples** ```javascript -import keplerGlReducer, {uiStateUpdaters} from 'kepler.gl/reducers'; +import keplerGlReducer, {uiStateUpdaters} from '@kepler.gl/reducers'; // Root Reducer const reducers = combineReducers({ keplerGl: keplerGlReducer, @@ -77,8 +82,8 @@ Existing notification is going to be updated in case of matching ids. **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** - - `action.payload` **[Object][55]** +- `action` **[Object][55]** + - `action.payload` **[Object][55]** Returns **[Object][55]** nextState @@ -174,9 +179,9 @@ Handles load file error and set fileLoading property to false **Parameters** -- `state` -- `error` **[Object][55]** - - `error.error` +- `state` +- `error` **[Object][55]** + - `error.error` Returns **[Object][55]** nextState @@ -201,7 +206,7 @@ Toggle active map control panel **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** +- `action` **[Object][55]** - `action.payload` **[string][57]** dataset id Returns **[Object][55]** nextState @@ -215,7 +220,7 @@ Remove a notification **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** +- `action` **[Object][55]** - `action.payload` **[String][57]** id of the notification to be removed Returns **[Object][55]** nextState @@ -229,7 +234,7 @@ Set data format for exporting data **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** +- `action` **[Object][55]** - `action.payload` **[string][57]** one of `'text/csv'` Returns **[Object][55]** nextState @@ -255,8 +260,8 @@ Whether to export filtered data, `true` or `false` **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** - - `action.payload` **[boolean][58]** +- `action` **[Object][55]** + - `action.payload` **[boolean][58]** Returns **[Object][55]** nextState @@ -269,7 +274,7 @@ Set `exportImage.setExportImageDataUri` to a image dataUri **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** +- `action` **[Object][55]** - `action.payload` **[string][57]** export image data uri Returns **[Object][55]** nextState @@ -283,8 +288,8 @@ Set `exportImage.legend` to `true` or `false` **Parameters** - `state` **[Object][55]** `uiState` -- `$1` **[Object][55]** - - `$1.payload` +- `$1` **[Object][55]** + - `$1.payload` Returns **[Object][55]** nextState @@ -297,7 +302,7 @@ Set selected dataset for export **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** +- `action` **[Object][55]** - `action.payload` **[string][57]** dataset id Returns **[Object][55]** nextState @@ -311,7 +316,7 @@ Hide and show side panel header dropdown, activated by clicking the share link o **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** +- `action` **[Object][55]** - `action.payload` **[string][57]** id of the dropdown Returns **[Object][55]** nextState @@ -339,7 +344,7 @@ Toggle active map control panel - `state` **[Object][55]** `uiState` - `action` **[Object][55]** action - `action.payload` **[string][57]** map control panel id, one of the keys of: [`DEFAULT_MAP_CONTROLS`][60] - - `action.payload.panelId` + - `action.payload.panelId` - `action.payload.index` (optional, default `0`) Returns **[Object][55]** nextState @@ -353,7 +358,7 @@ Show and hide modal dialog **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** +- `action` **[Object][55]** - `action.payload` **([string][57] | null)** id of modal to be shown, null to hide modals. One of:- [`DATA_TABLE_ID`][76] - [`DELETE_DATA_ID`][77] - [`ADD_DATA_ID`][78] @@ -372,7 +377,7 @@ Toggle active side panel **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** +- `action` **[Object][55]** - `action.payload` **([string][57] | null)** id of side panel to be shown, one of `layer`, `filter`, `interaction`, `map`. close side panel if `null` Returns **[Object][55]** nextState @@ -385,7 +390,7 @@ Handles toggle map split and reset all map control index to 0 **Parameters** -- `state` +- `state` Returns **[Object][55]** nextState @@ -407,8 +412,8 @@ whether to export a mapbox access to HTML single page **Parameters** - `state` **[Object][55]** `uiState` -- `action` **[Object][55]** - - `action.payload` **[string][57]** +- `action` **[Object][55]** + - `action.payload` **[string][57]** Returns **[Object][55]** nextState diff --git a/docs/api-reference/reducers/vis-state.md b/docs/api-reference/reducers/vis-state.md index d51bb329e2..10ca6e4c16 100644 --- a/docs/api-reference/reducers/vis-state.md +++ b/docs/api-reference/reducers/vis-state.md @@ -2,39 +2,40 @@ ### Table of Contents -- [visStateUpdaters][1] - - [addFilterUpdater][3] - - [addLayerUpdater][5] - - [applyCPUFilterUpdater][7] - - [enlargeFilterUpdater][9] - - [INITIAL_VIS_STATE][11] - - [interactionConfigChangeUpdater][13] - - [layerClickUpdater][15] - - [layerHoverUpdater][17] - - [layerTypeChangeUpdater][19] - - [layerVisConfigChangeUpdater][21] - - [layerVisualChannelChangeUpdater][23] - - [loadFilesErrUpdater][25] - - [loadFilesUpdater][27] - - [mapClickUpdater][29] - - [receiveMapConfigUpdater][31] - - [removeDatasetUpdater][33] - - [removeFilterUpdater][35] - - [removeLayerUpdater][37] - - [reorderLayerUpdater][39] - - [resetMapConfigUpdater][41] - - [setFilterPlotUpdater][43] - - [setFilterUpdater][45] - - [setMapInfoUpdater][47] - - [showDatasetTableUpdater][49] - - [toggleFilterAnimationUpdater][51] - - [toggleLayerForMapUpdater][53] - - [toggleSplitMapUpdater][55] - - [updateAnimationTimeUpdater][57] - - [updateFilterAnimationSpeedUpdater][59] - - [updateLayerAnimationSpeedUpdater][61] - - [updateLayerBlendingUpdater][63] - - [updateVisDataUpdater][65] +- [visStateUpdaters](#visstateupdaters) + - [addFilterUpdater](#addfilterupdater) + - [addLayerUpdater](#addlayerupdater) + - [applyCPUFilterUpdater](#applycpufilterupdater) + - [enlargeFilterUpdater](#enlargefilterupdater) + - [INITIAL\_VIS\_STATE](#initial_vis_state) + - [Properties](#properties) + - [interactionConfigChangeUpdater](#interactionconfigchangeupdater) + - [layerClickUpdater](#layerclickupdater) + - [layerHoverUpdater](#layerhoverupdater) + - [layerTypeChangeUpdater](#layertypechangeupdater) + - [layerVisConfigChangeUpdater](#layervisconfigchangeupdater) + - [layerVisualChannelChangeUpdater](#layervisualchannelchangeupdater) + - [loadFilesErrUpdater](#loadfileserrupdater) + - [loadFilesUpdater](#loadfilesupdater) + - [mapClickUpdater](#mapclickupdater) + - [receiveMapConfigUpdater](#receivemapconfigupdater) + - [removeDatasetUpdater](#removedatasetupdater) + - [removeFilterUpdater](#removefilterupdater) + - [removeLayerUpdater](#removelayerupdater) + - [reorderLayerUpdater](#reorderlayerupdater) + - [resetMapConfigUpdater](#resetmapconfigupdater) + - [setFilterPlotUpdater](#setfilterplotupdater) + - [setFilterUpdater](#setfilterupdater) + - [setMapInfoUpdater](#setmapinfoupdater) + - [showDatasetTableUpdater](#showdatasettableupdater) + - [toggleFilterAnimationUpdater](#togglefilteranimationupdater) + - [toggleLayerForMapUpdater](#togglelayerformapupdater) + - [toggleSplitMapUpdater](#togglesplitmapupdater) + - [updateAnimationTimeUpdater](#updateanimationtimeupdater) + - [updateFilterAnimationSpeedUpdater](#updatefilteranimationspeedupdater) + - [updateLayerAnimationSpeedUpdater](#updatelayeranimationspeedupdater) + - [updateLayerBlendingUpdater](#updatelayerblendingupdater) + - [updateVisDataUpdater](#updatevisdataupdater) ## visStateUpdaters @@ -44,7 +45,7 @@ Read more about [Using updaters][67] **Examples** ```javascript -import keplerGlReducer, {visStateUpdaters} from 'kepler.gl/reducers'; +import keplerGlReducer, {visStateUpdaters} from '@kepler.gl/reducers'; // Root Reducer const reducers = combineReducers({ keplerGl: keplerGlReducer, @@ -111,7 +112,7 @@ When select dataset for export, apply cpu filter to selected dataset **Parameters** - `state` **[Object][69]** `visState` -- `action` **[Object][69]** +- `action` **[Object][69]** - `action.dataId` **[string][70]** dataset id Returns **[Object][69]** nextState @@ -138,24 +139,24 @@ Type: [Object][69] #### Properties -- `layers` **[Array][75]** -- `layerData` **[Array][75]** -- `layerToBeMerged` **[Array][75]** -- `layerOrder` **[Array][75]** -- `filters` **[Array][75]** -- `filterToBeMerged` **[Array][75]** -- `datasets` **[Array][75]** -- `editingDataset` **[string][70]** -- `interactionConfig` **[Object][69]** -- `interactionToBeMerged` **[Object][69]** -- `layerBlending` **[string][70]** -- `hoverInfo` **[Object][69]** -- `clicked` **[Object][69]** -- `mousePos` **[Object][69]** +- `layers` **[Array][75]** +- `layerData` **[Array][75]** +- `layerToBeMerged` **[Array][75]** +- `layerOrder` **[Array][75]** +- `filters` **[Array][75]** +- `filterToBeMerged` **[Array][75]** +- `datasets` **[Array][75]** +- `editingDataset` **[string][70]** +- `interactionConfig` **[Object][69]** +- `interactionToBeMerged` **[Object][69]** +- `layerBlending` **[string][70]** +- `hoverInfo` **[Object][69]** +- `clicked` **[Object][69]** +- `mousePos` **[Object][69]** - `splitMaps` **[Array][75]** a list of objects of layer availabilities and visibilities for each map -- `layerClasses` **[Object][69]** -- `animationConfig` **[Object][69]** -- `editor` **[Object][69]** +- `layerClasses` **[Object][69]** +- `animationConfig` **[Object][69]** +- `editor` **[Object][69]** ### interactionConfigChangeUpdater @@ -255,7 +256,7 @@ Trigger loading file error - `state` **[Object][69]** `visState` - `action` **[Object][69]** action - - `action.error` **any** + - `action.error` **any** Returns **[Object][69]** nextState @@ -381,7 +382,7 @@ Set the property of a filter plot - `state` **[Object][69]** `visState` - `action` **[Object][69]** action - - `action.idx` **[Number][74]** + - `action.idx` **[Number][74]** - `action.newProp` **[Object][69]** key value mapping of new prop `{yAxis: 'histogram'}` Returns **[Object][69]** nextState @@ -453,8 +454,8 @@ Toggle visibility of a layer in a split map **Parameters** -- `state` **[Object][69]** -- `action` **[Object][69]** +- `state` **[Object][69]** +- `action` **[Object][69]** - `action.mapIndex` **[Number][74]** index of the split map - `action.layerId` **[string][70]** id of the layer diff --git a/examples/demo-app/package.json b/examples/demo-app/package.json index 43e43dd1fa..a0530c634b 100644 --- a/examples/demo-app/package.json +++ b/examples/demo-app/package.json @@ -44,6 +44,7 @@ "react-virtualized": "^9.21.0", "redux": "^4.2.1", "redux-actions": "^2.2.1", + "redux-logger": "^3.0.6", "redux-thunk": "^1.0.0", "styled-components": "^4.1.3" }, diff --git a/examples/demo-app/src/app.js b/examples/demo-app/src/app.tsx similarity index 62% rename from examples/demo-app/src/app.js rename to examples/demo-app/src/app.tsx index 8be0057a41..500c076993 100644 --- a/examples/demo-app/src/app.js +++ b/examples/demo-app/src/app.tsx @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project -import React, {Component} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer'; import styled, {ThemeProvider} from 'styled-components'; import window from 'global/window'; -import {connect} from 'react-redux'; +import {connect, useDispatch} from 'react-redux'; import cloneDeep from 'lodash.clonedeep'; import {ScreenshotWrapper} from 'react-ai-assist'; @@ -30,7 +30,7 @@ import { onLoadCloudMapSuccess } from './actions'; -import {loadCloudMap, addDataToMap, addNotification, replaceDataInMap} from '@kepler.gl/actions'; +import {loadCloudMap, addDataToMap, replaceDataInMap} from '@kepler.gl/actions'; import {CLOUD_PROVIDERS} from './cloud-providers'; const KeplerGl = require('@kepler.gl/components').injectComponents([ @@ -48,7 +48,7 @@ import sampleGeojsonConfig from './data/sample-geojson-config'; import sampleH3Data, {config as h3MapConfig} from './data/sample-hex-id-csv'; import sampleS2Data, {config as s2MapConfig, dataId as s2DataId} from './data/sample-s2-data'; import sampleAnimateTrip, {animateTripDataId} from './data/sample-animate-trip-data'; -import sampleIconCsv, {config as savedMapConfig} from './data/sample-icon-csv'; +import sampleIconCsv from './data/sample-icon-csv'; import sampleGpsData from './data/sample-gps-data'; import sampleRowData, {config as rowDataConfig} from './data/sample-row-data'; import {processCsvData, processGeojson, processRowObject} from '@kepler.gl/processors'; @@ -93,24 +93,23 @@ const CONTAINER_STYLE = { width: '100%', height: '100%', left: 0, - top: 0 + top: 0, + display: 'flex', + flexDirection: 'row' }; -class App extends Component { - state = { - showBanner: false, - width: window.innerWidth, - height: window.innerHeight - }; +const App = props => { + const [showBanner, toggleShowBanner] = useState(false); + const {params: {id, provider} = {}, location: {query = {}} = {}} = props; + const dispatch = useDispatch(); - componentDidMount() { + useEffect(() => { // if we pass an id as part of the url // we ry to fetch along map configurations - const {params: {id, provider} = {}, location: {query = {}} = {}} = this.props; const cloudProvider = CLOUD_PROVIDERS.find(c => c.name === provider); if (cloudProvider) { - this.props.dispatch( + dispatch( loadCloudMap({ loadParams: query, provider: cloudProvider, @@ -122,83 +121,56 @@ class App extends Component { // Load sample using its id if (id) { - this.props.dispatch(loadSampleConfigurations(id)); + dispatch(loadSampleConfigurations(id)); } // Load map using a custom if (query.mapUrl) { // TODO?: validate map url - this.props.dispatch(loadRemoteMap({dataUrl: query.mapUrl})); + dispatch(loadRemoteMap({dataUrl: query.mapUrl})); } // delay zs to show the banner // if (!window.localStorage.getItem(BannerKey)) { - // window.setTimeout(this._showBanner, 3000); + // window.setTimeout(_showBanner, 3000); // } // load sample data - // this._loadSampleData(); + // _loadSampleData(); // Notifications - // this._loadMockNotifications(); - } - - _setStartScreenCapture = flag => { - this.props.dispatch(setStartScreenCapture(flag)); - }; + // _loadMockNotifications(); + }, [dispatch, id, provider, query]); + + const _setStartScreenCapture = useCallback( + flag => { + dispatch(setStartScreenCapture(flag)); + }, + [dispatch] + ); - _setScreenCaptured = screenshot => { - this.props.dispatch(setScreenCaptured(screenshot)); - }; + const _setScreenCaptured = useCallback( + screenshot => { + dispatch(setScreenCaptured(screenshot)); + }, + [dispatch] + ); - _showBanner = () => { - this.setState({showBanner: true}); - }; + // eslint-disable-next-line no-unused-vars + const _showBanner = useCallback(() => { + toggleShowBanner(true); + }, [toggleShowBanner]); - _hideBanner = () => { - this.setState({showBanner: false}); - }; + const hideBanner = useCallback(() => { + toggleShowBanner(false); + }, [toggleShowBanner]); - _disableBanner = () => { - this._hideBanner(); + const _disableBanner = useCallback(() => { + hideBanner(); window.localStorage.setItem(BannerKey, 'true'); - }; - - _loadMockNotifications = () => { - const notifications = [ - [{message: 'Welcome to Kepler.gl'}, 3000], - [{message: 'Something is wrong', type: 'error'}, 1000], - [{message: 'I am getting better', type: 'warning'}, 1000], - [{message: 'Everything is fine', type: 'success'}, 1000] - ]; - - this._addNotifications(notifications); - }; - - _addNotifications(notifications) { - if (notifications && notifications.length) { - const [notification, timeout] = notifications[0]; - - window.setTimeout(() => { - this.props.dispatch(addNotification(notification)); - this._addNotifications(notifications.slice(1)); - }, timeout); - } - } + }, [hideBanner]); - _loadSampleData() { - // this._loadPointData(); - // this._loadGeojsonData(); - // this._loadTripGeoJson(); - // this._loadIconData(); - // this._loadH3HexagonData(); - // this._loadS2Data(); - // this._loadScenegraphLayer(); - // this._loadGpsData(); - // this._loadRowData(); - } - - _loadRowData() { - this.props.dispatch( + const _loadRowData = useCallback(() => { + dispatch( addDataToMap({ datasets: [ { @@ -212,10 +184,10 @@ class App extends Component { config: rowDataConfig }) ); - } + }, [dispatch]); - _loadPointData() { - this.props.dispatch( + const _loadPointData = useCallback(() => { + dispatch( addDataToMap({ datasets: [ { @@ -248,10 +220,10 @@ class App extends Component { config: sampleTripDataConfig }) ); - } + }, [dispatch]); - _loadScenegraphLayer() { - this.props.dispatch( + const _loadScenegraphLayer = useCallback(() => { + dispatch( addDataToMap({ datasets: { info: { @@ -282,11 +254,11 @@ class App extends Component { } }) ); - } + }, [dispatch]); - _loadIconData() { + const _loadIconData = useCallback(() => { // load icon data and config and process csv file - this.props.dispatch( + dispatch( addDataToMap({ datasets: [ { @@ -299,10 +271,10 @@ class App extends Component { ] }) ); - } + }, [dispatch]); - _loadTripGeoJson() { - this.props.dispatch( + const _loadTripGeoJson = useCallback(() => { + dispatch( addDataToMap({ datasets: [ { @@ -312,17 +284,45 @@ class App extends Component { ] }) ); - } + }, [dispatch]); - _replaceData = () => { + const _loadGeojsonData = useCallback(() => { + // load geojson + const geojsonPoints = processGeojson(sampleGeojsonPoints); + const geojsonZip = processGeojson(sampleGeojson); + dispatch( + addDataToMap({ + datasets: [ + geojsonPoints + ? { + info: {label: 'Bart Stops Geo', id: 'bart-stops-geo'}, + data: geojsonPoints + } + : null, + geojsonZip + ? { + info: {label: 'SF Zip Geo', id: 'sf-zip-geo'}, + data: geojsonZip + } + : null + ].filter(d => d !== null), + options: { + keepExistingConfig: true + }, + config: sampleGeojsonConfig + }) + ); + }, [dispatch]); + + const _replaceData = useCallback(() => { // add geojson data const sliceData = processGeojson({ type: 'FeatureCollection', features: sampleGeojsonPoints.features.slice(0, 5) }); - this._loadGeojsonData(); + _loadGeojsonData(); window.setTimeout(() => { - this.props.dispatch( + dispatch( replaceDataInMap({ datasetToReplaceId: 'bart-stops-geo', datasetToUse: { @@ -332,33 +332,11 @@ class App extends Component { }) ); }, 1000); - }; - - _loadGeojsonData() { - // load geojson - this.props.dispatch( - addDataToMap({ - datasets: [ - { - info: {label: 'Bart Stops Geo', id: 'bart-stops-geo'}, - data: processGeojson(sampleGeojsonPoints) - } - // { - // info: {label: 'SF Zip Geo', id: 'sf-zip-geo'}, - // data: processGeojson(sampleGeojson) - // } - ], - options: { - keepExistingConfig: true - }, - config: sampleGeojsonConfig - }) - ); - } + }, [dispatch, _loadGeojsonData]); - _loadH3HexagonData() { + const _loadH3HexagonData = useCallback(() => { // load h3 hexagon - this.props.dispatch( + dispatch( addDataToMap({ datasets: [ { @@ -375,11 +353,11 @@ class App extends Component { } }) ); - } + }, [dispatch]); - _loadS2Data() { + const _loadS2Data = useCallback(() => { // load s2 - this.props.dispatch( + dispatch( addDataToMap({ datasets: [ { @@ -396,10 +374,10 @@ class App extends Component { } }) ); - } + }, [dispatch]); - _loadGpsData() { - this.props.dispatch( + const _loadGpsData = useCallback(() => { + dispatch( addDataToMap({ datasets: [ { @@ -415,66 +393,66 @@ class App extends Component { } }) ); - } - _toggleCloudModal = () => { - // TODO: this lives only in the demo hence we use the state for now - // REFCOTOR using redux - this.setState({ - cloudModalOpen: !this.state.cloudModalOpen - }); - }; - - _getMapboxRef = mapbox => { - if (!mapbox) { - // The ref has been unset. - // https://reactjs.org/docs/refs-and-the-dom.html#callback-refs - // console.log(`Map ${index} has closed`); - } else { - // We expect an Map created by KeplerGl's MapContainer. - // https://visgl.github.io/react-map-gl/docs/api-reference/map - const map = mapbox.getMap(); - map.on('zoomend', () => { - // console.log(`Map ${index} zoom level: ${e.target.style.z}`); - }); - } - }; - - combinedMessages = Object.keys(messages).reduce( - (acc, language) => ({ - ...acc, - [language]: { - ...(messages[language] || {}), - ...(aiAssistantMessages[language] || {}) - } - }), - {} - ); - - render() { - return ( - <ThemeProvider theme={theme}> - <GlobalStyle - // this is to apply the same modal style as kepler.gl core - // because styled-components doesn't always return a node - // https://github.com/styled-components/styled-components/issues/617 - ref={node => { - node ? (this.root = node) : null; - }} + }, [dispatch]); + + const combinedMessages = useMemo(() => { + return Object.keys(messages).reduce( + (acc, language) => ({ + ...acc, + [language]: { + ...(messages[language] || {}), + ...(aiAssistantMessages[language] || {}) + } + }), + {} + ); + }, []); + + // eslint-disable-next-line no-unused-vars + const _loadSampleData = useCallback(() => { + // _loadPointData(); + // _loadGeojsonData(); + // _loadTripGeoJson(); + // _loadIconData(); + // _loadH3HexagonData(); + // _loadS2Data(); + // _loadScenegraphLayer(); + // _loadGpsData(); + // _loadRowData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + _loadPointData, + _loadGeojsonData, + _loadTripGeoJson, + _loadIconData, + _loadH3HexagonData, + _loadS2Data, + _loadScenegraphLayer, + _loadGpsData, + _loadRowData, + _replaceData + ]); + + return ( + <ThemeProvider theme={theme}> + <GlobalStyle + // this is to apply the same modal style as kepler.gl core + // because styled-components doesn't always return a node + // https://github.com/styled-components/styled-components/issues/617 + // ref={node => { + // node ? (this.root = node) : null; + // }} + > + <ScreenshotWrapper + startScreenCapture={props.demo.aiAssistant.screenshotToAsk.startScreenCapture} + setScreenCaptured={_setScreenCaptured} + setStartScreenCapture={_setStartScreenCapture} > - <ScreenshotWrapper - startScreenCapture={this.props.demo.aiAssistant.screenshotToAsk.startScreenCapture} - setScreenCaptured={this._setScreenCaptured} - setStartScreenCapture={this._setStartScreenCapture} - > - <Banner - show={this.state.showBanner} - height={BannerHeight} - bgColor="#2E7CF6" - onClose={this._hideBanner} - > - <Announcement onDisable={this._disableBanner} /> - </Banner> - <div style={CONTAINER_STYLE}> + <Banner show={showBanner} height={BannerHeight} bgColor="#2E7CF6" onClose={hideBanner}> + <Announcement onDisable={_disableBanner} /> + </Banner> + <div style={CONTAINER_STYLE}> + <div style={{flexGrow: 1}}> <AutoSizer> {({height, width}) => ( <KeplerGl @@ -487,7 +465,7 @@ class App extends Component { width={width} height={height} cloudProviders={CLOUD_PROVIDERS} - localeMessages={this.combinedMessages} + localeMessages={combinedMessages} onExportToCloudSuccess={onExportFileSuccess} onLoadCloudMapSuccess={onLoadCloudMapSuccess} featureFlags={DEFAULT_FEATURE_FLAGS} @@ -495,12 +473,12 @@ class App extends Component { )} </AutoSizer> </div> - </ScreenshotWrapper> - </GlobalStyle> - </ThemeProvider> - ); - } -} + </div> + </ScreenshotWrapper> + </GlobalStyle> + </ThemeProvider> + ); +}; const mapStateToProps = state => state; const dispatchToProps = dispatch => ({dispatch}); diff --git a/examples/demo-app/src/store.js b/examples/demo-app/src/store.js index 50841d8cc8..fba46ea6cc 100644 --- a/examples/demo-app/src/store.js +++ b/examples/demo-app/src/store.js @@ -6,7 +6,7 @@ import {routerReducer, routerMiddleware} from 'react-router-redux'; import {browserHistory} from 'react-router'; import {createLogger} from 'redux-logger'; import {enhanceReduxMiddleware} from '@kepler.gl/reducers'; -import thunk from 'redux-thunk'; +import {createLogger} from 'redux-logger'; // eslint-disable-next-line no-unused-vars import window from 'global/window'; diff --git a/package.json b/package.json index 68cd97c61c..906f66aa45 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "kepler.gl", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl is a webgl based application to visualize large scale location data in the browser", "license": "MIT", "main": "dist/index.js", @@ -18,6 +18,7 @@ "workspaces": [ "./src/types", "./src/constants", + "./src/common-utils", "./src/utils", "./src/styles", "./src/localization", @@ -102,7 +103,7 @@ "@deck.gl/mapbox": "^8.9.27", "@hubble.gl/core": "1.2.0-alpha.6", "@hubble.gl/react": "1.2.0-alpha.6", - "@kepler.gl/components": "3.0.0", + "@kepler.gl/components": "3.1.0-alpha.0", "@loaders.gl/polyfills": "^4.3.2", "@types/mapbox__geo-viewport": "^0.4.1", "html-webpack-plugin": "^4.3.0", diff --git a/src/actions/package.json b/src/actions/package.json index b945bd38e8..e15d30c7d3 100644 --- a/src/actions/package.json +++ b/src/actions/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/actions", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -31,11 +31,11 @@ ], "dependencies": { "@deck.gl/core": "^8.9.27", - "@kepler.gl/cloud-providers": "3.0.0", - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/layers": "3.0.0", - "@kepler.gl/processors": "3.0.0", - "@kepler.gl/types": "3.0.0", + "@kepler.gl/cloud-providers": "3.1.0-alpha.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/layers": "3.1.0-alpha.0", + "@kepler.gl/processors": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", "@reduxjs/toolkit": "^1.7.2", "@types/lodash.curry": "^4.1.7", "@types/react-redux": "^7.1.23", diff --git a/src/actions/src/action-types.ts b/src/actions/src/action-types.ts index dfa8f59651..f9e0cf0dc8 100644 --- a/src/actions/src/action-types.ts +++ b/src/actions/src/action-types.ts @@ -43,6 +43,7 @@ export const ActionTypes = { ADD_DATA: `${ACTION_PREFIX}ADD_DATA`, ADD_FILTER: `${ACTION_PREFIX}ADD_FILTER`, CREATE_OR_UPDATE_FILTER: `${ACTION_PREFIX}CREATE_OR_UPDATE_FILTER`, + CREATE_NEW_DATASET_SUCCESS: `${ACTION_PREFIX}CREATE_NEW_DATASET_SUCCESS`, ADD_LAYER: `${ACTION_PREFIX}ADD_LAYER`, APPLY_LAYER_CONFIG: `${ACTION_PREFIX}APPLY_LAYER_CONFIG`, DUPLICATE_LAYER: `${ACTION_PREFIX}DUPLICATE_LAYER`, @@ -190,6 +191,7 @@ export const ActionTypes = { }; // eslint-disable-next-line prettier/prettier -const assignType = <T>(obj: T): { [K in keyof T]: `${typeof ACTION_PREFIX}${string & K}`; } => obj as any +const assignType = <T>(obj: T): {[K in keyof T]: `${typeof ACTION_PREFIX}${string & K}`} => + obj as any; export default assignType(ActionTypes); diff --git a/src/actions/src/vis-state-actions.ts b/src/actions/src/vis-state-actions.ts index 2775d91ec4..73c2b77740 100644 --- a/src/actions/src/vis-state-actions.ts +++ b/src/actions/src/vis-state-actions.ts @@ -23,6 +23,9 @@ import { EffectPropsPartial, SyncTimelineMode } from '@kepler.gl/types'; +import {createAction} from '@reduxjs/toolkit'; + +import {KeplerTable} from '@kepler.gl/table'; // TODO - import LoaderObject type from @loaders.gl/core when supported // TODO - import LoadOptions type from @loaders.gl/core when supported @@ -1596,6 +1599,21 @@ export function setTimeFilterSyncTimelineMode({ }; } +export type CreateNewDatasetSuccessPayload = { + results: (PromiseFulfilledResult<KeplerTable<any>> | PromiseRejectedResult)[]; + addToMapOptions: AddDataToMapPayload['options']; +}; + +/** + * Called when a new dataset is created successfully via async table methods + * @param payload + * @param payload.results - results of promises.allSettlted + * @returns + */ +export const createNewDatasetSuccess = createAction<CreateNewDatasetSuccessPayload>( + ActionTypes.CREATE_NEW_DATASET_SUCCESS +); + /** * This declaration is needed to group actions in docs */ diff --git a/src/ai-assistant/package.json b/src/ai-assistant/package.json index 565b8ab921..4b382a392c 100644 --- a/src/ai-assistant/package.json +++ b/src/ai-assistant/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/ai-assistant", "author": "Xun Li<lixun910@gmail.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl AI assistant", "license": "MIT", "main": "dist/index.js", @@ -30,11 +30,11 @@ "umd" ], "dependencies": { - "@kepler.gl/components": "3.0.0", - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/layers": "3.0.0", - "@kepler.gl/types": "3.0.0", - "@kepler.gl/utils": "3.0.0", + "@kepler.gl/components": "3.1.0-alpha.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/layers": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", + "@kepler.gl/utils": "3.1.0-alpha.0", "global": "^4.3.0", "react-ai-assist": "0.0.14" }, diff --git a/src/cloud-providers/package.json b/src/cloud-providers/package.json index fca083c37e..237ef37adc 100644 --- a/src/cloud-providers/package.json +++ b/src/cloud-providers/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/cloud-providers", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -30,7 +30,7 @@ "umd" ], "dependencies": { - "@kepler.gl/types": "3.0.0", + "@kepler.gl/types": "3.1.0-alpha.0", "react": "^18.2.0" }, "nyc": { diff --git a/src/common-utils/babel.config.js b/src/common-utils/babel.config.js new file mode 100644 index 0000000000..eb16706b69 --- /dev/null +++ b/src/common-utils/babel.config.js @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +// Copyright contributors to the kepler.gl project + +const KeplerPackage = require('./package'); + +const PRESETS = ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript']; +const PLUGINS = [ + ['@babel/plugin-transform-typescript', {isTSX: true, allowDeclareFields: true}], + '@babel/plugin-transform-modules-commonjs', + '@babel/plugin-transform-class-properties', + '@babel/plugin-transform-optional-chaining', + '@babel/plugin-transform-logical-assignment-operators', + '@babel/plugin-transform-nullish-coalescing-operator', + '@babel/plugin-transform-export-namespace-from', + [ + '@babel/transform-runtime', + { + regenerator: true + } + ], + [ + 'search-and-replace', + { + rules: [ + { + search: '__PACKAGE_VERSION__', + replace: KeplerPackage.version + } + ] + } + ] +]; +const ENV = { + test: { + plugins: ['istanbul'] + }, + debug: { + sourceMaps: 'inline', + retainLines: true + } +}; + +module.exports = function babel(api) { + api.cache(true); + + return { + presets: PRESETS, + plugins: PLUGINS, + env: ENV + }; +}; diff --git a/src/common-utils/package.json b/src/common-utils/package.json new file mode 100644 index 0000000000..7f9aff0250 --- /dev/null +++ b/src/common-utils/package.json @@ -0,0 +1,55 @@ +{ + "name": "@kepler.gl/common-utils", + "author": "Shan He <heshan0131@gmail.com>", + "version": "3.1.0-alpha.0", + "description": "kepler.gl common utils", + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "keywords": [ + "babel", + "es6", + "react", + "webgl", + "visualization", + "deck.gl" + ], + "repository": { + "type": "git", + "url": "https://github.com/keplergl/kepler.gl.git" + }, + "scripts": { + "build": "rm -fr dist && babel src --out-dir dist --source-maps inline --extensions '.ts,.tsx,.js,.jsx' --ignore '**/*.d.ts'", + "build:umd": "NODE_OPTIONS=--openssl-legacy-provider webpack --config ./webpack/umd.js --progress --env.prod", + "build:types": "tsc --project ./tsconfig.production.json", + "prepublish": "babel-node ../../scripts/license-header/bin --license ../../FILE-HEADER && yarn build && yarn build:types", + "stab": "mkdir -p dist && touch dist/index.js" + }, + "files": [ + "dist", + "umd" + ], + "dependencies": { + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", + "d3-array": "^2.8.0", + "global": "^4.3.0", + "type-analyzer": "0.4.0" + }, + "nyc": { + "sourceMap": false, + "instrument": false + }, + "maintainers": [ + "Shan He <heshan0131@gmail.com>", + "Igor Dykhta <dikhta.igor@gmail.com>" + ], + "engines": { + "node": ">=18" + }, + "volta": { + "node": "18.18.2", + "yarn": "4.4.0" + }, + "packageManager": "yarn@4.4.0" +} diff --git a/src/common-utils/src/data-type.ts b/src/common-utils/src/data-type.ts new file mode 100644 index 0000000000..19740e6831 --- /dev/null +++ b/src/common-utils/src/data-type.ts @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: MIT +// Copyright contributors to the kepler.gl project + +import {Analyzer, DATA_TYPES as AnalyzerDATA_TYPES} from 'type-analyzer'; +import {RowData, Field} from '@kepler.gl/types'; +import {ALL_FIELD_TYPES} from '@kepler.gl/constants'; +import {console as globalConsole} from 'global/window'; +import {range} from 'd3-array'; +import {isHexWkb, notNullorUndefined} from './data'; + +export const ACCEPTED_ANALYZER_TYPES = [ + AnalyzerDATA_TYPES.DATE, + AnalyzerDATA_TYPES.TIME, + AnalyzerDATA_TYPES.DATETIME, + AnalyzerDATA_TYPES.NUMBER, + AnalyzerDATA_TYPES.INT, + AnalyzerDATA_TYPES.FLOAT, + AnalyzerDATA_TYPES.BOOLEAN, + AnalyzerDATA_TYPES.STRING, + AnalyzerDATA_TYPES.GEOMETRY, + AnalyzerDATA_TYPES.GEOMETRY_FROM_STRING, + AnalyzerDATA_TYPES.PAIR_GEOMETRY_FROM_STRING, + AnalyzerDATA_TYPES.ZIPCODE, + AnalyzerDATA_TYPES.ARRAY, + AnalyzerDATA_TYPES.OBJECT +]; + +const IGNORE_DATA_TYPES = Object.keys(AnalyzerDATA_TYPES).filter( + type => !ACCEPTED_ANALYZER_TYPES.includes(type) +); + +/** + * Getting sample data for analyzing field type. + */ +export function getSampleForTypeAnalyze({ + fields, + rows, + sampleCount = 50 +}: { + fields: string[]; + rows: unknown[][] | RowData; + sampleCount?: number; +}): RowData { + const total = Math.min(sampleCount, rows.length); + // const fieldOrder = fields.map(f => f.name); + const sample = range(0, total, 1).map(() => ({})); + + if (rows.length < 1) { + return []; + } + const isRowObject = !Array.isArray(rows[0]); + + // collect sample data for each field + fields.forEach((field, fieldIdx) => { + // row counter + let i = 0; + // sample counter + let j = 0; + + while (j < total) { + if (i >= rows.length) { + // if depleted data pool + sample[j][field] = null; + j++; + } else if (notNullorUndefined(rows[i][isRowObject ? field : fieldIdx])) { + const value = rows[i][isRowObject ? field : fieldIdx]; + sample[j][field] = typeof value === 'string' ? value.trim() : value; + j++; + i++; + } else { + i++; + } + } + }); + + return sample; +} + +/** + * Convert type-analyzer output to kepler.gl field types + * + * @param aType + * @returns corresponding type in `ALL_FIELD_TYPES` + */ +/* eslint-disable complexity */ +export function analyzerTypeToFieldType(aType: string): string { + const { + DATE, + TIME, + DATETIME, + NUMBER, + INT, + FLOAT, + BOOLEAN, + STRING, + GEOMETRY, + GEOMETRY_FROM_STRING, + PAIR_GEOMETRY_FROM_STRING, + ZIPCODE, + ARRAY, + OBJECT + } = AnalyzerDATA_TYPES; + + // TODO: un recognized types + // CURRENCY PERCENT NONE + switch (aType) { + case DATE: + return ALL_FIELD_TYPES.date; + case TIME: + case DATETIME: + return ALL_FIELD_TYPES.timestamp; + case FLOAT: + return ALL_FIELD_TYPES.real; + case INT: + return ALL_FIELD_TYPES.integer; + case BOOLEAN: + return ALL_FIELD_TYPES.boolean; + case GEOMETRY: + case GEOMETRY_FROM_STRING: + case PAIR_GEOMETRY_FROM_STRING: + return ALL_FIELD_TYPES.geojson; + case ARRAY: + return ALL_FIELD_TYPES.array; + case OBJECT: + return ALL_FIELD_TYPES.object; + case NUMBER: + case STRING: + case ZIPCODE: + return ALL_FIELD_TYPES.string; + default: + globalConsole.warn(`Unsupported analyzer type: ${aType}`); + return ALL_FIELD_TYPES.string; + } +} + +/** + * Analyze field types from data in `string` format, e.g. uploaded csv. + * Assign `type`, `fieldIdx` and `format` (timestamp only) to each field + * + * @param data array of row object + * @param fieldOrder array of field names as string + * @returns formatted fields + * @public + * @example + * + * import {getFieldsFromData} from 'kepler.gl/common-utils'; + * const data = [{ + * time: '2016-09-17 00:09:55', + * value: '4', + * surge: '1.2', + * isTrip: 'true', + * zeroOnes: '0' + * }, { + * time: '2016-09-17 00:30:08', + * value: '3', + * surge: null, + * isTrip: 'false', + * zeroOnes: '1' + * }, { + * time: null, + * value: '2', + * surge: '1.3', + * isTrip: null, + * zeroOnes: '1' + * }]; + * + * const fieldOrder = ['time', 'value', 'surge', 'isTrip', 'zeroOnes']; + * const fields = getFieldsFromData(data, fieldOrder); + * // fields = [ + * // {name: 'time', format: 'YYYY-M-D H:m:s', fieldIdx: 1, type: 'timestamp'}, + * // {name: 'value', format: '', fieldIdx: 4, type: 'integer'}, + * // {name: 'surge', format: '', fieldIdx: 5, type: 'real'}, + * // {name: 'isTrip', format: '', fieldIdx: 6, type: 'boolean'}, + * // {name: 'zeroOnes', format: '', fieldIdx: 7, type: 'integer'}]; + * + */ +export function getFieldsFromData(data: RowData, fieldOrder: string[]): Field[] { + // add a check for epoch timestamp + const metadata = Analyzer.computeColMeta( + data, + [ + {regex: /.*geojson|all_points/g, dataType: 'GEOMETRY'}, + {regex: /.*census/g, dataType: 'STRING'} + ], + {ignoredDataTypes: IGNORE_DATA_TYPES} + ); + + const {fieldByIndex} = renameDuplicateFields(fieldOrder); + + const result = fieldOrder.map((field, index) => { + const name = fieldByIndex[index]; + + const fieldMeta = metadata.find(m => m.key === field); + + // fieldMeta could be undefined if the field has no data and Analyzer.computeColMeta + // will ignore the field. In this case, we will simply assign the field type to STRING + // since dropping the column in the RowData could be expensive + let type = fieldMeta?.type || 'STRING'; + const format = fieldMeta?.format || ''; + + // check if string is hex wkb + if (type === AnalyzerDATA_TYPES.STRING) { + type = data.some(d => isHexWkb(d[name])) ? AnalyzerDATA_TYPES.GEOMETRY : type; + } + + return { + name, + id: name, + displayName: name, + format, + fieldIdx: index, + type: analyzerTypeToFieldType(type), + analyzerType: type, + valueAccessor: dc => d => { + return dc.valueAt(d.index, index); + } + }; + }); + + return result; +} + +/** + * pass in an array of field names, rename duplicated one + * and return a map from old field index to new name + * + * @param fieldOrder + * @returns new field name by index + */ +export function renameDuplicateFields(fieldOrder: string[]): { + allNames: string[]; + fieldByIndex: string[]; +} { + return fieldOrder.reduce<{allNames: string[]; fieldByIndex: string[]}>( + (accu, field, i) => { + const {allNames} = accu; + let fieldName = field; + + // add a counter to duplicated names + if (allNames.includes(field)) { + let counter = 0; + while (allNames.includes(`${field}-${counter}`)) { + counter++; + } + fieldName = `${field}-${counter}`; + } + + accu.fieldByIndex[i] = fieldName; + accu.allNames.push(fieldName); + + return accu; + }, + {allNames: [], fieldByIndex: []} + ); +} diff --git a/src/common-utils/src/data.ts b/src/common-utils/src/data.ts new file mode 100644 index 0000000000..18bf7ba48a --- /dev/null +++ b/src/common-utils/src/data.ts @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +// Copyright contributors to the kepler.gl project + +export function notNullorUndefined<T extends NonNullable<any>>(d: T | null | undefined): d is T { + return d !== undefined && d !== null; +} + +/** + * Check if string is a valid Well-known binary (WKB) in HEX format + * https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry + * + * @param str input string + * @returns true if string is a valid WKB in HEX format + */ +export function isHexWkb(str: string | null): boolean { + if (!str) return false; + // check if the length of the string is even and is at least 10 characters long + if (str.length < 10 || str.length % 2 !== 0) { + return false; + } + // check if first two characters are 00 or 01 + if (!str.startsWith('00') && !str.startsWith('01')) { + return false; + } + // check if the rest of the string is a valid hex + return /^[0-9a-fA-F]+$/.test(str.slice(2)); +} + +/** + * Converts non-arrays to arrays. Leaves arrays alone. Converts + * undefined values to empty arrays ([] instead of [undefined]). + * Otherwise, just returns [item] for non-array items. + * + * @param {*} item + * @returns {array} boom! much array. very indexed. so useful. + */ +export function toArray<T>(item: T | T[]): T[] { + if (Array.isArray(item)) { + return item; + } + + if (typeof item === 'undefined' || item === null) { + return []; + } + + return [item]; +} diff --git a/src/common-utils/src/index.ts b/src/common-utils/src/index.ts new file mode 100644 index 0000000000..fd626ebbbd --- /dev/null +++ b/src/common-utils/src/index.ts @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// Copyright contributors to the kepler.gl project + +export * from './data'; +export * from './data-type'; +export * from './string'; diff --git a/src/common-utils/src/string.ts b/src/common-utils/src/string.ts new file mode 100644 index 0000000000..3b6fc93559 --- /dev/null +++ b/src/common-utils/src/string.ts @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Copyright contributors to the kepler.gl project + +/** + * Generate a hash string based on number of character + * @param {number} count + * @returns {string} hash string + */ +export function generateHashId(count = 6): string { + return Math.random().toString(36).substr(count); +} diff --git a/src/common-utils/tsconfig.production.json b/src/common-utils/tsconfig.production.json new file mode 100644 index 0000000000..f8ee4aa488 --- /dev/null +++ b/src/common-utils/tsconfig.production.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es2020", + "allowJs": false, + "checkJs": false, + "jsx": "react", + "module": "esnext", + "moduleResolution": "node", + "declaration": true, + "emitDeclarationOnly": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "outDir": "dist", + "sourceMap": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": false, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "baseUrl": "./src" + }, + "include": ["src"] +} diff --git a/src/components/package.json b/src/components/package.json index d49b469b4c..04c0158672 100644 --- a/src/components/package.json +++ b/src/components/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/components", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -37,19 +37,20 @@ "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", "@floating-ui/react": "0.25.1", - "@kepler.gl/actions": "3.0.0", - "@kepler.gl/cloud-providers": "3.0.0", - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/effects": "3.0.0", - "@kepler.gl/layers": "3.0.0", - "@kepler.gl/localization": "3.0.0", - "@kepler.gl/processors": "3.0.0", - "@kepler.gl/reducers": "3.0.0", - "@kepler.gl/schemas": "3.0.0", - "@kepler.gl/styles": "3.0.0", - "@kepler.gl/table": "3.0.0", - "@kepler.gl/types": "3.0.0", - "@kepler.gl/utils": "3.0.0", + "@kepler.gl/actions": "3.1.0-alpha.0", + "@kepler.gl/cloud-providers": "3.1.0-alpha.0", + "@kepler.gl/common-utils": "3.1.0-alpha.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/effects": "3.1.0-alpha.0", + "@kepler.gl/layers": "3.1.0-alpha.0", + "@kepler.gl/localization": "3.1.0-alpha.0", + "@kepler.gl/processors": "3.1.0-alpha.0", + "@kepler.gl/reducers": "3.1.0-alpha.0", + "@kepler.gl/schemas": "3.1.0-alpha.0", + "@kepler.gl/styles": "3.1.0-alpha.0", + "@kepler.gl/table": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", + "@kepler.gl/utils": "3.1.0-alpha.0", "@mapbox/mapbox-sdk": "^0.15.3", "@nebula.gl/edit-modes": "1.0.2-alpha.1", "@tippyjs/react": "^4.2.0", diff --git a/src/components/src/common/field-selector.tsx b/src/components/src/common/field-selector.tsx index df058ca616..e317a51d73 100644 --- a/src/components/src/common/field-selector.tsx +++ b/src/components/src/common/field-selector.tsx @@ -7,7 +7,7 @@ import styled from 'styled-components'; import {createSelector} from 'reselect'; import {Field, TooltipField} from '@kepler.gl/types'; -import {notNullorUndefined, toArray} from '@kepler.gl/utils'; +import {notNullorUndefined, toArray} from '@kepler.gl/common-utils'; import ItemSelector from './item-selector/item-selector'; import {classList} from './item-selector/dropdown-list'; diff --git a/src/components/src/common/field-token.tsx b/src/components/src/common/field-token.tsx index 49567f05e4..e66657eaf6 100644 --- a/src/components/src/common/field-token.tsx +++ b/src/components/src/common/field-token.tsx @@ -8,30 +8,27 @@ import {FIELD_TYPE_DISPLAY, FIELD_COLORS} from '@kepler.gl/constants'; export type FieldTokenProps = { type: string; }; +const FieldTag = styled.div` + background-color: rgba(${props => props.color}, 0.2); + border-radius: 2px; + border: 1px solid rgb(${props => props.color}); + color: rgb(${props => props.color}); + display: inline-block; + font-size: 10px; + font-weight: 400; + padding: 0 5px; + text-align: center; + width: ${props => props.theme.fieldTokenWidth}px; + line-height: ${props => props.theme.fieldTokenHeight}px; +`; function FieldTokenFactory( fieldTypeDisplay: ReturnType<typeof getFieldTypes>, fieldColors: ReturnType<typeof getFieldColors> ): React.FC<FieldTokenProps> { - const FieldTag = styled.div` - background-color: rgba(${props => props.color}, 0.2); - border-radius: 2px; - border: 1px solid rgb(${props => props.color}); - color: rgb(${props => props.color}); - display: inline-block; - font-size: 10px; - font-weight: 400; - padding: 0 5px; - text-align: center; - width: ${props => props.theme.fieldTokenWidth}px; - line-height: ${props => props.theme.fieldTokenHeight}px; - `; - const FieldToken = ({type}: FieldTokenProps) => ( - <FieldTag - color={(fieldTypeDisplay[type] && fieldTypeDisplay[type].color) || fieldColors.default} - > - {fieldTypeDisplay[type].label} + <FieldTag color={fieldTypeDisplay[type]?.color || fieldColors.default}> + {fieldTypeDisplay[type]?.label} </FieldTag> ); return FieldToken; diff --git a/src/components/src/common/item-selector/dropdown-select.tsx b/src/components/src/common/item-selector/dropdown-select.tsx index 9b61abdb71..c537830a70 100644 --- a/src/components/src/common/item-selector/dropdown-select.tsx +++ b/src/components/src/common/item-selector/dropdown-select.tsx @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project -import React from 'react'; +import React, {ComponentType} from 'react'; import styled from 'styled-components'; -import {notNullorUndefined} from '@kepler.gl/utils'; -import {ComponentType} from 'react'; +import {notNullorUndefined} from '@kepler.gl/common-utils'; +import {FormattedMessage} from '@kepler.gl/localization'; + import {ArrowDown, Delete} from '../icons'; import {ListItem} from './dropdown-list'; -import {FormattedMessage} from '@kepler.gl/localization'; export type ListItemProps<Option> = { value: Option; @@ -100,7 +100,6 @@ function DropdownSelect<Option>({ displayOption, disabled, onClick, - error, inputTheme, size, value, diff --git a/src/components/src/common/item-selector/item-selector.tsx b/src/components/src/common/item-selector/item-selector.tsx index e95856be32..99cfe058b4 100644 --- a/src/components/src/common/item-selector/item-selector.tsx +++ b/src/components/src/common/item-selector/item-selector.tsx @@ -11,7 +11,8 @@ import ChickletedInput from './chickleted-input'; import Typeahead from './typeahead'; import DropdownList, {ListItem} from './dropdown-list'; import Portaled from '../../common/portaled'; -import {toArray, observeDimensions, unobserveDimensions} from '@kepler.gl/utils'; +import {observeDimensions, unobserveDimensions} from '@kepler.gl/utils'; +import {toArray} from '@kepler.gl/common-utils'; import {injectIntl, IntlShape} from 'react-intl'; import {ListItemProps} from './dropdown-select'; import DropdownSelect from './dropdown-select'; diff --git a/src/components/src/common/map-layer-selector.tsx b/src/components/src/common/map-layer-selector.tsx index e92a6d9b46..6473ff0b96 100644 --- a/src/components/src/common/map-layer-selector.tsx +++ b/src/components/src/common/map-layer-selector.tsx @@ -4,7 +4,7 @@ import React from 'react'; import styled from 'styled-components'; import Checkbox from './checkbox'; -import {generateHashId} from '@kepler.gl/utils'; +import {generateHashId} from '@kepler.gl/common-utils'; const MapLayerSelect = styled.div` padding: 12px; diff --git a/src/components/src/container.tsx b/src/components/src/container.tsx index b5bcba03c3..f93af11465 100644 --- a/src/components/src/container.tsx +++ b/src/components/src/container.tsx @@ -1,16 +1,15 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project -import React, {Component, ComponentType, Dispatch} from 'react'; -import {connect, ConnectedProps} from 'react-redux'; -import memoize from 'lodash.memoize'; +import React, {useRef, ComponentType, Dispatch, useEffect, useMemo} from 'react'; +import {connect, ConnectedProps, useDispatch} from 'react-redux'; + import {console as Console} from 'global/window'; import {injector, provideRecipesToInjector, flattenDeps} from './injector'; import KeplerGlFactory from './kepler-gl'; - import {registerEntry, deleteEntry, renameEntry, forwardTo} from '@kepler.gl/actions'; -import {notNullorUndefined} from '@kepler.gl/utils'; import {KeplerGlState} from '@kepler.gl/reducers'; +import {getApplicationConfig} from '@kepler.gl/utils'; export const ERROR_MSG = { noState: @@ -69,26 +68,32 @@ export function ContainerFactory( * @public */ - class Container extends Component<PropsFromRedux> { - // default id and address if not provided - static defaultProps = { - id: 'map', - getState: state => state.keplerGl, - mint: true - }; - - componentDidMount() { - const { - id, - mint, - mapboxApiAccessToken, - mapboxApiUrl, - mapStylesReplaceDefault, - initialUiState - } = this.props; + function usePreviousId(value) { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }); + return ref.current; + } + const Container: React.FC<PropsFromRedux> = props => { + const { + // default id and address if not provided + id = 'map', + getState = state => state.keplerGl, + mint = true, + mapboxApiAccessToken, + mapboxApiUrl, + mapStylesReplaceDefault, + initialUiState, + state + } = props; + const prevId = usePreviousId(id); + const dispatch = useDispatch(); + + useEffect(() => { // add a new entry to reducer - this.props.dispatch( + dispatch( registerEntry({ id, mint, @@ -98,56 +103,53 @@ export function ContainerFactory( initialUiState }) ); - } - componentDidUpdate(prevProps) { - // check if id has changed, if true, copy state over - if ( - notNullorUndefined(prevProps.id) && - notNullorUndefined(this.props.id) && - prevProps.id !== this.props.id - ) { - this.props.dispatch(renameEntry(prevProps.id, this.props.id)); + // initialize plugins + if (getApplicationConfig().plugins.length) { + getApplicationConfig().plugins.forEach(async plugin => { + await plugin.init(); + }); } - } - - componentWillUnmount() { - if (this.props.mint !== false) { - // delete entry in reducer - this.props.dispatch(deleteEntry(this.props.id)); - } - } - getSelector = memoize((id, getState) => state => { - if (!getState(state)) { - // log error - Console.error(ERROR_MSG.noState); + // cleanup + return () => { + if (mint !== false) { + // delete entry in reducer + dispatch(deleteEntry(id)); + } + }; + }, [id]); - return null; - } - return getState(state)[id]; - }); - getDispatch = memoize((id, dispatch) => forwardTo(id, dispatch)); - - render() { - const {id, getState, dispatch, state} = this.props; - const selector = this.getSelector(id, getState); - - if (!selector || !selector(state)) { - // instance state hasn't been mounted yet - return <div />; + useEffect(() => { + // check if id has changed, if true, copy state over + if (prevId && id && prevId !== id) { + dispatch(renameEntry(prevId, id)); } - - return ( - <KeplerGl - {...this.props} - id={id} - selector={selector} - dispatch={this.getDispatch(id, dispatch)} - /> - ); + }, [dispatch, prevId, id]); + + const stateSelector = useMemo( + () => keplerState => { + if (!getState(keplerState)) { + // log error + Console.error(ERROR_MSG.noState); + + return null; + } + return getState(keplerState)[id]; + }, + [id, getState] + ); + const forwardDispatch = useMemo(() => forwardTo(id, dispatch), [id, dispatch]); + + // const selector = getSelector(id, getState); + + if (!stateSelector || !stateSelector(state)) { + // instance state hasn't been mounted yet + return <div />; } - } + + return <KeplerGl {...props} id={id} selector={stateSelector} dispatch={forwardDispatch} />; + }; return connector(Container); } diff --git a/src/components/src/index.ts b/src/components/src/index.ts index 3c347295f4..2245370a61 100644 --- a/src/components/src/index.ts +++ b/src/components/src/index.ts @@ -27,6 +27,7 @@ import { default as LayerColumnModeConfigFactory, ColumnModeConfigFactory } from './side-panel/layer-panel/layer-column-mode-config'; +import DataTableFactory from './common/data-table'; // Components export { @@ -250,8 +251,8 @@ export { LegendRowFactory, ResetColorLabelFactory } from './common/color-legend'; -export {default as DataTableFactory} from './common/data-table'; export {default as CanvasHack} from './common/data-table/canvas'; +export {renderedSize} from './common/data-table/cell-size'; export { default as DataTableConfigFactory, NumberFormatConfig @@ -428,6 +429,7 @@ export { ChannelByValueSelectorFactory, ColorSelectorFactory, ColumnModeConfigFactory, + DataTableFactory, FieldListItemFactoryFactory, InfoHelperFactory, LayerColorRangeSelectorFactory, @@ -462,6 +464,7 @@ export const ColorSelector = appInjector.get(ColorSelectorFactory); export const LayerColorSelector = appInjector.get(LayerColorSelectorFactory); export const LayerColorRangeSelector = appInjector.get(LayerColorRangeSelectorFactory); export const ArcLayerColorSelector = appInjector.get(ArcLayerColorSelectorFactory); +export const DataTable = appInjector.get(DataTableFactory); // hooks export {CloudListProvider, useCloudListProvider} from './hooks/use-cloud-list-provider'; diff --git a/src/components/src/kepler-gl.tsx b/src/components/src/kepler-gl.tsx index e4f97b5474..4e729f98f9 100644 --- a/src/components/src/kepler-gl.tsx +++ b/src/components/src/kepler-gl.tsx @@ -20,6 +20,8 @@ import { VisStateActions } from '@kepler.gl/actions'; +import {generateHashId} from '@kepler.gl/common-utils'; + type KeplerGlActions = { visStateActions: typeof VisStateActions; mapStateActions: typeof MapStateActions; @@ -52,7 +54,6 @@ import {CloudListProvider} from './hooks/use-cloud-list-provider'; import { filterObjectByPredicate, - generateHashId, validateToken, mergeMessages, observeDimensions, diff --git a/src/components/src/layer-animation-controller.tsx b/src/components/src/layer-animation-controller.tsx index 57c37def01..5a3ad5ddba 100644 --- a/src/components/src/layer-animation-controller.tsx +++ b/src/components/src/layer-animation-controller.tsx @@ -4,7 +4,8 @@ import React, {useCallback} from 'react'; import {ANIMATION_WINDOW} from '@kepler.gl/constants'; import {AnimationConfig, Timeline} from '@kepler.gl/types'; -import {snapToMarks, getTimelineFromAnimationConfig, toArray} from '@kepler.gl/utils'; +import {snapToMarks, getTimelineFromAnimationConfig} from '@kepler.gl/utils'; +import {toArray} from '@kepler.gl/common-utils'; import AnimationControllerFactory from './common/animation-control/animation-controller'; interface LayerAnimationControllerProps { diff --git a/src/components/src/map-container.tsx b/src/components/src/map-container.tsx index c7011d65fc..82e452cec4 100644 --- a/src/components/src/map-container.tsx +++ b/src/components/src/map-container.tsx @@ -4,7 +4,7 @@ // libraries import React, {Component, createRef, useMemo} from 'react'; import styled, {withTheme} from 'styled-components'; -import {Map, MapboxMap, MapRef} from 'react-map-gl'; +import {Map, MapRef} from 'react-map-gl'; import {PickInfo} from '@deck.gl/core/lib/deck'; import DeckGL from '@deck.gl/react'; import {createSelector, Selector} from 'reselect'; @@ -364,7 +364,7 @@ export default function MapContainerFactory( } _deck: any = null; - _map: MapboxMap | null = null; + _map: GetMapRef | null = null; _ref = createRef<HTMLDivElement>(); _deckGLErrorsElapsed: {[id: string]: number} = {}; diff --git a/src/components/src/map/layer-hover-info.tsx b/src/components/src/map/layer-hover-info.tsx index bedd23bc15..6b15a6a92f 100644 --- a/src/components/src/map/layer-hover-info.tsx +++ b/src/components/src/map/layer-hover-info.tsx @@ -7,7 +7,7 @@ import {TooltipField} from '@kepler.gl/types'; import {CenterFlexbox} from '../common/styled-components'; import {Layers} from '../common/icons'; import PropTypes from 'prop-types'; -import {notNullorUndefined} from '@kepler.gl/utils'; +import {notNullorUndefined} from '@kepler.gl/common-utils'; import {Layer} from '@kepler.gl/layers'; import { AggregationLayerHoverData, diff --git a/src/components/src/map/map-popover.tsx b/src/components/src/map/map-popover.tsx index a698a6872e..420b4f1458 100644 --- a/src/components/src/map/map-popover.tsx +++ b/src/components/src/map/map-popover.tsx @@ -9,7 +9,8 @@ import {injectIntl, IntlShape} from 'react-intl'; import {FormattedMessage} from '@kepler.gl/localization'; import {RootContext} from '../context'; import {parseGeoJsonRawFeature} from '@kepler.gl/layers'; -import {idToPolygonGeo, generateHashId} from '@kepler.gl/utils'; +import {idToPolygonGeo} from '@kepler.gl/utils'; +import {generateHashId} from '@kepler.gl/common-utils'; import {LAYER_TYPES} from '@kepler.gl/constants'; import {LayerHoverProp} from '@kepler.gl/reducers'; import {Feature, FeatureSelectionContext} from '@kepler.gl/types'; diff --git a/src/components/src/side-panel/layer-panel/layer-column-config.tsx b/src/components/src/side-panel/layer-panel/layer-column-config.tsx index 156097ce53..c992f53641 100644 --- a/src/components/src/side-panel/layer-panel/layer-column-config.tsx +++ b/src/components/src/side-panel/layer-panel/layer-column-config.tsx @@ -11,7 +11,7 @@ import { ColumnLabels, EnhancedFieldPair } from '@kepler.gl/types'; -import {toArray} from '@kepler.gl/utils'; +import {toArray} from '@kepler.gl/common-utils'; import ColumnSelectorFactory from './column-selector'; import {MinimalField} from '../../common/field-selector'; diff --git a/src/constants/package.json b/src/constants/package.json index 2dbe3b4b15..f9d3f9311a 100644 --- a/src/constants/package.json +++ b/src/constants/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/constants", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -30,7 +30,7 @@ "umd" ], "dependencies": { - "@kepler.gl/types": "3.0.0", + "@kepler.gl/types": "3.1.0-alpha.0", "@types/d3-scale": "^3.2.2", "@types/keymirror": "^0.1.1", "colorbrewer": "^1.5.0", diff --git a/src/deckgl-arrow-layers/package.json b/src/deckgl-arrow-layers/package.json index dd58464440..4d98cabbd8 100644 --- a/src/deckgl-arrow-layers/package.json +++ b/src/deckgl-arrow-layers/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/deckgl-arrow-layers", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "Deck.gl layers with GeoArrow and GeoParquet support", "license": "MIT", "main": "dist/index.js", diff --git a/src/deckgl-layers/package.json b/src/deckgl-layers/package.json index 268ebc190d..7735bca55b 100644 --- a/src/deckgl-layers/package.json +++ b/src/deckgl-layers/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/deckgl-layers", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -35,9 +35,9 @@ "@deck.gl/core": "^8.9.27", "@deck.gl/geo-layers": "^8.9.27", "@deck.gl/layers": "^8.9.27", - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/types": "3.0.0", - "@kepler.gl/utils": "3.0.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", + "@kepler.gl/utils": "3.1.0-alpha.0", "@luma.gl/constants": "^8.5.20", "@luma.gl/core": "^8.5.20", "@mapbox/geo-viewport": "^0.4.1", diff --git a/src/effects/package.json b/src/effects/package.json index 337a3bba96..000bca5cd8 100644 --- a/src/effects/package.json +++ b/src/effects/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/effects", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl viaual effects", "license": "MIT", "main": "dist/index.js", @@ -31,9 +31,9 @@ ], "dependencies": { "@deck.gl/core": "^8.9.27", - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/types": "3.0.0", - "@kepler.gl/utils": "3.0.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", + "@kepler.gl/utils": "3.1.0-alpha.0", "@luma.gl/core": "^8.5.20", "@luma.gl/shadertools": "^8.5.20", "suncalc": "^1.9.0" diff --git a/src/effects/src/effect.ts b/src/effects/src/effect.ts index dc35a57b2f..3958b92e17 100644 --- a/src/effects/src/effect.ts +++ b/src/effects/src/effect.ts @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project -import {generateHashId, validateEffectParameters} from '@kepler.gl/utils'; +import {validateEffectParameters} from '@kepler.gl/utils'; +import {generateHashId} from '@kepler.gl/common-utils'; import { Effect as EffectInterface, EffectProps, diff --git a/src/layers/package.json b/src/layers/package.json index 9d521b99d3..85b06054c2 100644 --- a/src/layers/package.json +++ b/src/layers/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/layers", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -36,13 +36,14 @@ "@deck.gl/geo-layers": "^8.9.27", "@deck.gl/layers": "^8.9.27", "@deck.gl/mesh-layers": "^8.9.27", - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/deckgl-arrow-layers": "3.0.0", - "@kepler.gl/deckgl-layers": "3.0.0", - "@kepler.gl/localization": "3.0.0", - "@kepler.gl/table": "3.0.0", - "@kepler.gl/types": "3.0.0", - "@kepler.gl/utils": "3.0.0", + "@kepler.gl/common-utils": "3.1.0-alpha.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/deckgl-arrow-layers": "3.1.0-alpha.0", + "@kepler.gl/deckgl-layers": "3.1.0-alpha.0", + "@kepler.gl/localization": "3.1.0-alpha.0", + "@kepler.gl/table": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", + "@kepler.gl/utils": "3.1.0-alpha.0", "@loaders.gl/arrow": "^4.3.2", "@loaders.gl/core": "^4.3.2", "@loaders.gl/gis": "^4.3.2", diff --git a/src/layers/src/base-layer.ts b/src/layers/src/base-layer.ts index 35bd3fdf7c..91439d4e24 100644 --- a/src/layers/src/base-layer.ts +++ b/src/layers/src/base-layer.ts @@ -37,18 +37,15 @@ import { import { DataContainerInterface, - generateHashId, getColorGroupByName, getLatLngBounds, getSampleContainerData, hasColorMap, hexToRgb, isPlainObject, - toArray, - notNullorUndefined, reverseColorRange } from '@kepler.gl/utils'; - +import {generateHashId, toArray, notNullorUndefined} from '@kepler.gl/common-utils'; import {Datasets, GpuFilter, KeplerTable} from '@kepler.gl/table'; import { ColorUI, diff --git a/src/layers/src/editor-layer/editor-layer.ts b/src/layers/src/editor-layer/editor-layer.ts index d9279c3ee9..bd8c109f58 100644 --- a/src/layers/src/editor-layer/editor-layer.ts +++ b/src/layers/src/editor-layer/editor-layer.ts @@ -13,7 +13,7 @@ import {PathStyleExtension} from '@deck.gl/extensions'; import {EDITOR_LAYER_ID, EDITOR_MODES, EDITOR_LAYER_PICKING_RADIUS} from '@kepler.gl/constants'; import {Viewport, Editor, Feature, FeatureSelectionContext} from '@kepler.gl/types'; -import {generateHashId} from '@kepler.gl/utils'; +import {generateHashId} from '@kepler.gl/common-utils'; import {EDIT_TYPES} from './constants'; import {LINE_STYLE, FEATURE_STYLE, EDIT_HANDLE_STYLE} from './feature-styles'; diff --git a/src/layers/src/geojson-layer/geojson-layer.ts b/src/layers/src/geojson-layer/geojson-layer.ts index b38ee500f2..c7e6bbbb17 100644 --- a/src/layers/src/geojson-layer/geojson-layer.ts +++ b/src/layers/src/geojson-layer/geojson-layer.ts @@ -49,7 +49,7 @@ import { VisConfigBoolean, Merge, RGBColor, - Field, + ProtoDatasetField, LayerColumn } from '@kepler.gl/types'; import {KeplerTable} from '@kepler.gl/table'; @@ -206,7 +206,7 @@ const getTableModeFieldValue = (field, data) => { const geoFieldAccessor = ({geojson}: GeoJsonLayerColumnsConfig) => - (dc: DataContainerInterface): Field | null => + (dc: DataContainerInterface): ProtoDatasetField | null => dc.getField ? dc.getField(geojson.fieldIdx) : null; // access feature properties from geojson sub layer @@ -377,6 +377,7 @@ export default class GeoJsonLayer extends Layer { .filter( f => (f.type === 'geojson' || f.type === 'geoarrow') && + f.analyzerType && SUPPORTED_ANALYZER_TYPES[f.analyzerType] ) .map(f => f.name); @@ -403,7 +404,7 @@ export default class GeoJsonLayer extends Layer { const defaultLayerConfig = super.getDefaultLayerConfig(props ?? {}); return { ...defaultLayerConfig, - + columnMode: props?.columnMode ?? DEFAULT_COLUMN_MODE, // add height visual channel heightField: null, diff --git a/src/layers/src/layer-text-label.ts b/src/layers/src/layer-text-label.ts index 35760e88ea..d90607734a 100644 --- a/src/layers/src/layer-text-label.ts +++ b/src/layers/src/layer-text-label.ts @@ -3,7 +3,8 @@ import * as arrow from 'apache-arrow'; import {getDistanceScales} from 'viewport-mercator-project'; -import {notNullorUndefined, DataContainerInterface, ArrowDataContainer} from '@kepler.gl/utils'; +import {DataContainerInterface, ArrowDataContainer} from '@kepler.gl/utils'; +import {notNullorUndefined} from '@kepler.gl/common-utils'; import uniq from 'lodash.uniq'; export const defaultPadding = 20; diff --git a/src/layers/src/layer-utils.ts b/src/layers/src/layer-utils.ts index a962904f7c..2f3dbe71be 100644 --- a/src/layers/src/layer-utils.ts +++ b/src/layers/src/layer-utils.ts @@ -5,7 +5,13 @@ import * as arrow from 'apache-arrow'; import {Feature, BBox} from 'geojson'; import {getGeoMetadata} from '@loaders.gl/gis'; -import {Field, FieldPair, SupportedColumnMode, LayerColumn} from '@kepler.gl/types'; +import { + Field, + ProtoDatasetField, + FieldPair, + SupportedColumnMode, + LayerColumn +} from '@kepler.gl/types'; import {DataContainerInterface, ArrowDataContainer} from '@kepler.gl/utils'; import { getBinaryGeometriesFromArrow, @@ -150,7 +156,7 @@ export function getGeojsonLayerMetaFromArrow({ }: { dataContainer: DataContainerInterface; geoColumn: arrow.Vector; - geoField: Field; + geoField: ProtoDatasetField; chunkIndex?: number; }): GeojsonLayerMetaProps { const encoding = geoField?.metadata?.get('ARROW:extension:name'); diff --git a/src/layers/src/trip-layer/trip-utils.ts b/src/layers/src/trip-layer/trip-utils.ts index 201eaedbce..d76432baa4 100644 --- a/src/layers/src/trip-layer/trip-utils.ts +++ b/src/layers/src/trip-layer/trip-utils.ts @@ -6,12 +6,8 @@ import {Analyzer, DATA_TYPES} from 'type-analyzer'; import {Field} from '@kepler.gl/types'; import {parseGeoJsonRawFeature, getGeojsonFeatureTypes} from '../geojson-layer/geojson-utils'; -import { - DataContainerInterface, - getSampleContainerData, - notNullorUndefined, - timeToUnixMilli -} from '@kepler.gl/utils'; +import {DataContainerInterface, getSampleContainerData, timeToUnixMilli} from '@kepler.gl/utils'; +import {notNullorUndefined} from '@kepler.gl/common-utils'; import {Feature} from '@turf/helpers'; import {GeoJsonProperties, Geometry} from 'geojson'; diff --git a/src/localization/package.json b/src/localization/package.json index cfa860ad2e..ba77d83628 100644 --- a/src/localization/package.json +++ b/src/localization/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/localization", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", diff --git a/src/processors/package.json b/src/processors/package.json index 742e3d4f0e..ab5b7a1cb4 100644 --- a/src/processors/package.json +++ b/src/processors/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/processors", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -31,10 +31,12 @@ ], "dependencies": { "@danmarshall/deckgl-typings": "4.9.22", - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/schemas": "3.0.0", - "@kepler.gl/types": "3.0.0", - "@kepler.gl/utils": "3.0.0", + "@kepler.gl/common-utils": "3.1.0-alpha.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/schemas": "3.1.0-alpha.0", + "@kepler.gl/table": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", + "@kepler.gl/utils": "3.1.0-alpha.0", "@loaders.gl/arrow": "^4.3.2", "@loaders.gl/core": "^4.3.2", "@loaders.gl/csv": "^4.3.2", diff --git a/src/processors/src/data-processor.ts b/src/processors/src/data-processor.ts index 80e5fbee8f..6e5e34971a 100644 --- a/src/processors/src/data-processor.ts +++ b/src/processors/src/data-processor.ts @@ -11,14 +11,15 @@ import {ProcessorResult, Field} from '@kepler.gl/types'; import { arrowDataTypeToAnalyzerDataType, arrowDataTypeToFieldType, - notNullorUndefined, hasOwnProperty, - isPlainObject, - analyzerTypeToFieldType, + isPlainObject +} from '@kepler.gl/utils'; +import {notNullorUndefined, toArray} from '@kepler.gl/common-utils'; +import { getSampleForTypeAnalyze, getFieldsFromData, - toArray -} from '@kepler.gl/utils'; + analyzerTypeToFieldType +} from '@kepler.gl/common-utils'; import {KeplerGlSchema, ParsedDataset, SavedMap, LoadedMap} from '@kepler.gl/schemas'; import {Feature} from '@nebula.gl/edit-modes'; @@ -247,7 +248,7 @@ export function processRowObject(rawData: unknown[]): ProcessorResult { const keys = Object.keys(rawData[0]); // [lat, lng, value] const rows = rawData.map(d => keys.map(key => d[key])); // [[31.27, 127.56, 3]] - // row object an still contain values like `Null` or `N/A` + // row object can still contain values like `Null` or `N/A` cleanUpFalsyCsvValue(rows); return processCsvData(rows, keys); @@ -387,38 +388,39 @@ export function processArrowTable(arrowTable: ArrowTable): ProcessorResult | nul return processArrowBatches(arrowTable.data.batches); } -/** - * Parse arrow batches returned from parseInBatches() - * - * @param arrowTable the arrow table to parse - * @returns dataset containing `fields` and `rows` or null - */ -export function processArrowBatches(arrowBatches: arrow.RecordBatch[]): ProcessorResult | null { - if (arrowBatches.length === 0) { - return null; - } - const arrowTable = new arrow.Table(arrowBatches); - const fields: Field[] = []; - - // parse fields - arrowTable.schema.fields.forEach((field: arrow.Field, index: number) => { - const isGeometryColumn = field.metadata.get('ARROW:extension:name')?.startsWith('geoarrow'); - fields.push({ +export function arrowSchemaToFields(schema: arrow.Schema): Field[] { + return schema.fields.map((field: arrow.Field, index: number) => { + const isGeoArrowColumn = field.metadata.get('ARROW:extension:name')?.startsWith('geoarrow'); + return { + ...field, name: field.name, id: field.name, displayName: field.name, format: '', fieldIdx: index, - type: isGeometryColumn ? ALL_FIELD_TYPES.geoarrow : arrowDataTypeToFieldType(field.type), - analyzerType: isGeometryColumn + type: isGeoArrowColumn ? ALL_FIELD_TYPES.geoarrow : arrowDataTypeToFieldType(field.type), + analyzerType: isGeoArrowColumn ? AnalyzerDATA_TYPES.GEOMETRY : arrowDataTypeToAnalyzerDataType(field.type), valueAccessor: (dc: any) => d => { return dc.valueAt(d.index, index); }, metadata: field.metadata - }); + }; }); +} +/** + * Parse arrow batches returned from parseInBatches() + * + * @param arrowTable the arrow table to parse + * @returns dataset containing `fields` and `rows` or null + */ +export function processArrowBatches(arrowBatches: arrow.RecordBatch[]): ProcessorResult | null { + if (arrowBatches.length === 0) { + return null; + } + const arrowTable = new arrow.Table(arrowBatches); + const fields = arrowSchemaToFields(arrowTable.schema); const cols = [...Array(arrowTable.numCols).keys()].map(i => arrowTable.getChildAt(i)); diff --git a/src/processors/src/file-handler.ts b/src/processors/src/file-handler.ts index ac3a75cf8a..d137f4b309 100644 --- a/src/processors/src/file-handler.ts +++ b/src/processors/src/file-handler.ts @@ -8,10 +8,16 @@ import {CSVLoader} from '@loaders.gl/csv'; import {GeoArrowLoader} from '@loaders.gl/arrow'; import {ParquetWasmLoader} from '@loaders.gl/parquet'; import {Loader} from '@loaders.gl/loader-utils'; -import {generateHashId, isPlainObject, generateHashIdFromString} from '@kepler.gl/utils'; +import { + isPlainObject, + generateHashIdFromString, + getApplicationConfig, + getError +} from '@kepler.gl/utils'; +import {generateHashId} from '@kepler.gl/common-utils'; import {DATASET_FORMATS} from '@kepler.gl/constants'; -import {LoadedMap, ProcessorResult} from '@kepler.gl/types'; -import {Feature, AddDataToMapPayload} from '@kepler.gl/types'; +import {AddDataToMapPayload, Feature, LoadedMap, ProcessorResult} from '@kepler.gl/types'; +import {KeplerTable} from '@kepler.gl/table'; import {FeatureCollection} from '@turf/helpers'; import { @@ -200,21 +206,29 @@ export async function readFileInBatches({ return readBatch(progressIterator, file.name); } -export function processFileData({ +export async function processFileData({ content, fileCache }: { content: ProcessFileDataContent; fileCache: FileCacheItem[]; }): Promise<FileCacheItem[]> { - return new Promise((resolve, reject) => { - const {fileName, data} = content; - let format: string | undefined; - let processor: ((data: any) => ProcessorResult | LoadedMap | null) | undefined; - - // generate unique id with length of 4 using fileName string - const id = generateHashIdFromString(fileName); - + const {fileName, data} = content; + let format: string | undefined; + let processor: ((data: any) => ProcessorResult | LoadedMap | null) | undefined; + console.log('Processing file', fileName); + // generate unique id with length of 4 using fileName string + const id = generateHashIdFromString(fileName); + // decide on which table class to use based on application config + const table = getApplicationConfig().table ?? KeplerTable; + + if (typeof table.getFileProcessor === 'function') { + // use custom processors from table class + const processorResult = table.getFileProcessor(data); + format = processorResult.format; + processor = processorResult.processor; + } else { + // use default processors if (isArrowData(data)) { format = DATASET_FORMATS.arrow; processor = processArrowBatches; @@ -222,31 +236,37 @@ export function processFileData({ format = DATASET_FORMATS.keplergl; processor = processKeplerglJSON; } else if (isRowObject(data)) { + // csv file goes here format = DATASET_FORMATS.row; processor = processRowObject; } else if (isGeoJson(data)) { format = DATASET_FORMATS.geojson; processor = processGeojson; } - - if (format && processor) { - const result = processor(data); - - resolve([ - ...fileCache, - { - data: result, - info: { - id, - label: content.fileName, - format - } - } - ]); + } + if (format && processor) { + // eslint-disable-next-line no-useless-catch + let result; + try { + result = await processor(data); + } catch (error) { + throw new Error(`Can not process uploaded file, ${getError(error as Error)}`); } - reject('Unknown File Format'); - }); + return [ + ...fileCache, + { + data: result, + info: { + id, + label: content.fileName, + format + } + } + ]; + } else { + throw new Error('Can not process uploaded file, unknown file format'); + } } export function filesToDataPayload(fileCache: FileCacheItem[]): AddDataToMapPayload[] { diff --git a/src/reducers/package.json b/src/reducers/package.json index 4bdc4c66af..fc5050b8a2 100644 --- a/src/reducers/package.json +++ b/src/reducers/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/reducers", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -30,20 +30,21 @@ "umd" ], "dependencies": { - "@kepler.gl/actions": "3.0.0", - "@kepler.gl/cloud-providers": "3.0.0", - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/deckgl-arrow-layers": "3.0.0", - "@kepler.gl/deckgl-layers": "3.0.0", - "@kepler.gl/effects": "3.0.0", - "@kepler.gl/layers": "3.0.0", - "@kepler.gl/localization": "3.0.0", - "@kepler.gl/processors": "3.0.0", - "@kepler.gl/schemas": "3.0.0", - "@kepler.gl/table": "3.0.0", - "@kepler.gl/tasks": "3.0.0", - "@kepler.gl/types": "3.0.0", - "@kepler.gl/utils": "3.0.0", + "@kepler.gl/actions": "3.1.0-alpha.0", + "@kepler.gl/cloud-providers": "3.1.0-alpha.0", + "@kepler.gl/common-utils": "3.1.0-alpha.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/deckgl-arrow-layers": "3.1.0-alpha.0", + "@kepler.gl/deckgl-layers": "3.1.0-alpha.0", + "@kepler.gl/effects": "3.1.0-alpha.0", + "@kepler.gl/layers": "3.1.0-alpha.0", + "@kepler.gl/localization": "3.1.0-alpha.0", + "@kepler.gl/processors": "3.1.0-alpha.0", + "@kepler.gl/schemas": "3.1.0-alpha.0", + "@kepler.gl/table": "3.1.0-alpha.0", + "@kepler.gl/tasks": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", + "@kepler.gl/utils": "3.1.0-alpha.0", "@loaders.gl/loader-utils": "^4.3.2", "@turf/bbox": "^6.0.1", "@types/lodash.clonedeep": "^4.5.7", diff --git a/src/reducers/src/combined-updaters.ts b/src/reducers/src/combined-updaters.ts index 7ce54f1498..0be3b1d0d7 100644 --- a/src/reducers/src/combined-updaters.ts +++ b/src/reducers/src/combined-updaters.ts @@ -143,7 +143,7 @@ export const addDataToMapUpdater = ( ...payload.options }; - // check if progresive loading dataset by bataches, and update visState directly + // check if progressive loading dataset by batches, and update visState directly const isProgressiveLoading = Array.isArray(datasets) && datasets[0]?.info.format === 'arrow' && @@ -189,6 +189,8 @@ export const addDataToMapUpdater = ( ), if_(Boolean(info), pick_('visState')(apply_<VisState, any>(setMapInfoUpdater, {info}))), + // Note that fit bounds here won't be called in case datasets are created in Tasks. + // A separate Task to update bounds is created once the datasets are ready. with_(({visState}) => pick_('mapState')( apply_( diff --git a/src/reducers/src/interaction-utils.ts b/src/reducers/src/interaction-utils.ts index d390842998..4c304bd2ad 100644 --- a/src/reducers/src/interaction-utils.ts +++ b/src/reducers/src/interaction-utils.ts @@ -11,14 +11,8 @@ import { } from '@kepler.gl/constants'; import {Field, TooltipField, CompareType} from '@kepler.gl/types'; -import { - DataRow, - parseFieldValue, - getFormatter, - isNumber, - defaultFormatter, - notNullorUndefined -} from '@kepler.gl/utils'; +import {DataRow, parseFieldValue, getFormatter, isNumber, defaultFormatter} from '@kepler.gl/utils'; +import {notNullorUndefined} from '@kepler.gl/common-utils'; /** * Minus sign used in tooltip formatting. diff --git a/src/reducers/src/map-style-updaters.ts b/src/reducers/src/map-style-updaters.ts index 3850e3e097..0f488a3a9e 100644 --- a/src/reducers/src/map-style-updaters.ts +++ b/src/reducers/src/map-style-updaters.ts @@ -13,11 +13,11 @@ import { editTopMapStyle, editBottomMapStyle, getStyleImageIcon, - generateHashId, isPlainObject, hexToRgb, colorMaybeToRGB } from '@kepler.gl/utils'; +import {generateHashId} from '@kepler.gl/common-utils'; import { DEFAULT_MAP_STYLES, DEFAULT_LAYER_GROUPS, diff --git a/src/reducers/src/merger-handler.ts b/src/reducers/src/merger-handler.ts index 848242518c..8d46c187cb 100644 --- a/src/reducers/src/merger-handler.ts +++ b/src/reducers/src/merger-handler.ts @@ -2,7 +2,8 @@ // Copyright contributors to the kepler.gl project import {getGlobalTaskQueue} from 'react-palm/tasks'; -import {isObject, toArray} from '@kepler.gl/utils'; +import {isObject} from '@kepler.gl/utils'; +import {toArray} from '@kepler.gl/common-utils'; import {ValueOf} from '@kepler.gl/types'; import {VisState, Merger, PostMergerPayload} from '@kepler.gl/schemas'; @@ -67,7 +68,7 @@ export function mergeStateFromMergers<State extends VisState>( // check if asyncTask was created (time consuming tasks) if (newTasks.length && merger.waitToFinish) { - // skip rest, the async merger will call applyMergerupdater() to continue + // skip rest, the async merger will call applyMergerUpdater() to continue return {mergedState, allMerged: false}; } } diff --git a/src/reducers/src/provider-state-updaters.ts b/src/reducers/src/provider-state-updaters.ts index a48e5257a7..19a3c6a667 100644 --- a/src/reducers/src/provider-state-updaters.ts +++ b/src/reducers/src/provider-state-updaters.ts @@ -3,7 +3,8 @@ import {withTask} from 'react-palm/tasks'; import Console from 'global/console'; -import {generateHashId, getError, isPlainObject, toArray} from '@kepler.gl/utils'; +import {getError, isPlainObject} from '@kepler.gl/utils'; +import {generateHashId, toArray} from '@kepler.gl/common-utils'; import { EXPORT_FILE_TO_CLOUD_TASK, ACTION_TASK, diff --git a/src/reducers/src/vis-state-updaters.ts b/src/reducers/src/vis-state-updaters.ts index 9793f66f31..6298109a61 100644 --- a/src/reducers/src/vis-state-updaters.ts +++ b/src/reducers/src/vis-state-updaters.ts @@ -11,16 +11,24 @@ import isEqual from 'lodash.isequal'; import pick from 'lodash.pick'; import uniq from 'lodash.uniq'; import xor from 'lodash.xor'; -import {disableStackCapturing, withTask} from 'react-palm/tasks'; +import Task, {disableStackCapturing, withTask} from 'react-palm/tasks'; // Tasks -import {DELAY_TASK, LOAD_FILE_TASK, PROCESS_FILE_DATA, UNWRAP_TASK} from '@kepler.gl/tasks'; +import { + DELAY_TASK, + ACTION_TASK, + LOAD_FILE_TASK, + PROCESS_FILE_DATA, + UNWRAP_TASK +} from '@kepler.gl/tasks'; // Actions import { ActionTypes, + CreateNewDatasetSuccessPayload, MapStateActions, ReceiveMapConfigPayload, VisStateActions, applyLayerConfig, + createNewDatasetSuccess, layerConfigChange, layerTypeChange, layerVisConfigChange, @@ -30,7 +38,8 @@ import { loadFilesSuccess, loadNextFile, nextFileBatch, - processFileContent + processFileContent, + fitBounds as fitMapBounds } from '@kepler.gl/actions'; // Utils @@ -45,7 +54,6 @@ import { adjustValueToFilterDomain, featureToFilterValue, filterDatasetCPU, - generateHashId, generatePolygonFilter, getDefaultFilter, getFilterIdInFeature, @@ -57,13 +65,12 @@ import { parseFieldValue, removeLayerFromSplitMaps, set, - toArray, mergeFilterDomainStep, updateFilterPlot, removeFilterPlot, isLayerAnimatable } from '@kepler.gl/utils'; - +import {generateHashId, toArray} from '@kepler.gl/common-utils'; // Mergers import { ANIMATION_WINDOW, @@ -137,6 +144,9 @@ import { TIME_INTERVALS_ORDERED } from '@kepler.gl/utils'; import {createEffect} from '@kepler.gl/effects'; +import {PayloadAction} from '@reduxjs/toolkit'; + +import {findMapBounds} from './data-utils'; // react-palm // disable capture exception for react-palm call to withTask @@ -2123,7 +2133,7 @@ export const updateVisDataUpdater = ( const {config, options} = action; // apply config if passed from action - // TODO: we don't handle asyn mergers here yet + // TODO: we don't handle async mergers here yet const previousState = config ? receiveMapConfigUpdater(state, { payload: {config, options} @@ -2132,19 +2142,37 @@ export const updateVisDataUpdater = ( const datasets = toArray(action.datasets); - const newDataEntries = datasets.reduce( - // @ts-expect-error Type '{}' is missing the following properties from type 'ProtoDataset': data, info - (accu, {info = {}, ...rest} = {}) => ({ - ...accu, - ...(createNewDataEntry({info, ...rest}, state.datasets) || {}) - }), - {} + const allCreateDatasetsTasks = datasets.map( + ({info = {}, ...rest}) => createNewDataEntry({info, ...rest}, state.datasets) || {} ); + // call all Tasks + const tasks = Task.allSettled(allCreateDatasetsTasks).map(results => + createNewDatasetSuccess({results, addToMapOptions: options}) + ); + + return withTask(previousState, tasks); +}; +export const createNewDatasetSuccessUpdater = ( + state: VisState, + action: PayloadAction<CreateNewDatasetSuccessPayload> +): VisState => { + // console.log('createNewDatasetSuccessUpdater', action.payload); + const {results, addToMapOptions} = action.payload; + const newDataEntries = results.reduce((accu, result) => { + if (result.status === 'fulfilled') { + const dataset = result.value; + return {...accu, [dataset.id]: dataset}; + } else { + // handle create dataset error + console.error(result.reason); + return accu; + } + }, {} as Datasets); // save new dataset entry to state const mergedState = { - ...previousState, - datasets: mergeDatasetsByOrder(previousState, newDataEntries) + ...state, + datasets: mergeDatasetsByOrder(state, newDataEntries) }; // merge state with config to be merged @@ -2154,7 +2182,7 @@ export const updateVisDataUpdater = ( const newDataIds = Object.keys(newDataEntries); const postMergerPayload = { newDataIds, - options, + options: addToMapOptions, layerMergers }; @@ -2259,12 +2287,26 @@ function postMergeUpdater(mergedState: VisState, postMergerPayload: PostMergerPa updatedState = updateAnimationDomain(updatedState); // try to process layerMergers after dataset+datasetMergers - return layerMergers && layerMergers.length > 0 - ? applyMergersUpdater(updatedState, { - mergers: layerMergers, - postMergerPayload: {...postMergerPayload, layerMergers: []} - }) - : updatedState; + updatedState = + layerMergers && layerMergers.length > 0 + ? applyMergersUpdater(updatedState, { + mergers: layerMergers, + postMergerPayload: {...postMergerPayload, layerMergers: []} + }) + : updatedState; + + // center the map once the dataset is created + if (newLayers.length && (options || {}).centerMap) { + const bounds = findMapBounds(newLayers); + if (bounds) { + const fitBoundsTask = ACTION_TASK().map(() => { + return fitMapBounds(bounds); + }); + updatedState = withTask(updatedState, fitBoundsTask); + } + } + + return updatedState; } /** diff --git a/src/reducers/src/vis-state.ts b/src/reducers/src/vis-state.ts index e0e31dfd8a..f6de096dd1 100644 --- a/src/reducers/src/vis-state.ts +++ b/src/reducers/src/vis-state.ts @@ -153,7 +153,9 @@ const actionHandler = { [ActionTypes.REMOVE_EFFECT]: visStateUpdaters.removeEffectUpdater, - [ActionTypes.UPDATE_EFFECT]: visStateUpdaters.updateEffectUpdater + [ActionTypes.UPDATE_EFFECT]: visStateUpdaters.updateEffectUpdater, + + [ActionTypes.CREATE_NEW_DATASET_SUCCESS]: visStateUpdaters.createNewDatasetSuccessUpdater }; // construct vis-state reducer diff --git a/src/schemas/package.json b/src/schemas/package.json index fa091f17da..a2244cc4c8 100644 --- a/src/schemas/package.json +++ b/src/schemas/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/schemas", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl schemas used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -30,12 +30,13 @@ "umd" ], "dependencies": { - "@kepler.gl/ai-assistant": "3.0.0", - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/layers": "3.0.0", - "@kepler.gl/table": "3.0.0", - "@kepler.gl/types": "3.0.0", - "@kepler.gl/utils": "3.0.0", + "@kepler.gl/ai-assistant": "3.1.0-alpha.0", + "@kepler.gl/common-utils": "3.1.0-alpha.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/layers": "3.1.0-alpha.0", + "@kepler.gl/table": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", + "@kepler.gl/utils": "3.1.0-alpha.0", "@loaders.gl/loader-utils": "^4.3.2", "@types/keymirror": "^0.1.1", "@types/lodash.clonedeep": "^4.5.7", diff --git a/src/schemas/src/dataset-schema.ts b/src/schemas/src/dataset-schema.ts index b1725b5e82..ecd0446e60 100644 --- a/src/schemas/src/dataset-schema.ts +++ b/src/schemas/src/dataset-schema.ts @@ -8,7 +8,7 @@ import {ProtoDataset, RGBColor} from '@kepler.gl/types'; import {KeplerTable} from '@kepler.gl/table'; import {VERSIONS} from './versions'; import Schema from './schema'; -import {getFieldsFromData, getSampleForTypeAnalyze} from '@kepler.gl/utils'; +import {getFieldsFromData, getSampleForTypeAnalyze} from '@kepler.gl/common-utils'; export type SavedField = { name: string; diff --git a/src/schemas/src/vis-state-schema.ts b/src/schemas/src/vis-state-schema.ts index 302459a396..e2e36c4c08 100644 --- a/src/schemas/src/vis-state-schema.ts +++ b/src/schemas/src/vis-state-schema.ts @@ -4,7 +4,8 @@ import pick from 'lodash.pick'; import {VERSIONS} from './versions'; import {LAYER_VIS_CONFIGS, FILTER_VIEW_TYPES} from '@kepler.gl/constants'; -import {isFilterValidToSave, notNullorUndefined, findById} from '@kepler.gl/utils'; +import {isFilterValidToSave, findById} from '@kepler.gl/utils'; +import {notNullorUndefined} from '@kepler.gl/common-utils'; import Schema from './schema'; import cloneDeep from 'lodash.clonedeep'; import { diff --git a/src/styles/package.json b/src/styles/package.json index 8422086c65..a4ffdd8454 100644 --- a/src/styles/package.json +++ b/src/styles/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/styles", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -30,7 +30,7 @@ "umd" ], "dependencies": { - "@kepler.gl/constants": "3.0.0", + "@kepler.gl/constants": "3.1.0-alpha.0", "@types/styled-components": "^5.1.25", "styled-components": "^4.1.3" }, diff --git a/src/table/package.json b/src/table/package.json index 2885c09c2f..ebfdd60f39 100644 --- a/src/table/package.json +++ b/src/table/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/table", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -30,16 +30,18 @@ "umd" ], "dependencies": { - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/layers": "3.0.0", - "@kepler.gl/types": "3.0.0", - "@kepler.gl/utils": "3.0.0", + "@kepler.gl/common-utils": "3.1.0-alpha.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/layers": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", + "@kepler.gl/utils": "3.1.0-alpha.0", "@types/d3-array": "^2.8.0", "@types/lodash.uniq": "^4.5.7", "d3-array": "^2.8.0", "global": "^4.3.0", "lodash.uniq": "^4.0.1", - "moment": "^2.10.6" + "moment": "^2.10.6", + "react-palm": "^3.3.8" }, "nyc": { "sourceMap": false, diff --git a/src/table/src/dataset-utils.ts b/src/table/src/dataset-utils.ts index 62f845a57e..6108eb3812 100644 --- a/src/table/src/dataset-utils.ts +++ b/src/table/src/dataset-utils.ts @@ -4,8 +4,14 @@ import uniq from 'lodash.uniq'; import KeplerTable, {Datasets} from './kepler-table'; import {ProtoDataset, RGBColor} from '@kepler.gl/types'; +import Task from 'react-palm/tasks'; -import {hexToRgb, validateInputData, datasetColorMaker} from '@kepler.gl/utils'; +import { + hexToRgb, + validateInputData, + datasetColorMaker, + getApplicationConfig +} from '@kepler.gl/utils'; // apply a color for each dataset // to use as label colors @@ -56,17 +62,31 @@ export function createNewDataEntry( // get keplerTable from datasets const keplerTable = datasets[info.id]; // update the data in keplerTable - keplerTable.update(validatedData); - return { - [keplerTable.id]: keplerTable - }; + return UPDATE_TABLE_TASK({table: keplerTable, data: validatedData}); } info = info || {}; const color = info.color || getNewDatasetColor(datasets); - const keplerTable = new KeplerTable({info, data: validatedData, color, ...opts}); - return { - [keplerTable.id]: keplerTable - }; + return CREATE_TABLE_TASK({ + info, + color, + opts, + data: validatedData + }); } + +async function updateTable({table, data}) { + const updated = await table.update(data); // Assuming `table` has an `update` method + return updated; +} + +async function createTable({info, color, opts, data}) { + const TableClass = getApplicationConfig().table ?? KeplerTable; + const table = new TableClass({info, color, ...opts}); + await table.importData({data}); + + return table; +} +const UPDATE_TABLE_TASK = Task.fromPromise(updateTable, 'UPDATE_TABLE_TASK'); +const CREATE_TABLE_TASK = Task.fromPromise(createTable, 'CREATE_TABLE_TASK'); diff --git a/src/table/src/gpu-filter-utils.ts b/src/table/src/gpu-filter-utils.ts index 8e4c0e2b0b..15d58bec55 100644 --- a/src/table/src/gpu-filter-utils.ts +++ b/src/table/src/gpu-filter-utils.ts @@ -4,8 +4,9 @@ import moment from 'moment'; import {MAX_GPU_FILTERS, FILTER_TYPES} from '@kepler.gl/constants'; import {Field, Filter} from '@kepler.gl/types'; +import {set, DataContainerInterface} from '@kepler.gl/utils'; +import {toArray, notNullorUndefined} from '@kepler.gl/common-utils'; -import {set, toArray, notNullorUndefined, DataContainerInterface} from '@kepler.gl/utils'; import {GpuFilter} from './kepler-table'; /** diff --git a/src/table/src/kepler-table.ts b/src/table/src/kepler-table.ts index 2cc19fea21..425000b9eb 100644 --- a/src/table/src/kepler-table.ts +++ b/src/table/src/kepler-table.ts @@ -30,7 +30,6 @@ import {getGpuFilterProps, getDatasetFieldIndexForFilter} from './gpu-filter-uti import {Layer} from '@kepler.gl/layers'; import { - generateHashId, getSortingFunction, timeToUnixMilli, createDataContainer, @@ -48,9 +47,9 @@ import { getOrdinalDomain, getQuantileDomain, DataContainerInterface, - notNullorUndefined, FilterChanged } from '@kepler.gl/utils'; +import {generateHashId, notNullorUndefined} from '@kepler.gl/common-utils'; export type GpuFilter = { filterRange: number[][]; @@ -112,7 +111,7 @@ export function maybeToDate( return dc.valueAt(d.index, fieldIdx); } -class KeplerTable { +class KeplerTable<F extends Field = Field> { readonly id: string; type?: string; @@ -120,15 +119,15 @@ class KeplerTable { color: RGBColor; // fields and data - fields: Field[]; + fields: F[] = []; dataContainer: DataContainerInterface; - allIndexes: number[]; - filteredIndex: number[]; + allIndexes: number[] = []; + filteredIndex: number[] = []; filteredIdxCPU?: number[]; - filteredIndexForDomain: number[]; - fieldPairs: FieldPair[]; + filteredIndexForDomain: number[] = []; + fieldPairs: FieldPair[] = []; gpuFilter: GpuFilter; filterRecord?: FilterRecord; filterRecordCPU?: FilterRecord; @@ -150,14 +149,12 @@ class KeplerTable { constructor({ info, - data, color, metadata, supportedFilterTypes = null, disableDataOperation = false }: { info?: ProtoDataset['info']; - data: ProtoDataset['data']; color: RGBColor; metadata?: ProtoDataset['metadata']; supportedFilterTypes?: ProtoDataset['supportedFilterTypes']; @@ -169,32 +166,13 @@ class KeplerTable { // return this; // } - const dataContainerData = data.cols ? data.cols : data.rows; - const inputDataFormat = data.cols ? DataForm.COLS_ARRAY : DataForm.ROWS_ARRAY; - - const dataContainer = createDataContainer(dataContainerData, { - // @ts-expect-error ProtoDataset field missing property fieldIdx, valueAccessor - fields: data.fields, - inputDataFormat - }); - const datasetInfo = { id: generateHashId(4), label: 'new dataset', type: '', ...info }; - const dataId = datasetInfo.id; - // @ts-expect-error - const fields: Field[] = data.fields.map((f, i) => ({ - ...f, - fieldIdx: i, - id: f.name, - displayName: f.displayName || f.name, - valueAccessor: getFieldValueAccessor(f, i, dataContainer) - })); - const allIndexes = dataContainer.getPlainIndex(); const defaultMetadata = { id: datasetInfo.id, // @ts-ignore @@ -211,15 +189,41 @@ class KeplerTable { ...metadata }; + this.supportedFilterTypes = supportedFilterTypes; + this.disableDataOperation = disableDataOperation; + + this.dataContainer = createDataContainer([]); + this.gpuFilter = getGpuFilterProps([], this.id, [], undefined); + } + + importData({data}: {data: ProtoDataset['data']}) { + const dataContainerData = data.cols ? data.cols : data.rows; + const inputDataFormat = data.cols ? DataForm.COLS_ARRAY : DataForm.ROWS_ARRAY; + + const dataContainer = createDataContainer(dataContainerData, { + fields: data.fields, + inputDataFormat + }); + + const fields: Field[] = data.fields.map((f, i) => ({ + ...f, + fieldIdx: i, + id: f.name, + displayName: f.displayName || f.name, + analyzerType: f.analyzerType || ALL_FIELD_TYPES.string, + format: f.format || '', + valueAccessor: getFieldValueAccessor(f, i, dataContainer) + })); + + const allIndexes = dataContainer.getPlainIndex(); this.dataContainer = dataContainer; this.allIndexes = allIndexes; this.filteredIndex = allIndexes; this.filteredIndexForDomain = allIndexes; this.fieldPairs = findPointFieldPairs(fields); + // @ts-expect-error Make sure that fields satisfies F extends Field this.fields = fields; - this.gpuFilter = getGpuFilterProps([], dataId, fields, undefined); - this.supportedFilterTypes = supportedFilterTypes; - this.disableDataOperation = disableDataOperation; + this.gpuFilter = getGpuFilterProps([], this.id, fields, undefined); } /** @@ -242,7 +246,7 @@ class KeplerTable { * Get field * @param columnName */ - getColumnField(columnName: string): Field | undefined { + getColumnField(columnName: string): F | undefined { const field = this.fields.find(fd => fd[FID_KEY] === columnName); this._assetField(columnName, field); return field; @@ -281,7 +285,7 @@ class KeplerTable { * @param fieldIdx * @param newField */ - updateColumnField(fieldIdx: number, newField: Field): void { + updateColumnField(fieldIdx: number, newField: F): void { this.fields = Object.assign([...this.fields], {[fieldIdx]: newField}); } @@ -297,7 +301,7 @@ class KeplerTable { * Save filterProps to field and retrieve it * @param columnName */ - getColumnFilterProps(columnName: string): Field['filterProps'] | null | undefined { + getColumnFilterProps(columnName: string): F['filterProps'] | null | undefined { const fieldIdx = this.getColumnFieldIdx(columnName); if (fieldIdx < 0) { return null; @@ -329,7 +333,7 @@ class KeplerTable { * @param layers * @param opt */ - filterTable(filters: Filter[], layers: Layer[], opt?: FilterDatasetOpt): KeplerTable { + filterTable(filters: Filter[], layers: Layer[], opt?: FilterDatasetOpt): KeplerTable<Field> { const {dataContainer, id: dataId, filterRecord: oldFilterRecord, fields} = this; // if there is no filters @@ -385,7 +389,7 @@ class KeplerTable { * @param filters * @param layers */ - filterTableCPU(filters: Filter[], layers: Layer[]): KeplerTable { + filterTableCPU(filters: Filter[], layers: Layer[]): KeplerTable<Field> { const opt = { cpuOnly: true, ignoreDomain: true @@ -423,7 +427,7 @@ class KeplerTable { * Calculate field domain based on field type and data * for Filter */ - getColumnFilterDomain(field: Field): FieldDomain { + getColumnFilterDomain(field: F): FieldDomain { const {dataContainer} = this; const {valueAccessor} = field; @@ -454,10 +458,7 @@ class KeplerTable { /** * Get the domain of this column based on scale type */ - getColumnLayerDomain( - field: Field, - scaleType: string - ): number[] | string[] | [number, number] | null { + getColumnLayerDomain(field: F, scaleType: string): number[] | string[] | [number, number] | null { const {dataContainer, filteredIndexForDomain} = this; if (!SCALE_TYPES[scaleType]) { @@ -516,7 +517,7 @@ class KeplerTable { } export type Datasets = { - [key: string]: KeplerTable; + [key: string]: KeplerTable<Field>; }; // HELPER FUNCTIONS (MAINLY EXPORTED FOR TEST...) @@ -601,10 +602,10 @@ export function findPointFieldPairs(fields: Field[]): FieldPair[] { * @type */ export function sortDatasetByColumn( - dataset: KeplerTable, + dataset: KeplerTable<Field>, column: string, mode?: string -): KeplerTable { +): KeplerTable<Field> { const {allIndexes, fields, dataContainer} = dataset; const fieldIndex = fields.findIndex(f => f.name === column); if (fieldIndex < 0) { @@ -640,7 +641,7 @@ export function sortDatasetByColumn( return dataset; } -export function pinTableColumns(dataset: KeplerTable, column: string): KeplerTable { +export function pinTableColumns(dataset: KeplerTable<Field>, column: string): KeplerTable<Field> { const field = dataset.getColumnField(column); if (!field) { return dataset; @@ -658,7 +659,7 @@ export function pinTableColumns(dataset: KeplerTable, column: string): KeplerTab return copyTableAndUpdate(dataset, {pinnedColumns}); } -export function copyTable(original: KeplerTable): KeplerTable { +export function copyTable(original: KeplerTable<Field>): KeplerTable<Field> { return Object.assign(Object.create(Object.getPrototypeOf(original)), original); } @@ -667,9 +668,9 @@ export function copyTable(original: KeplerTable): KeplerTable { * @returns */ export function copyTableAndUpdate( - original: KeplerTable, - options: Partial<KeplerTable> = {} -): KeplerTable { + original: KeplerTable<Field>, + options: Partial<KeplerTable<Field>> = {} +): KeplerTable<Field> { return Object.entries(options).reduce((acc, entry) => { acc[entry[0]] = entry[1]; return acc; diff --git a/src/tasks/package.json b/src/tasks/package.json index cf72cb8cd2..5ed5b3e0d8 100644 --- a/src/tasks/package.json +++ b/src/tasks/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/tasks", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -30,7 +30,7 @@ "umd" ], "dependencies": { - "@kepler.gl/processors": "3.0.0", + "@kepler.gl/processors": "3.1.0-alpha.0", "react-palm": "^3.3.8" }, "nyc": { diff --git a/src/types/actions.d.ts b/src/types/actions.d.ts index add827c2aa..5d734dfd4b 100644 --- a/src/types/actions.d.ts +++ b/src/types/actions.d.ts @@ -31,6 +31,15 @@ export type ExportFileToCloudPayload = { /** * Input dataset parsed to addDataToMap */ +export type ProtoDatasetField = { + name: string; + type: string; + format?: string; + displayName?: string; + analyzerType?: string; + id?: string; + metadata?: Record<string, any>; +}; export type ProtoDataset = { info: { id?: string; @@ -41,14 +50,7 @@ export type ProtoDataset = { hidden?: boolean; }; data: { - fields: { - name: string; - type?: string; - format?: string; - displayName?: string; - analyzerType?: string; - id?: string; - }[]; + fields: ProtoDatasetField[]; rows: any[][]; cols?: any[]; }; diff --git a/src/types/layers.d.ts b/src/types/layers.d.ts index 266bf33578..15da495ce8 100644 --- a/src/types/layers.d.ts +++ b/src/types/layers.d.ts @@ -98,16 +98,16 @@ export type LayerWeightConfig = { }; export type Field = { - analyzerType: string; - id?: string; name: string; displayName: string; - format: string; type: string; fieldIdx: number; valueAccessor(v: {index: number}): any; + analyzerType?: string; + id?: string; + format: string; filterProps?: FilterProps; - metadata?: any; + metadata?: Record<string, any>; displayFormat?: string; }; diff --git a/src/types/package.json b/src/types/package.json index 8df15a4504..8d905306cf 100644 --- a/src/types/package.json +++ b/src/types/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/types", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl types used by kepler.gl components, actions and reducers", "license": "MIT", "types": "index.d.ts", diff --git a/src/utils/package.json b/src/utils/package.json index 50259d0462..0c50feac0c 100644 --- a/src/utils/package.json +++ b/src/utils/package.json @@ -1,7 +1,7 @@ { "name": "@kepler.gl/utils", "author": "Shan He <shan@uber.com>", - "version": "3.0.0", + "version": "3.1.0-alpha.0", "description": "kepler.gl constants used by kepler.gl components, actions and reducers", "license": "MIT", "main": "dist/index.js", @@ -30,8 +30,9 @@ "umd" ], "dependencies": { - "@kepler.gl/constants": "3.0.0", - "@kepler.gl/types": "3.0.0", + "@kepler.gl/common-utils": "3.1.0-alpha.0", + "@kepler.gl/constants": "3.1.0-alpha.0", + "@kepler.gl/types": "3.1.0-alpha.0", "@luma.gl/constants": "^8.5.20", "@luma.gl/core": "^8.5.20", "@mapbox/geo-viewport": "^0.4.1", diff --git a/src/utils/src/application-config.ts b/src/utils/src/application-config.ts index 359152a510..f4640af573 100644 --- a/src/utils/src/application-config.ts +++ b/src/utils/src/application-config.ts @@ -23,6 +23,10 @@ export type KeplerApplicationConfig<Map> = { mapLibCssClass?: string; mapLibName?: string; mapLibUrl?: string; + plugins?: any[]; + // KeplerTable alternative + // TODO improve typing by exporting KeplerTable interface to @kepler.gl/types + table?: any; }; const DEFAULT_APPLICATION_CONFIG: Required<KeplerApplicationConfig<mapboxgl.Map>> = { @@ -37,7 +41,11 @@ const DEFAULT_APPLICATION_CONFIG: Required<KeplerApplicationConfig<mapboxgl.Map> getMap: (mapRef: MapRef): mapboxgl.Map => mapRef?.getMap(), mapLibCssClass: 'maplibregl', mapLibName: 'MapLibre', - mapLibUrl: 'https://www.maplibre.org/' + mapLibUrl: 'https://www.maplibre.org/', + plugins: [], + // The default table class is KeplerTable. + // TODO include KeplerTable here when the circular dependency with @kepler.gl/table and @kepler.gl/utils are resolved. + table: null }; const applicationConfig: Required<KeplerApplicationConfig<mapboxgl.Map>> = diff --git a/src/utils/src/arrow-data-container.ts b/src/utils/src/arrow-data-container.ts index cabefa4691..d31034748e 100644 --- a/src/utils/src/arrow-data-container.ts +++ b/src/utils/src/arrow-data-container.ts @@ -4,7 +4,7 @@ import * as arrow from 'apache-arrow'; import {console as globalConsole} from 'global/window'; import {DATA_TYPES as AnalyzerDATA_TYPES} from 'type-analyzer'; -import {Field} from '@kepler.gl/types'; +import {ProtoDatasetField} from '@kepler.gl/types'; import {ALL_FIELD_TYPES} from '@kepler.gl/constants'; import {DataRow, SharedRowOptions} from './data-row'; @@ -12,7 +12,7 @@ import {DataContainerInterface, RangeOptions} from './data-container-interface'; type ArrowDataContainerInput = { cols: arrow.Vector[]; - fields?: Field[]; + fields?: ProtoDatasetField[]; }; /** @@ -44,7 +44,7 @@ export class ArrowDataContainer implements DataContainerInterface { _cols: arrow.Vector[]; _numColumns: number; _numRows: number; - _fields: Field[]; + _fields: ProtoDatasetField[]; _numChunks: number; // cache column data to make valueAt() faster // _colData: any[][]; @@ -144,7 +144,7 @@ export class ArrowDataContainer implements DataContainerInterface { return this._cols[columnIndex]; } - getField(columnIndex: number): Field { + getField(columnIndex: number): ProtoDatasetField { return this._fields[columnIndex]; } diff --git a/src/utils/src/data-container-interface.ts b/src/utils/src/data-container-interface.ts index 350a2f2e4f..37e669d1de 100644 --- a/src/utils/src/data-container-interface.ts +++ b/src/utils/src/data-container-interface.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project -import {Field} from '@kepler.gl/types'; +import {ProtoDatasetField} from '@kepler.gl/types'; import {DataRow, SharedRowOptions} from './data-row'; /** @@ -82,7 +82,7 @@ export interface DataContainerInterface { * @param columnIndex Column index. * @returns The field object at the specified index. */ - getField?(columnIndex: number): Field; + getField?(columnIndex: number): ProtoDatasetField; /** * Returns the underlying data object. diff --git a/src/utils/src/data-container-utils.ts b/src/utils/src/data-container-utils.ts index 05a5fccea0..46f01af1b0 100644 --- a/src/utils/src/data-container-utils.ts +++ b/src/utils/src/data-container-utils.ts @@ -6,11 +6,11 @@ import {RowDataContainer} from './row-data-container'; import {IndexedDataContainer} from './indexed-data-container'; import {DataContainerInterface} from './data-container-interface'; -import {Field} from '@kepler.gl/types'; +import {ProtoDatasetField} from '@kepler.gl/types'; export type DataContainerOptions = { inputDataFormat?: string; // one of DataForm - fields?: Field[]; + fields?: ProtoDatasetField[]; }; export const DataForm = { diff --git a/src/utils/src/data-scale-utils.ts b/src/utils/src/data-scale-utils.ts index eb4ef80ad7..78dfb0a668 100644 --- a/src/utils/src/data-scale-utils.ts +++ b/src/utils/src/data-scale-utils.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project +import {notNullorUndefined} from '@kepler.gl/common-utils'; import {ALL_FIELD_TYPES, ColorMap, ColorRange, SCALE_FUNC, SCALE_TYPES} from '@kepler.gl/constants'; import {Layer, VisualChannel, VisualChannelDomain} from '@kepler.gl/layers'; import {HexColor, MapState} from '@kepler.gl/types'; @@ -8,7 +9,7 @@ import {bisectLeft, quantileSorted as d3Quantile, extent} from 'd3-array'; import moment from 'moment'; import {isRgbColor, rgbToHex} from './color-utils'; import {DataContainerInterface} from './data-container-interface'; -import {formatNumber, notNullorUndefined, reverseFormatNumber, unique} from './data-utils'; +import {formatNumber, reverseFormatNumber, unique} from './data-utils'; import {getTimeWidgetHintFormatter} from './filter-utils'; import {isPlainObject} from './utils'; diff --git a/src/utils/src/data-utils.ts b/src/utils/src/data-utils.ts index c5fb67e4f5..f34364233f 100644 --- a/src/utils/src/data-utils.ts +++ b/src/utils/src/data-utils.ts @@ -12,6 +12,7 @@ import { TOOLTIP_KEY, TooltipFormat } from '@kepler.gl/constants'; +import {notNullorUndefined} from '@kepler.gl/common-utils'; import {Field, Millisecond} from '@kepler.gl/types'; import {snapToMarks} from './plot'; @@ -86,13 +87,6 @@ export function timeToUnixMilli(value: string | number | Date, format: string): return null; } -/** - * whether null or undefined - */ -export function notNullorUndefined<T extends NonNullable<any>>(d: T | null | undefined): d is T { - return d !== undefined && d !== null; -} - /** * Whether d is a number, this filtered out NaN as well */ @@ -452,7 +446,3 @@ export function datetimeFormatter( : // return empty string instead of 'Invalid date' if ts is undefined/null format => ts => ts ? moment.utc(ts).format(format) : ''; } - -export function notNullOrUndefined(d: any): boolean { - return d !== undefined && d !== null; -} diff --git a/src/utils/src/dataset-utils.ts b/src/utils/src/dataset-utils.ts index 620e284548..ba7417defd 100644 --- a/src/utils/src/dataset-utils.ts +++ b/src/utils/src/dataset-utils.ts @@ -1,20 +1,19 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project -import {console as globalConsole} from 'global/window'; import { ALL_FIELD_TYPES, FIELD_OPTS, TOOLTIP_FORMATS, TOOLTIP_FORMAT_TYPES } from '@kepler.gl/constants'; -import {Analyzer, DATA_TYPES as AnalyzerDATA_TYPES} from 'type-analyzer'; +import {getSampleForTypeAnalyze, getFieldsFromData} from '@kepler.gl/common-utils'; +import {Analyzer} from 'type-analyzer'; import assert from 'assert'; import { ProcessorResult, RGBColor, - RowData, Field, FieldPair, TimeLabelFormat, @@ -22,11 +21,11 @@ import { ProtoDataset } from '@kepler.gl/types'; import {TooltipFormat} from '@kepler.gl/constants'; +import {notNullorUndefined} from '@kepler.gl/common-utils'; import {isPlainObject} from './utils'; -import {notNullorUndefined, getFormatter} from './data-utils'; +import {getFormatter} from './data-utils'; import {getFormatValue} from './format'; -import {range} from 'd3-array'; import {hexToRgb} from './color-utils'; // apply a color for each dataset @@ -226,27 +225,6 @@ export function findDefaultColorField({ return null; } -export const ACCEPTED_ANALYZER_TYPES = [ - AnalyzerDATA_TYPES.DATE, - AnalyzerDATA_TYPES.TIME, - AnalyzerDATA_TYPES.DATETIME, - AnalyzerDATA_TYPES.NUMBER, - AnalyzerDATA_TYPES.INT, - AnalyzerDATA_TYPES.FLOAT, - AnalyzerDATA_TYPES.BOOLEAN, - AnalyzerDATA_TYPES.STRING, - AnalyzerDATA_TYPES.GEOMETRY, - AnalyzerDATA_TYPES.GEOMETRY_FROM_STRING, - AnalyzerDATA_TYPES.PAIR_GEOMETRY_FROM_STRING, - AnalyzerDATA_TYPES.ZIPCODE, - AnalyzerDATA_TYPES.ARRAY, - AnalyzerDATA_TYPES.OBJECT -]; - -const IGNORE_DATA_TYPES = Object.keys(AnalyzerDATA_TYPES).filter( - type => !ACCEPTED_ANALYZER_TYPES.includes(type) -); - /** * Validate input data, adding missing field types, rename duplicate columns */ @@ -268,7 +246,7 @@ export function validateInputData(data: ProtoDataset['data']): ProcessorResult { const allValid = fields.every((f, i) => { if (!isPlainObject(f)) { assert(`fields needs to be an array of object, but find ${typeof f}`); - fields[i] = {name: `column_${i}`}; + fields[i] = {name: `column_${i}`, type: ALL_FIELD_TYPES.string}; } if (!f.name) { @@ -330,246 +308,6 @@ function findNonEmptyRowsAtField(rows: unknown[][], fieldIdx: number, total: num } return sample; } -/** - * Getting sample data for analyzing field type. - */ -export function getSampleForTypeAnalyze({ - fields, - rows, - sampleCount = 50 -}: { - fields: string[]; - rows: unknown[][]; - sampleCount?: number; -}): RowData { - const total = Math.min(sampleCount, rows.length); - // const fieldOrder = fields.map(f => f.name); - const sample = range(0, total, 1).map(() => ({})); - - // collect sample data for each field - fields.forEach((field, fieldIdx) => { - // data counter - let i = 0; - // sample counter - let j = 0; - - while (j < total) { - if (i >= rows.length) { - // if depleted data pool - sample[j][field] = null; - j++; - } else if (notNullorUndefined(rows[i][fieldIdx])) { - const value = rows[i][fieldIdx]; - sample[j][field] = typeof value === 'string' ? value.trim() : value; - j++; - i++; - } else { - i++; - } - } - }); - - return sample; -} - -/** - * Check if string is a valid Well-known binary (WKB) in HEX format - * https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry - * - * @param str input string - * @returns true if string is a valid WKB in HEX format - */ -export function isHexWkb(str: string | null): boolean { - if (!str) return false; - // check if the length of the string is even and is at least 10 characters long - if (str.length < 10 || str.length % 2 !== 0) { - return false; - } - // check if first two characters are 00 or 01 - if (!str.startsWith('00') && !str.startsWith('01')) { - return false; - } - // check if the rest of the string is a valid hex - return /^[0-9a-fA-F]+$/.test(str.slice(2)); -} - -/** - * Analyze field types from data in `string` format, e.g. uploaded csv. - * Assign `type`, `fieldIdx` and `format` (timestamp only) to each field - * - * @param data array of row object - * @param fieldOrder array of field names as string - * @returns formatted fields - * @public - * @example - * - * import {getFieldsFromData} from 'kepler.gl/processors'; - * const data = [{ - * time: '2016-09-17 00:09:55', - * value: '4', - * surge: '1.2', - * isTrip: 'true', - * zeroOnes: '0' - * }, { - * time: '2016-09-17 00:30:08', - * value: '3', - * surge: null, - * isTrip: 'false', - * zeroOnes: '1' - * }, { - * time: null, - * value: '2', - * surge: '1.3', - * isTrip: null, - * zeroOnes: '1' - * }]; - * - * const fieldOrder = ['time', 'value', 'surge', 'isTrip', 'zeroOnes']; - * const fields = getFieldsFromData(data, fieldOrder); - * // fields = [ - * // {name: 'time', format: 'YYYY-M-D H:m:s', fieldIdx: 1, type: 'timestamp'}, - * // {name: 'value', format: '', fieldIdx: 4, type: 'integer'}, - * // {name: 'surge', format: '', fieldIdx: 5, type: 'real'}, - * // {name: 'isTrip', format: '', fieldIdx: 6, type: 'boolean'}, - * // {name: 'zeroOnes', format: '', fieldIdx: 7, type: 'integer'}]; - * - */ -export function getFieldsFromData(data: RowData, fieldOrder: string[]): Field[] { - // add a check for epoch timestamp - const metadata = Analyzer.computeColMeta( - data, - [ - {regex: /.*geojson|all_points/g, dataType: 'GEOMETRY'}, - {regex: /.*census/g, dataType: 'STRING'} - ], - {ignoredDataTypes: IGNORE_DATA_TYPES} - ); - - const {fieldByIndex} = renameDuplicateFields(fieldOrder); - - const result = fieldOrder.map((field, index) => { - const name = fieldByIndex[index]; - - const fieldMeta = metadata.find(m => m.key === field); - - // fieldMeta could be undefined if the field has no data and Analyzer.computeColMeta - // will ignore the field. In this case, we will simply assign the field type to STRING - // since dropping the column in the RowData could be expensive - let type = fieldMeta?.type || 'STRING'; - const format = fieldMeta?.format || ''; - - // check if string is hex wkb - if (type === AnalyzerDATA_TYPES.STRING) { - type = data.some(d => isHexWkb(d[name])) ? AnalyzerDATA_TYPES.GEOMETRY : type; - } - - return { - name, - id: name, - displayName: name, - format, - fieldIdx: index, - type: analyzerTypeToFieldType(type), - analyzerType: type, - valueAccessor: dc => d => { - return dc.valueAt(d.index, index); - } - }; - }); - - return result; -} - -/** - * pass in an array of field names, rename duplicated one - * and return a map from old field index to new name - * - * @param fieldOrder - * @returns new field name by index - */ -export function renameDuplicateFields(fieldOrder: string[]): { - allNames: string[]; - fieldByIndex: string[]; -} { - return fieldOrder.reduce<{allNames: string[]; fieldByIndex: string[]}>( - (accu, field, i) => { - const {allNames} = accu; - let fieldName = field; - - // add a counter to duplicated names - if (allNames.includes(field)) { - let counter = 0; - while (allNames.includes(`${field}-${counter}`)) { - counter++; - } - fieldName = `${field}-${counter}`; - } - - accu.fieldByIndex[i] = fieldName; - accu.allNames.push(fieldName); - - return accu; - }, - {allNames: [], fieldByIndex: []} - ); -} - -/** - * Convert type-analyzer output to kepler.gl field types - * - * @param aType - * @returns corresponding type in `ALL_FIELD_TYPES` - */ -/* eslint-disable complexity */ -export function analyzerTypeToFieldType(aType: string): string { - const { - DATE, - TIME, - DATETIME, - NUMBER, - INT, - FLOAT, - BOOLEAN, - STRING, - GEOMETRY, - GEOMETRY_FROM_STRING, - PAIR_GEOMETRY_FROM_STRING, - ZIPCODE, - ARRAY, - OBJECT - } = AnalyzerDATA_TYPES; - - // TODO: un recognized types - // CURRENCY PERCENT NONE - switch (aType) { - case DATE: - return ALL_FIELD_TYPES.date; - case TIME: - case DATETIME: - return ALL_FIELD_TYPES.timestamp; - case FLOAT: - return ALL_FIELD_TYPES.real; - case INT: - return ALL_FIELD_TYPES.integer; - case BOOLEAN: - return ALL_FIELD_TYPES.boolean; - case GEOMETRY: - case GEOMETRY_FROM_STRING: - case PAIR_GEOMETRY_FROM_STRING: - return ALL_FIELD_TYPES.geojson; - case ARRAY: - return ALL_FIELD_TYPES.array; - case OBJECT: - return ALL_FIELD_TYPES.object; - case NUMBER: - case STRING: - case ZIPCODE: - return ALL_FIELD_TYPES.string; - default: - globalConsole.warn(`Unsupported analyzer type: ${aType}`); - return ALL_FIELD_TYPES.string; - } -} const TIME_DISPLAY = '2020-05-11 14:00'; diff --git a/src/utils/src/export-utils.ts b/src/utils/src/export-utils.ts index cc0acd86ce..453c5cd114 100644 --- a/src/utils/src/export-utils.ts +++ b/src/utils/src/export-utils.ts @@ -13,8 +13,9 @@ import { OneXResolutionOption, ExportImage } from '@kepler.gl/constants'; +import {generateHashId} from '@kepler.gl/common-utils'; import domtoimage from './dom-to-image'; -import {generateHashId, set} from './utils'; +import {set} from './utils'; import {exportMapToHTML} from './export-map-html'; import {getApplicationConfig} from './application-config'; diff --git a/src/utils/src/filter-utils.ts b/src/utils/src/filter-utils.ts index 6690bb8b36..d33a648b95 100644 --- a/src/utils/src/filter-utils.ts +++ b/src/utils/src/filter-utils.ts @@ -41,9 +41,10 @@ import { AnimationConfig } from '@kepler.gl/types'; +import {generateHashId, toArray, notNullorUndefined} from '@kepler.gl/common-utils'; import {DataContainerInterface} from './data-container-interface'; -import {generateHashId, set, toArray} from './utils'; -import {notNullorUndefined, timeToUnixMilli, unique} from './data-utils'; +import {set} from './utils'; +import {timeToUnixMilli, unique} from './data-utils'; import {getCentroid} from './h3-utils'; import {updateTimeFilterPlotType, updateRangeFilterPlotType} from './plot'; import {KeplerTableModel} from './types'; diff --git a/src/utils/src/h3-utils.ts b/src/utils/src/h3-utils.ts index 64f00647a2..7346f365dd 100644 --- a/src/utils/src/h3-utils.ts +++ b/src/utils/src/h3-utils.ts @@ -3,7 +3,7 @@ import {h3GetResolution, H3Index, h3IsValid, h3ToGeo, h3ToGeoBoundary} from 'h3-js'; import {ALL_FIELD_TYPES} from '@kepler.gl/constants'; -import {notNullorUndefined} from './data-utils'; +import {notNullorUndefined} from '@kepler.gl/common-utils'; export {h3GetResolution, h3IsValid}; diff --git a/src/utils/src/index.ts b/src/utils/src/index.ts index d01fef6507..6bc80717a8 100644 --- a/src/utils/src/index.ts +++ b/src/utils/src/index.ts @@ -50,15 +50,10 @@ export { } from './time'; export { - ACCEPTED_ANALYZER_TYPES, - analyzerTypeToFieldType, datasetColorMaker, findDefaultColorField, getFieldFormatLabels, - getFieldsFromData, getFormatLabels, - getSampleForTypeAnalyze, - renameDuplicateFields, validateInputData } from './dataset-utils'; export {exportMapToHTML} from './export-map-html'; @@ -147,5 +142,5 @@ export {getCentroid, getHexFields, h3IsValid, idToPolygonGeo} from './h3-utils'; export type {Centroid} from './h3-utils'; // Application config -export {getApplicationConfig, initApplicationConfig} from '../../utils/src/application-config'; -export type {KeplerApplicationConfig, MapLibInstance} from '../../utils/src/application-config'; +export {getApplicationConfig, initApplicationConfig} from './application-config'; +export type {KeplerApplicationConfig, MapLibInstance} from './application-config'; diff --git a/src/utils/src/notifications-utils.ts b/src/utils/src/notifications-utils.ts index 3d0343eb28..319d232238 100644 --- a/src/utils/src/notifications-utils.ts +++ b/src/utils/src/notifications-utils.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project -import {generateHashId} from './utils'; +import {generateHashId} from '@kepler.gl/common-utils'; import { DEFAULT_NOTIFICATION_MESSAGE, diff --git a/src/utils/src/plot.ts b/src/utils/src/plot.ts index d60b2618db..c962335a55 100644 --- a/src/utils/src/plot.ts +++ b/src/utils/src/plot.ts @@ -17,6 +17,7 @@ import { ValueOf, LineDatum } from '@kepler.gl/types'; +import {notNullorUndefined} from '@kepler.gl/common-utils'; import { ANIMATION_WINDOW, BINS, @@ -28,7 +29,7 @@ import { } from '@kepler.gl/constants'; import {VisState} from '@kepler.gl/schemas'; -import {notNullOrUndefined, roundValToStep} from './data-utils'; +import {roundValToStep} from './data-utils'; import {aggregate, AGGREGATION_NAME} from './aggregation'; import {capitalizeFirstLetter} from './strings'; import {getDefaultTimeFormat} from './format'; @@ -413,11 +414,11 @@ export function splitSeries(series) { let temp: any[] = []; for (let i = 0; i < series.length; i++) { const d = series[i]; - if (!notNullOrUndefined(d.y) && temp.length) { + if (!notNullorUndefined(d.y) && temp.length) { // ends temp lines.push(temp); temp = []; - } else if (notNullOrUndefined(d.y)) { + } else if (notNullorUndefined(d.y)) { temp.push(d); } @@ -426,7 +427,7 @@ export function splitSeries(series) { } } - const markers = lines.length > 1 ? series.filter(d => notNullOrUndefined(d.y)) : []; + const markers = lines.length > 1 ? series.filter(d => notNullorUndefined(d.y)) : []; return {lines, markers}; } diff --git a/src/utils/src/row-data-container.ts b/src/utils/src/row-data-container.ts index 4284ea0068..50222a3172 100644 --- a/src/utils/src/row-data-container.ts +++ b/src/utils/src/row-data-container.ts @@ -2,12 +2,12 @@ // Copyright contributors to the kepler.gl project import {DataRow, SharedRowOptions} from './data-row'; -import {Field} from '@kepler.gl/types'; +import {ProtoDatasetField} from '@kepler.gl/types'; import {DataContainerInterface, RangeOptions} from './data-container-interface'; type RowDataContainerInput = { rows: any[][]; - fields?: Field[]; + fields?: ProtoDatasetField[]; }; /** diff --git a/src/utils/src/time.ts b/src/utils/src/time.ts index 4bbbde87be..5543861212 100644 --- a/src/utils/src/time.ts +++ b/src/utils/src/time.ts @@ -13,9 +13,9 @@ import { INTERVAL, TickInterval } from '@kepler.gl/constants'; +import {toArray} from '@kepler.gl/common-utils'; import {AnimationConfig, Timeline, TimeRangeFilter} from '@kepler.gl/types'; -import {toArray} from './utils'; import {getFrequency} from './aggregation'; export const TileTimeInterval = { diff --git a/src/utils/src/utils.ts b/src/utils/src/utils.ts index fdc1d90bb1..174ce34b0e 100644 --- a/src/utils/src/utils.ts +++ b/src/utils/src/utils.ts @@ -4,15 +4,6 @@ import window from 'global/window'; import {capitalizeFirstLetter} from './strings'; -/** - * Generate a hash string based on number of character - * @param {number} count - * @returns {string} hash string - */ -export function generateHashId(count = 6): string { - return Math.random().toString(36).substr(count); -} - /** * Generate a hash string based on string * @param str @@ -68,26 +59,6 @@ export const camelize = (str: string): string => { }); }; -/** - * Converts non-arrays to arrays. Leaves arrays alone. Converts - * undefined values to empty arrays ([] instead of [undefined]). - * Otherwise, just returns [item] for non-array items. - * - * @param {*} item - * @returns {array} boom! much array. very indexed. so useful. - */ -export function toArray<T>(item: T | T[]): T[] { - if (Array.isArray(item)) { - return item; - } - - if (typeof item === 'undefined' || item === null) { - return []; - } - - return [item]; -} - /** * immutably insert value to an Array or Object * @param {Array|Object} obj diff --git a/test/browser/components/container-test.js b/test/browser/components/container-test.js index 3fe784d8a6..720c209498 100644 --- a/test/browser/components/container-test.js +++ b/test/browser/components/container-test.js @@ -244,13 +244,15 @@ test('Components -> Container -> Mount then rename', t => { // mount with mint: false t.doesNotThrow(() => { wrapper = mount( - <Container - getState={s => s.smoothie} - id={testId.id} - mapboxApiAccessToken="hello.world" - dispatch={dispatch} - store={store} - /> + <Provider store={store}> + <Container + getState={s => s.smoothie} + id={testId.id} + mapboxApiAccessToken="hello.world" + dispatch={dispatch} + store={store} + /> + </Provider> ); }, 'Should not throw error when mount'); @@ -283,14 +285,24 @@ test('Components -> Container -> Mount then rename', t => { }; t.deepEqual(nextState, expectedState, 'should register milkshake to root reducer'); - wrapper.setProps({id: 'milkshake-2'}); - // actions = store.getActions(); + wrapper.setProps({ + children: ( + <Container + getState={s => s.smoothie} + id={'milkshake-2'} + mapboxApiAccessToken="hello.world" + dispatch={dispatch} + store={store} + /> + ) + }); + const expectedActions1 = { type: '@@kepler.gl/RENAME_ENTRY', payload: {oldId: 'milkshake', newId: 'milkshake-2'} }; - t.deepEqual(store.getActions().pop(), expectedActions1, 'should rename entry'); + t.deepEqual(store.getActions().pop(), expectedActions1, 'should rename entry '); const nextState1 = appReducer(nextState, expectedActions1); const expectedState1 = { diff --git a/test/browser/components/injector-test.js b/test/browser/components/injector-test.js index 7cbf4dc9c8..0873e9c4da 100644 --- a/test/browser/components/injector-test.js +++ b/test/browser/components/injector-test.js @@ -235,7 +235,7 @@ test('Components -> injector -> withState.lens', t => { }); test('Components -> injector -> withState.mapStateToProps', t => { - const CustomHeader = () => <div clssName="my-test-header-3">smoothie</div>; + const CustomHeader = () => <div className="my-test-header-3">smoothie</div>; const myCustomHeaderFactory = () => withState([], state => ({ids: Object.keys(state)}))(CustomHeader); diff --git a/test/browser/components/side-panel/channel-by-value-selctor-test.js b/test/browser/components/side-panel/channel-by-value-selctor-test.js index c3472ce12d..b7797dcfb3 100644 --- a/test/browser/components/side-panel/channel-by-value-selctor-test.js +++ b/test/browser/components/side-panel/channel-by-value-selctor-test.js @@ -200,7 +200,7 @@ test('Components -> ChannelByValueSelector -> ColorScaleSelector -> disabled', t t.end(); }); -test.only('Components -> ChannelByValueSelector -> ColorScaleSelector -> ColorBreakDisplay', t => { +test('Components -> ChannelByValueSelector -> ColorScaleSelector -> ColorBreakDisplay', t => { const InitialState = cloneDeep(StateWFilesFiltersLayerColor); const {layers, datasets} = InitialState.visState; const pointLayer = layers[0]; diff --git a/test/browser/components/side-panel/layer-list.spec.js b/test/browser/components/side-panel/layer-list.spec.js index b06fe30da7..aaf310c297 100644 --- a/test/browser/components/side-panel/layer-list.spec.js +++ b/test/browser/components/side-panel/layer-list.spec.js @@ -20,19 +20,11 @@ import {keplerGlReducerCore as keplerGlReducer} from '@kepler.gl/reducers'; import {renderWithTheme} from '../../../helpers/component-jest-utils'; import testLayerData from '../../../fixtures/test-layer-data'; import {dataId as csvDataId} from '../../../fixtures/test-csv-data'; +import {applyActions} from '../../../helpers/mock-state'; // TODO: need to be deleted and imported from raw-states const InitialState = keplerGlReducer(undefined, keplerGlInit({})); -function applyActions(reducer, initialState, actions) { - const actionQ = Array.isArray(actions) ? actions : [actions]; - - return actionQ.reduce( - (updatedState, {action, payload}) => reducer(updatedState, action(...payload)), - initialState - ); -} - function mockStateWithMultipleH3Layers() { const initialState = cloneDeep(InitialState); const prepareState = applyActions(keplerGlReducer, initialState, [ diff --git a/test/browser/layer-tests/geojson-layer-specs.js b/test/browser/layer-tests/geojson-layer-specs.js index 771fdb95eb..80f9e01fd6 100644 --- a/test/browser/layer-tests/geojson-layer-specs.js +++ b/test/browser/layer-tests/geojson-layer-specs.js @@ -3,7 +3,7 @@ import test from 'tape'; import {defaultElevation, defaultLineWidth, defaultRadius, KeplerGlLayers} from '@kepler.gl/layers'; -import {copyTableAndUpdate, createNewDataEntry} from '@kepler.gl/table'; +import {copyTableAndUpdate} from '@kepler.gl/table'; const {GeojsonLayer} = KeplerGlLayers; @@ -18,6 +18,7 @@ import { geoFilterDomain0, geojsonFilterDomain0 } from 'test/helpers/layer-utils'; +import {createNewDataEntryMock} from 'test/helpers/table-utils'; import { updatedGeoJsonLayer, geoJsonWithStyle, @@ -48,7 +49,7 @@ test('#GeojsonLayer -> constructor', t => { t.end(); }); -test('#GeojsonLayer -> formatLayerData', t => { +test('#GeojsonLayer -> formatLayerData', async t => { const filteredIndex = [0, 2, 4]; const TEST_CASES = [ @@ -400,7 +401,7 @@ test('#GeojsonLayer -> formatLayerData', t => { color: [5, 5, 5] } }, - datasets: createNewDataEntry({ + datasets: await createNewDataEntryMock({ info: {id: dataId}, data: processGeojson(geoJsonWithStyle) }), diff --git a/test/browser/layer-tests/point-layer-specs.js b/test/browser/layer-tests/point-layer-specs.js index 58598f88d7..ab23683f8e 100644 --- a/test/browser/layer-tests/point-layer-specs.js +++ b/test/browser/layer-tests/point-layer-specs.js @@ -19,7 +19,8 @@ import {processGeojson} from '@kepler.gl/processors'; import {geoJsonWithStyle, geojsonData} from 'test/fixtures/geojson'; import testArcData, {pointFromNeighbor} from 'test/fixtures/test-arc-data'; import {StateWArcNeighbors} from 'test/helpers/mock-state'; -import {copyTableAndUpdate, createNewDataEntry} from '@kepler.gl/table'; +import {createNewDataEntryMock} from 'test/helpers/table-utils'; +import {copyTableAndUpdate} from '@kepler.gl/table'; import {KeplerGlLayers} from '@kepler.gl/layers'; import {INITIAL_MAP_STATE} from '@kepler.gl/reducers'; import {DEFAULT_TEXT_LABEL, PROJECTED_PIXEL_SIZE_MULTIPLIER} from '@kepler.gl/constants'; @@ -732,7 +733,7 @@ test('#PointLayer -> updateLayer', t => { t.end(); }); -test('#PointLayer -> formatLayerData -> Geojson column mode', t => { +test('#PointLayer -> formatLayerData -> Geojson column mode', async t => { // create a mockup GeoJson dataset with Point and MultiPoint geometry const geoJsonWithMultiPoint = cloneDeep(geoJsonWithStyle); geoJsonWithMultiPoint.features.push({ @@ -809,7 +810,7 @@ test('#PointLayer -> formatLayerData -> Geojson column mode', t => { } } }, - datasets: createNewDataEntry({ + datasets: await createNewDataEntryMock({ info: {id: dataId}, data: geojsonPointDataset }), @@ -873,7 +874,7 @@ test('#PointLayer -> formatLayerData -> Geojson column mode', t => { } } }, - datasets: createNewDataEntry({ + datasets: await createNewDataEntryMock({ info: {id: dataId}, data: geojsonPolygonDataset }), @@ -918,7 +919,7 @@ test('#PointLayer -> formatLayerData -> Geojson column mode', t => { } } }, - datasets: createNewDataEntry({ + datasets: await createNewDataEntryMock({ info: {id: dataId}, data: geojsonMultiPolygonDataset }), @@ -988,7 +989,7 @@ test('#PointLayer -> formatLayerData -> Geojson column mode', t => { } } }, - datasets: createNewDataEntry({ + datasets: await createNewDataEntryMock({ info: {id: dataId}, data: geojsonGeometryCollectionDataset }), @@ -1062,7 +1063,7 @@ test('#PointLayer -> formatLayerData -> Geojson column mode', t => { } } }, - datasets: createNewDataEntry({ + datasets: await createNewDataEntryMock({ info: {id: dataId}, data: geojsonPointWithNullDataset }), diff --git a/test/fixtures/synced-filter-with-trip-layer.js b/test/fixtures/synced-filter-with-trip-layer.js index 730ba55ecb..9db9ded364 100644 --- a/test/fixtures/synced-filter-with-trip-layer.js +++ b/test/fixtures/synced-filter-with-trip-layer.js @@ -5,7 +5,7 @@ import {processKeplerglJSON} from '@kepler.gl/processors'; import CloneDeep from 'lodash.clonedeep'; import {keplerGlReducerCore as coreReducer} from '@kepler.gl/reducers'; import {addDataToMap} from '@kepler.gl/actions'; -import {InitialState} from '../helpers/mock-state'; +import {InitialState, applyActions} from '../helpers/mock-state'; export const syncedFilterWithTripLayerMap = { datasets: [ @@ -391,7 +391,13 @@ export const syncedFilterWithTripLayerMap = { export const mockStateWithSyncedFilterAndTripLayer = () => { const initialState = CloneDeep(InitialState); const result = processKeplerglJSON(syncedFilterWithTripLayerMap); - const newState = coreReducer(initialState, addDataToMap(result)); + + const newState = applyActions(coreReducer, initialState, [ + { + action: addDataToMap, + payload: [result] + } + ]); return newState; }; diff --git a/test/helpers/comparison-utils.js b/test/helpers/comparison-utils.js index c66d8b0969..8ab7e18c54 100644 --- a/test/helpers/comparison-utils.js +++ b/test/helpers/comparison-utils.js @@ -2,7 +2,7 @@ // Copyright contributors to the kepler.gl project import {FILTER_TYPES} from '@kepler.gl/constants'; -import {toArray} from '@kepler.gl/utils'; +import {toArray} from '@kepler.gl/common-utils'; import {KeplerTable} from '@kepler.gl/table'; export function cmpObjectKeys(t, expectedObj, actualObj, name) { diff --git a/test/helpers/layer-utils.js b/test/helpers/layer-utils.js index 6e26f2249c..737481deff 100644 --- a/test/helpers/layer-utils.js +++ b/test/helpers/layer-utils.js @@ -291,24 +291,27 @@ export function testUpdateLayer(t, {layerConfig, shouldUpdate}) { value: [3, 8.33] }; - const stateWithLayer = keplerGlReducerCore( - initialState, - addDataToMap({ - datasets: { - info: {id: dataId}, - data: processCsvData(testLayerData) - }, + const addDataToMapPayload = { + datasets: { + info: {id: dataId}, + data: processCsvData(testLayerData) + }, + config: { + version: 'v1', config: { - version: 'v1', - config: { - visState: { - layers: [layerConfig], - filters: [filter0, filter1] - } + visState: { + layers: [layerConfig], + filters: [filter0, filter1] } } - }) - ); + } + }; + const stateWithLayer = applyActions(keplerGlReducerCore, initialState, [ + { + action: addDataToMap, + payload: [addDataToMapPayload] + } + ]); const layer = stateWithLayer.visState.layers[0]; t.ok(layer, 'should create layer'); diff --git a/test/helpers/mock-state.js b/test/helpers/mock-state.js index 04773661f4..c3121b7549 100644 --- a/test/helpers/mock-state.js +++ b/test/helpers/mock-state.js @@ -3,7 +3,7 @@ import test from 'tape-catch'; import cloneDeep from 'lodash.clonedeep'; -import {drainTasksForTesting} from 'react-palm/tasks'; +import {drainTasksForTesting, succeedTaskWithValues} from 'react-palm/tasks'; import { getInitialInputStyle, @@ -29,6 +29,7 @@ import { UIStateActions, ProviderActions } from '@kepler.gl/actions'; +import {KeplerTable} from '@kepler.gl/table'; // fixtures import { @@ -80,18 +81,69 @@ export const geojsonInfo = { params: {file: null} }; +/** + * Applies actions one by one using the reducer. + * After each action drain Tasks and automarically resolve tasks + * of type CREATE_TABLE_TASK in order to create datasets. + * @param {Reducer} reducer A reducer to use. + * @param {State} initialState Initial state. + * @param {Action | Action[]}actions An array of actions. + * @returns + */ export function applyActions(reducer, initialState, actions) { const actionQ = Array.isArray(actions) ? actions : [actions]; - return actionQ.reduce( - (updatedState, {action, payload}) => reducer(updatedState, action(...payload)), - initialState - ); + // remove any existing tasks before actions + drainTasksForTesting(); + + let updatedState = actionQ.reduce((updatedState, {action, payload}) => { + let newState = reducer(updatedState, action(...payload)); + const tasks = drainTasksForTesting(); + newState = applyCreateTableTasks(tasks, reducer, newState); + return newState; + }, initialState); + + return updatedState; } // TODO: need to be deleted and imported from raw-states export const InitialState = keplerGlReducer(undefined, {}); +/** + * Mock instant sync result of createNewDataEntry. + */ +const mockCreateNewDataEntry = ({info, color, opts, data}) => { + const table = new KeplerTable({info, color, ...opts}); + table.importData({data}); + return table; +}; + +/** + * Execute tasks and mock CREATE_TABLE_TASK with success. + * @param {Task[]} tasks + * @param {Reducer} reducer + * @param {VisState} initialState + * @returns + */ +export const applyCreateTableTasks = (tasks, reducer, initialState) => { + return tasks.reduce((updatedState, task) => { + if (!task.label.includes('CREATE_TABLE_TASK')) return updatedState; + const tables = task.payload.map(payload => mockCreateNewDataEntry(payload)); + return reducer(updatedState, succeedTaskWithValues(task, tables)); + }, initialState); +}; + +/** + * Execute existing tasts and mock CREATE_TABLE_TASK with success. + * @param {VisStateReducer} reducer + * @param {VisState} initialState + * @returns + */ +export function applyExistingDatasetTasks(reducer, initialState) { + const tasks = drainTasksForTesting(); + return applyCreateTableTasks(tasks, reducer, initialState); +} + /** * Mock app state with uploaded geojson and csv file * @returns {Immutable} appState @@ -114,8 +166,7 @@ export function mockStateWithFileUpload() { payload: [[{info: geojsonInfo, data: {fields: geojsonFields, rows: geojsonRows}}]] } ]); - // cleanup tasks created during loadMapStyles - drainTasksForTesting(); + // replace layer id and color with controlled value for testing updatedState.visState.layers.forEach((l, i) => { const oldLayerId = l.id; @@ -205,9 +256,6 @@ function mockStateWithLayerCustomColorBreaksLegends() { } ]); - // cleanup tasks created during loadMapStyles - drainTasksForTesting(); - test(t => { t.equal(updatedState.visState.layers.length, 1, 'should load 1 layer'); t.deepEqual( @@ -580,19 +628,22 @@ export function mockStateWithMultipleH3Layers() { function mockStateWithTripData() { const initialState = cloneDeep(InitialState); - return keplerGlReducer( - initialState, - addDataToMap({ - datasets: { - info: { - label: 'Sample Trips', - id: tripDataId - }, - data: {fields: tripFields, rows: tripRows} + const addDataPayload = { + datasets: { + info: { + label: 'Sample Trips', + id: tripDataId }, - config: tripConfig - }) - ); + data: {fields: tripFields, rows: tripRows} + }, + config: tripConfig + }; + return applyActions(keplerGlReducer, initialState, [ + { + action: addDataToMap, + payload: [addDataPayload] + } + ]); } export function mockStateWithLayerDimensions(state) { diff --git a/test/helpers/table-utils.js b/test/helpers/table-utils.js new file mode 100644 index 0000000000..3c97a17d46 --- /dev/null +++ b/test/helpers/table-utils.js @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// Copyright contributors to the kepler.gl project + +import {createNewDataEntry} from '@kepler.gl/table'; + +/** + * A test function to execute the task from createNewDataEntry with await syntax. + * @param {ProtoDataset} props + * @returns {Datasets} + */ +export const createNewDataEntryMock = async props => { + const newDataEntry = createNewDataEntry(props); + let table = null; + await newDataEntry.run( + async (effectorPrime, success, error) => { + const res = effectorPrime(success, error); + await res; + }, + value => { + table = value; + } + ); + return { + [props.info.id]: table + }; +}; diff --git a/test/node/reducers/composer-state-test.js b/test/node/reducers/composer-state-test.js index a0437f8d23..cab648aac1 100644 --- a/test/node/reducers/composer-state-test.js +++ b/test/node/reducers/composer-state-test.js @@ -8,10 +8,13 @@ import keplerGlReducer, { addDataToMapUpdater, replaceDataInMapUpdater, fitBoundsUpdater, - INITIAL_UI_STATE + INITIAL_UI_STATE, + visStateReducer, + mapStateReducer } from '@kepler.gl/reducers'; import {processCsvData} from '@kepler.gl/processors'; import {registerEntry} from '@kepler.gl/actions'; +import {drainTasksForTesting, succeedTaskWithValues} from 'react-palm/tasks'; import testCsvData, {sampleConfig, dataWithNulls} from 'test/fixtures/test-csv-data'; import testHexIdData, { @@ -21,6 +24,8 @@ import testHexIdData, { expectedMergedDataset } from 'test/fixtures/test-hex-id-data'; import {cmpLayers, cmpFilters, cmpDataset, cmpInteraction} from 'test/helpers/comparison-utils'; +import {applyExistingDatasetTasks} from 'test/helpers/mock-state'; + const mockRawData = { fields: [ { @@ -81,19 +86,21 @@ test('#composerStateReducer - addDataToMapUpdater: mapStyle', t => { } }); + drainTasksForTesting(); + t.equal(newState.mapStyle.styleType, 'light', 'Map style is set correctly'); t.end(); }); -test('#composerStateReducer - addDataToMapUpdater: mapState should be centered', t => { +test('#composerStateReducer - addDataToMapUpdater: mapState should be centered (after dataset tasts are completed)', t => { // init kepler.gl root and instance const state = keplerGlReducer({}, registerEntry({id: 'test'})).test; const mapStateProperties = { latitude: 33.88608913680742, longitude: -84.43459130456425 }; - const newState = addDataToMapUpdater(state, { + let newState = addDataToMapUpdater(state, { payload: { datasets: { data: mockRawData, @@ -110,6 +117,15 @@ test('#composerStateReducer - addDataToMapUpdater: mapState should be centered', } }); + // create datasets from existing tasks, trigger auto create layers + newState.visState = applyExistingDatasetTasks(visStateReducer, newState.visState); + + // layers should generate a fit bounds task + const tasks = drainTasksForTesting(); + t.equal(tasks.length, 1, 'One fit bounds task should be present'); + + newState.mapState = mapStateReducer(newState.mapState, succeedTaskWithValues(tasks[0], {})); + t.equal(newState.mapState.latitude, 29.23, 'centerMap: true should override mapState config'); t.equal(newState.mapState.longitude, 60.71, 'centerMap: true should override mapState config'); @@ -131,6 +147,8 @@ test('#composerStateReducer - addDataToMapUpdater: uiState', t => { } }); + drainTasksForTesting(); + const expectedUIState = { ...INITIAL_UI_STATE, initialState: {}, @@ -153,7 +171,7 @@ test('#composerStateReducer - addDataToMapUpdater: keepExistingConfig', t => { const state = keplerGlReducer({}, registerEntry({id: 'test'})).test; // old state contain splitMaps - const oldState = addDataToMapUpdater(state, { + let oldState = addDataToMapUpdater(state, { payload: { datasets: { data, @@ -165,6 +183,9 @@ test('#composerStateReducer - addDataToMapUpdater: keepExistingConfig', t => { } }); + // create datasets from existing tasks, trigger auto create layers + oldState = {...oldState, visState: applyExistingDatasetTasks(visStateReducer, oldState.visState)}; + const { layers: oldLayers, filters: oldFilters, @@ -177,7 +198,7 @@ test('#composerStateReducer - addDataToMapUpdater: keepExistingConfig', t => { const hexDataId = hexIdDataConfig.dataId; // keepExistingConfig is not defined, default to false - const nextState1 = addDataToMapUpdater(oldState, { + let nextState1 = addDataToMapUpdater(oldState, { payload: { datasets: { data: hexData, @@ -189,6 +210,12 @@ test('#composerStateReducer - addDataToMapUpdater: keepExistingConfig', t => { } }); + // create datasets from existing tasks, trigger auto create layers + nextState1 = { + ...nextState1, + visState: applyExistingDatasetTasks(visStateReducer, nextState1.visState) + }; + t.deepEqual(nextState1.visState.layerOrder, ['avlgol'], 'Should contain nextState1 layer order'); cmpDataset(t, expectedMergedDataset, nextState1.visState.datasets[hexDataId]); @@ -199,7 +226,7 @@ test('#composerStateReducer - addDataToMapUpdater: keepExistingConfig', t => { cmpFilters(t, mergedFilters, nextState1.visState.filters); // add data and config keep existing data and config - const nextState2 = addDataToMapUpdater(oldState, { + let nextState2 = addDataToMapUpdater(oldState, { payload: { datasets: { data: hexData, @@ -214,6 +241,12 @@ test('#composerStateReducer - addDataToMapUpdater: keepExistingConfig', t => { } }); + // create datasets from existing tasks, trigger auto create layers + nextState2 = { + ...nextState2, + visState: applyExistingDatasetTasks(visStateReducer, nextState2.visState) + }; + const actualVisState = nextState2.visState; const newLayers = [...oldLayers, mergedH3Layer]; @@ -378,20 +411,37 @@ test('#composerStateReducer - replaceDataInMapUpdater', t => { const state = keplerGlReducer({}, registerEntry({id: 'test'})).test; // old state contain splitMaps - const oldState = addDataToMapUpdater(state, { + let oldState = addDataToMapUpdater(state, { payload: { datasets, config: sampleConfig.config } }); + // create datasets from existing tasks, trigger auto create layers + oldState = {...oldState, visState: applyExistingDatasetTasks(visStateReducer, oldState.visState)}; + const oldSavedConfig = state.visState.schema.getConfigToSave(oldState).config; - const nextState = replaceDataInMapUpdater(oldState, { + let nextState = replaceDataInMapUpdater(oldState, { payload: { datasetToReplaceId: sampleConfig.dataId, datasetToUse } }); + // create datasets from existing tasks, trigger auto create layers + nextState = { + ...nextState, + visState: applyExistingDatasetTasks(visStateReducer, nextState.visState) + }; + + // layers should generate a fit bounds task + const tasks = drainTasksForTesting(); + t.equal(tasks.length, 1, 'A fit bounds Task should be present'); + nextState = { + ...nextState, + mapState: mapStateReducer(nextState.mapState, succeedTaskWithValues(tasks[0], {})) + }; + const nextSavedConfig = nextState.visState.schema.getConfigToSave(nextState).config; const expectedLayers = oldSavedConfig.visState.layers.map(l => ({ @@ -492,21 +542,28 @@ test('#composerStateReducer - replaceDataInMapUpdater: same dataId', t => { const state = keplerGlReducer({}, registerEntry({id: 'test'})).test; // old state contain splitMaps - const oldState = addDataToMapUpdater(state, { + let oldState = addDataToMapUpdater(state, { payload: { datasets, config: sampleConfig.config } }); + // create datasets from existing tasks, trigger auto create layers + oldState = {...oldState, visState: applyExistingDatasetTasks(visStateReducer, oldState.visState)}; const oldSavedConfig = state.visState.schema.getConfigToSave(oldState).config; - const nextState = replaceDataInMapUpdater(oldState, { + let nextState = replaceDataInMapUpdater(oldState, { payload: { datasetToReplaceId: sampleConfig.dataId, datasetToUse } }); + // create datasets from existing tasks, trigger auto create layers + nextState = { + ...nextState, + visState: applyExistingDatasetTasks(visStateReducer, nextState.visState) + }; // dataset should be replaced t.ok(nextState.visState.datasets[sampleConfig.dataId], ' dataset should be replaced'); diff --git a/test/node/reducers/root-test.js b/test/node/reducers/root-test.js index 1e70a5e541..5a96151d5e 100644 --- a/test/node/reducers/root-test.js +++ b/test/node/reducers/root-test.js @@ -14,6 +14,7 @@ import { ActionTypes } from '@kepler.gl/actions'; import {createAction, handleActions} from 'redux-actions'; +import {applyActions} from 'test/helpers/mock-state'; test('keplerGlReducer.initialState', t => { const test1Reducer = keplerGlReducer.initialState({ @@ -318,17 +319,20 @@ test('keplerGlReducer.plugin override', t => { let nextState = testReducer(undefined, registerEntry({id: 'test3'})); - nextState = testReducer( - nextState, - addDataToMap({ - datasets: { - data: mockRawData, - info: { - id: 'foo' - } + const addDataToMapPayload = { + datasets: { + data: mockRawData, + info: { + id: 'foo' } - }) - ); + } + }; + nextState = applyActions(testReducer, nextState, [ + { + action: addDataToMap, + payload: [addDataToMapPayload] + } + ]); t.equal(nextState.test3.visState.layers.length, 4, 'Should have 4 layer'); diff --git a/test/node/reducers/vis-state-merger-test.js b/test/node/reducers/vis-state-merger-test.js index 30ef16a336..8862e48ec6 100644 --- a/test/node/reducers/vis-state-merger-test.js +++ b/test/node/reducers/vis-state-merger-test.js @@ -81,7 +81,8 @@ import { StateWSplitMaps, testCsvDataId, testGeoJsonDataId, - StateWFiles + StateWFiles, + applyActions } from 'test/helpers/mock-state'; import { @@ -131,7 +132,9 @@ test('VisStateMerger.v0 -> mergeFilters -> toEmptyState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed filters cmpFilters(t, mergedFiltersV0, stateWData.filters); @@ -164,7 +167,9 @@ test('VisStateMerger.v1 -> mergeFilters -> toEmptyState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed filters cmpFilters(t, expectedMergedFilterV1, stateWData.filters); @@ -198,7 +203,9 @@ test('VisStateMerger.v0 -> mergeFilters -> toWorkingState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed filters cmpFilters(t, [...oldFilters, ...mergedFiltersV0], stateWData.filters); @@ -235,7 +242,9 @@ test('VisStateMerger.v1 -> mergeFilters -> toWorkingState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed filters cmpFilters(t, [...oldFilters, ...mergedFiltersV1], stateWData.filters); @@ -330,7 +339,9 @@ test('VisStateMerger.current -> mergeLayers -> toEmptyState', t => { const parsedData = SchemaManager.parseSavedData(appStateToSave.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed layers const genericLayersByOrder = stateToSave.visState.layerOrder.map(id => @@ -364,7 +375,10 @@ test('visStateMerger -> mergeLayer -> incremental load', t => { // load dataset2 const parsedData2 = SchemaManager.parseSavedData([dataset2]); - const stateWithData2 = coreReducer(stateWithConfig, addDataToMap({datasets: parsedData2})); + const stateWithData2 = applyActions(coreReducer, stateWithConfig, [ + {action: addDataToMap, payload: [{datasets: parsedData2}]} + ]); + t.deepEqual( stateWithData2.visState.preserveLayerOrder, ['hexagon-2', 'point-0', 'geojson-1'], @@ -390,7 +404,10 @@ test('visStateMerger -> mergeLayer -> incremental load', t => { // load dataset1 const parsedData1 = SchemaManager.parseSavedData([dataset1]); - const stateWithData1 = coreReducer(stateWithData2, addDataToMap({datasets: parsedData1})); + const stateWithData1 = applyActions(coreReducer, stateWithData2, [ + {action: addDataToMap, payload: [{datasets: parsedData1}]} + ]); + t.deepEqual( stateWithData1.visState.preserveLayerOrder, ['hexagon-2', 'point-0', 'geojson-1'], @@ -445,7 +462,9 @@ test('VisStateMerger.v1 -> mergeLayers -> toEmptyState', t => { const parsedData = SchemaManager.parseSavedData(savedStateV1.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed layers cmpLayers(t, mergedLayersV1, stateWData.layers, {id: true, color: true}); @@ -478,7 +497,9 @@ test('VisStateMerger.v1.label -> mergeLayers -> toEmptyState', t => { const parsedData = SchemaManager.parseSavedData(savedStateV1Label.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed layers cmpLayers(t, mergedLayersV1Label, stateWData.layers, {id: true}); @@ -527,7 +548,9 @@ test('VisStateMerger.v1.split -> mergeLayers -> toEmptyState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test split Maps t.deepEqual(stateWData.splitMaps, expectedConfig, 'should merge splitMaps'); @@ -578,7 +601,9 @@ test('VisStateMerger.v0 -> mergeLayers -> toWorkingState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed layers cmpLayers(t, [...oldLayers, ...mergedLayersV0], stateWData.layers); @@ -633,7 +658,9 @@ test('VisStateMerger.v1 -> mergeLayers -> toWorkingState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed filters cmpLayers(t, [...oldLayers, ...mergedLayersV1], stateWData.layers); @@ -717,7 +744,9 @@ test('VisStateMerger.v0 -> mergeInteractions -> toEmptyState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed interactions t.deepEqual(stateWData.interactionConfig, mergedInteractionsV0, 'should merge interactionConfig'); @@ -814,7 +843,9 @@ test('VisStateMerger.v0 -> mergeInteractions -> toWorkingState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); const expectedInteractions = { ...defaultInteractionConfig, @@ -986,7 +1017,9 @@ test('VisStateMerger.v1 -> mergeInteractions -> toEmptyState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); // test parsed interactions t.deepEqual(stateWData.interactionConfig, MergedInteractionV1, 'should merge interactionConfig'); @@ -1119,7 +1152,9 @@ test('VisStateMerger.v1 -> mergeInteractions -> toWorkingState', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); const expectedInteractions = { ...defaultInteractionConfig, @@ -1259,7 +1294,9 @@ test('VisStateMerger.v1 -> mergeInteractions -> coordinate', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // load data into reducer - const stateWData = visStateReducer(mergedState, updateVisData(parsedData)); + const stateWData = applyActions(visStateReducer, mergedState, [ + {action: updateVisData, payload: [parsedData]} + ]); const expectedInteractions = { ...defaultInteractionConfig, @@ -1403,7 +1440,9 @@ test('VisStateMerger - mergeSplitMaps -> split to split', t => { const parsedData = SchemaManager.parseSavedData(savedConfig.datasets); // 3. load data into reducer - const mergedState3 = visStateReducer(mergedState2, updateVisData(parsedData)); + const mergedState3 = applyActions(visStateReducer, mergedState2, [ + {action: updateVisData, payload: [parsedData]} + ]); t.deepEqual(mergedState3.splitMaps, expectedToMergeAll, 'Should merge all splitMaps'); t.deepEqual(mergedState3.splitMapsToBeMerged, [], 'Should empty splitMapsToBeMerged'); @@ -1490,7 +1529,9 @@ test('VisStateMerger - mergeTripGeojson', t => { // processKeplerglJSON const result = processKeplerglJSON(savedStateV1TripGeoJson); - const updatedCore = coreReducer(initialState, addDataToMap(result)); + const updatedCore = applyActions(coreReducer, initialState, [ + {action: addDataToMap, payload: [result]} + ]); const mergedVieState = updatedCore.visState; @@ -1566,10 +1607,9 @@ test('VisStateMerger.v1 -> mergeFilters -> multiFilters', t => { const oldState = cloneDeep(InitialState); const oldVisState = oldState.visState; - const mergedState = visStateReducer( - oldVisState, - updateVisData(stateParsed.datasets, {}, stateParsed.config) - ); + const mergedState = applyActions(visStateReducer, oldVisState, [ + {action: updateVisData, payload: [stateParsed.datasets, {}, stateParsed.config]} + ]); // check datasets is filtered // and field has filterProps @@ -1725,7 +1765,9 @@ test('VisStateMerger.v1 -> mergeFilters -> syncedFilters', t => { t.equal(datasets.length, 2, 'should save 2 datasets'); // load config to initial state - const stateWithConfig = coreReducer(InitialState, addDataToMap({config})); + const stateWithConfig = applyActions(coreReducer, InitialState, [ + {action: addDataToMap, payload: [{config}]} + ]); t.equal(stateWithConfig.visState.filters.length, 0, 'should not load filter without data'); t.equal( @@ -1737,12 +1779,17 @@ test('VisStateMerger.v1 -> mergeFilters -> syncedFilters', t => { const parsedDatasets = SchemaManager.parseSavedData(datasets); // load data 1 - const stateWithData1 = coreReducer(stateWithConfig, addDataToMap({datasets: parsedDatasets[0]})); + const stateWithData1 = applyActions(coreReducer, stateWithConfig, [ + {action: addDataToMap, payload: [{datasets: parsedDatasets[0]}]} + ]); + t.equal(Object.keys(stateWithData1.visState.datasets).length, 1, 'should load 1 dataset'); t.equal(stateWithData1.visState.filters.length, 0, 'should not load filter without all datasets'); // load data 2 - const stateWithData2 = coreReducer(stateWithData1, addDataToMap({datasets: parsedDatasets[1]})); + const stateWithData2 = applyActions(coreReducer, stateWithData1, [ + {action: addDataToMap, payload: [{datasets: parsedDatasets[1]}]} + ]); t.equal(Object.keys(stateWithData2.visState.datasets).length, 2, 'should load 2 datasets'); t.equal( stateWithData2.visState.filters.length, @@ -1991,10 +2038,9 @@ test('VisStateMerger -> load time filter/trip layer synced map', t => { const initialState = cloneDeep(InitialState); const initialVisState = initialState.visState; - const visState = visStateReducer( - initialVisState, - updateVisData(stateParsed.datasets, {}, stateParsed.config) - ); + const visState = applyActions(visStateReducer, initialVisState, [ + {action: updateVisData, payload: [stateParsed.datasets, {}, stateParsed.config]} + ]); const newFilter = visState.filters[0]; @@ -2131,7 +2177,7 @@ const mockReducer = keplerGlReducer }); // eslint-disable-next-line max-statements -test('VisStateMerger -> asyne mergers', t => { +test('VisStateMerger -> asynс mergers', t => { // adding mock process to state const stateToSave = cloneDeep(StateWMultiFilters); const appStateToSave = SchemaManager.save(stateToSave); @@ -2148,15 +2194,13 @@ test('VisStateMerger -> asyne mergers', t => { const initialState = mockReducer(undefined, registerEntry({id: 'test'})); // apply config with process to merge - const nextState = mockReducer( - initialState, - // add csv data first - updateVisData( - stateParsed.datasets.find(d => d.info.id === testCsvDataId), - {}, - configWithProcess - ) - ); + const nextState = applyActions(mockReducer, initialState, [ + { + // add csv data first + action: updateVisData, + payload: [stateParsed.datasets.find(d => d.info.id === testCsvDataId), {}, configWithProcess] + } + ]); t.deepEqual( nextState.test.visState.isMergingDatasets, @@ -2177,11 +2221,13 @@ test('VisStateMerger -> asyne mergers', t => { t.equal(tasks[0].type, 'MOCK_MERGE_TASK', 'should create merger task'); // add another dataset will async merger is in process - const nextState1 = mockReducer( - nextState, - // add geojson data - updateVisData(stateParsed.datasets.find(d => d.info.id === testGeoJsonDataId)) - ); + const nextState1 = applyActions(mockReducer, nextState, [ + { + // add geojson data + action: updateVisData, + payload: [stateParsed.datasets.find(d => d.info.id === testGeoJsonDataId)] + } + ]); t.ok(nextState1.test.visState.datasets[testGeoJsonDataId], 'should add geojson data'); diff --git a/test/node/reducers/vis-state-test.js b/test/node/reducers/vis-state-test.js index ea7d743656..7309d7b56f 100644 --- a/test/node/reducers/vis-state-test.js +++ b/test/node/reducers/vis-state-test.js @@ -27,7 +27,7 @@ import { import {processCsvData, processGeojson} from '@kepler.gl/processors'; import {Layer, KeplerGlLayers, COLUMN_MODE_TABLE} from '@kepler.gl/layers'; -import {createNewDataEntry, maybeToDate} from '@kepler.gl/table'; +import {maybeToDate} from '@kepler.gl/table'; import { createDataContainer, applyFilterFieldName, @@ -107,6 +107,7 @@ import { testCsvDataSlice2Id } from '../../fixtures/test-csv-data'; import {mockStateWithSyncedFilterAndTripLayer} from '../../fixtures/synced-filter-with-trip-layer'; +import {createNewDataEntryMock} from 'test/helpers/table-utils'; const mockData = { fields: [ @@ -361,11 +362,11 @@ test('#visStateReducer -> LAYER_TYPE_CHANGE.0', t => { t.end(); }); -test('#visStateReducer -> LAYER_TYPE_CHANGE.1', t => { +test('#visStateReducer -> LAYER_TYPE_CHANGE.1', async t => { const layer = new Layer({id: 'more_layer'}); const oldState = { ...INITIAL_VIS_STATE, - datasets: createNewDataEntry({ + datasets: await createNewDataEntryMock({ info: {id: 'puppy', label: 'puppy'}, data: { rows: mockData.data, @@ -432,14 +433,14 @@ test('#visStateReducer -> LAYER_TYPE_CHANGE.1', t => { t.end(); }); -test('#visStateReducer -> LAYER_TYPE_CHANGE.2', t => { +test('#visStateReducer -> LAYER_TYPE_CHANGE.2', async t => { const pointLayer = new PointLayer({id: 'a', dataId: 'smoothie'}); const mockColorRange = { name: 'abc', isReversed: true, colors: ['a', 'b', 'c'] }; - const datasets = createNewDataEntry({ + const datasets = await createNewDataEntryMock({ info: {id: 'smoothie'}, data: { rows: testAllData, @@ -543,7 +544,7 @@ test('#visStateReducer -> LAYER_TYPE_CHANGE.2', t => { t.end(); }); -test('#visStateReducer -> LAYER_TYPE_CHANGE.3 -> animationConfig', t => { +test('#visStateReducer -> LAYER_TYPE_CHANGE.3 -> animationConfig', async t => { const layer = new GeojsonLayer({ label: 'taro and blue', dataId: 'taro', @@ -553,7 +554,7 @@ test('#visStateReducer -> LAYER_TYPE_CHANGE.3 -> animationConfig', t => { id: 'taro' }); - const dataset = createNewDataEntry({ + const dataset = await createNewDataEntryMock({ info: {id: 'taro'}, data: processGeojson(tripGeojson) }); @@ -698,10 +699,12 @@ test('#visStateReducer -> LAYER_CONFIG_CHANGE -> isVisible -> splitMaps', t => { test('#visStateReducer -> LAYER_CONFIG_CHANGE -> columnMode', t => { const initialState = InitialState.visState; // const initialState = cloneDeep(state || InitialState); - const updatedState = reducer( - initialState, - VisStateActions.updateVisData({info: tripCsvDataInfo, data: processCsvData(tripCsvData)}) - ); + const updatedState = applyActions(reducer, initialState, [ + { + action: VisStateActions.updateVisData, + payload: [{info: tripCsvDataInfo, data: processCsvData(tripCsvData)}] + } + ]); const pointLayer = updatedState.layers[0]; // change layer type to trip @@ -824,16 +827,20 @@ test('visStateReducer -> layerDataIdChangeUpdater', t => { test('visStateReducer -> layerDataIdChangeUpdater -> geojson', t => { const initialState = CloneDeep(StateWFilesFiltersLayerColor).visState; - const nextState = reducer( - initialState, + const nextState = applyActions(reducer, initialState, [ // add another geojson - VisStateActions.updateVisData([ - { - info: {id: 'geojson2', label: 'Some Geojson'}, - data: {fields: geojsonFields, rows: geojsonRows.slice(0, 3)} - } - ]) - ); + { + action: VisStateActions.updateVisData, + payload: [ + [ + { + info: {id: 'geojson2', label: 'Some Geojson'}, + data: {fields: geojsonFields, rows: geojsonRows.slice(0, 3)} + } + ] + ] + } + ]); // find geojson layer const index = nextState.layers.findIndex(l => l.type === 'geojson'); @@ -874,10 +881,12 @@ test('visStateReducer -> layerDataIdChangeUpdater -> validation', t => { ...row.slice(fieldIdx + 1, row.length) ]); // add another dataset - const nextState = reducer( - initialState, - VisStateActions.updateVisData([{info: newDataInfo, data: {fields, rows}}]) - ); + const nextState = applyActions(reducer, initialState, [ + { + action: VisStateActions.updateVisData, + payload: [[{info: newDataInfo, data: {fields, rows}}]] + } + ]); const nextState1 = reducer( nextState, @@ -1361,16 +1370,20 @@ test('#visStateReducer -> UPDATE_VIS_DATA.1 -> No data', t => { test('#visStateReducer -> UPDATE_VIS_DATA.2 -> to empty state', t => { const oldState = INITIAL_VIS_STATE; - const newState = reducer( - oldState, - VisStateActions.updateVisData([ - { - data: mockRawData, - info: {id: 'smoothie', label: 'exciting dataset'}, - metadata: {album: 'taro_and_blue'} - } - ]) - ); + const newState = applyActions(reducer, oldState, [ + { + action: VisStateActions.updateVisData, + payload: [ + [ + { + data: mockRawData, + info: {id: 'smoothie', label: 'exciting dataset'}, + metadata: {album: 'taro_and_blue'} + } + ] + ] + } + ]); const expectedDatasets = { smoothie: { @@ -1547,15 +1560,19 @@ test('#visStateReducer -> UPDATE_VIS_DATA.3 -> merge w/ existing state', t => { smoothie: [] }; - const newState = reducer( - oldState, - VisStateActions.updateVisData([ - { - data: mockRawData, - info: {id: 'smoothie', label: 'smoothie and milkshake'} - } - ]) - ); + const newState = applyActions(reducer, oldState, [ + { + action: VisStateActions.updateVisData, + payload: [ + [ + { + data: mockRawData, + info: {id: 'smoothie', label: 'smoothie and milkshake'} + } + ] + ] + } + ]); Object.keys(expectedDatasets).forEach(key => cmpDataset(t, expectedDatasets[key], newState.datasets[key]) @@ -1607,7 +1624,12 @@ test('#visStateReducer -> UPDATE_VIS_DATA.4.Geojson -> geojson data', t => { const [layer1Color, layer1StrokeColor] = getNextColorMakerValue(2); // receive data - const initialState = reducer(initialVisState, VisStateActions.updateVisData(payload)); + const initialState = applyActions(reducer, initialVisState, [ + { + action: VisStateActions.updateVisData, + payload: [payload] + } + ]); const expectedDatasets = { metadata: { @@ -1706,7 +1728,12 @@ test('#visStateReducer -> UPDATE_VIS_DATA.4.Geojson -> with config', t => { ]; // receive data - const initialState = reducer(initialVisState, VisStateActions.updateVisData(payload)); + const initialState = applyActions(reducer, initialVisState, [ + { + action: VisStateActions.updateVisData, + payload: [payload] + } + ]); t.equal(initialState.layers.length, 1, 'should create 1 layer'); @@ -1740,7 +1767,12 @@ test('#visStateReducer -> UPDATE_VIS_DATA.4.Geojson -> with config', t => { } }; - const testState = reducer(initialState, VisStateActions.updateVisData(datasets, {}, config)); + const testState = applyActions(reducer, initialState, [ + { + action: VisStateActions.updateVisData, + payload: [datasets, {}, config] + } + ]); t.deepEqual( Object.keys(testState.datasets), @@ -1804,15 +1836,19 @@ test('#visStateReducer -> UPDATE_VIS_DATA -> mergeFilters', t => { value: mockFilter.value }; - const newState = reducer( - oldState, - VisStateActions.updateVisData([ - { - data: mockRawData, - info: {id: 'smoothie', label: 'smoothie and milkshake'} - } - ]) - ); + const newState = applyActions(reducer, oldState, [ + { + action: VisStateActions.updateVisData, + payload: [ + [ + { + data: mockRawData, + info: {id: 'smoothie', label: 'smoothie and milkshake'} + } + ] + ] + } + ]); const dc = createDataContainer(mockRawData.rows, {fields: mockRawData.fields}); const allIndexes = dc.getPlainIndex(); @@ -1951,15 +1987,19 @@ test('#visStateReducer -> UPDATE_VIS_DATA.SPLIT_MAPS', t => { layerOrder: [layers[2].id, layers[1].id, layers[0].id, layers[3].id] }; - const newState = reducer( - oldState, - VisStateActions.updateVisData([ - { - data: mockRawData, - info: {id: 'smoothie', label: 'smoothie and milkshake'} - } - ]) - ); + const newState = applyActions(reducer, oldState, [ + { + action: VisStateActions.updateVisData, + payload: [ + [ + { + data: mockRawData, + info: {id: 'smoothie', label: 'smoothie and milkshake'} + } + ] + ] + } + ]); // first visible layer should be point const id1 = newState.layers[4].id; @@ -2018,7 +2058,12 @@ test('#visStateReducer -> setFilter.dynamicDomain & cpu', t => { ]; // receive data - const initialState = reducer(INITIAL_VIS_STATE, VisStateActions.updateVisData(payload)); + const initialState = applyActions(reducer, INITIAL_VIS_STATE, [ + { + action: VisStateActions.updateVisData, + payload: [payload] + } + ]); const expectedLayer1 = new PointLayer({ isVisible: true, @@ -2650,7 +2695,12 @@ function testSetFilterDynamicDomainGPU(t, setFilter) { ]; // receive data - const initialState = reducer(INITIAL_VIS_STATE, VisStateActions.updateVisData(payload)); + const initialState = applyActions(reducer, INITIAL_VIS_STATE, [ + { + action: VisStateActions.updateVisData, + payload: [payload] + } + ]); // add filter const stateWithFilter = reducer(initialState, VisStateActions.addFilter('milkshake')); @@ -2827,7 +2877,7 @@ test('#visStateReducer -> UPDATE_FILTER_ANIMATION_SPEED', t => { t.end(); }); -test('#visStateReducer -> setFilter.fixedDomain & DynamicDomain & gpu & cpu', t => { +test('#visStateReducer -> setFilter.fixedDomain & DynamicDomain & gpu & cpu', async t => { // get test data const {fields, rows} = processCsvData(testData); const payload = [ @@ -2840,13 +2890,15 @@ test('#visStateReducer -> setFilter.fixedDomain & DynamicDomain & gpu & cpu', t } ]; - const datasetSmoothie = createNewDataEntry({ - info: {id: 'smoothie', label: 'queen smoothie'}, - data: { - rows: testAllData, - fields: testFields - } - }).smoothie; + const datasetSmoothie = ( + await createNewDataEntryMock({ + info: {id: 'smoothie', label: 'queen smoothie'}, + data: { + rows: testAllData, + fields: testFields + } + }) + ).smoothie; // add fixedDomain & gpu filter const stateWidthTsFilter = applyActions(reducer, INITIAL_VIS_STATE, [ @@ -3178,7 +3230,12 @@ test('#visStateReducer -> SET_FILTER_PLOT.yAxis', t => { ]; // receive data - const initialState = reducer(INITIAL_VIS_STATE, VisStateActions.updateVisData(payload)); + const initialState = applyActions(reducer, INITIAL_VIS_STATE, [ + { + action: VisStateActions.updateVisData, + payload: [payload] + } + ]); // add filter const stateWithFilter = reducer(initialState, VisStateActions.addFilter('smoothie')); @@ -4834,7 +4891,12 @@ test('#visStateReducer -> POLYGON: Create polygon filter', t => { }; // visStateUpdateVisDataUpdater - creates 4 layers - let newReducer = reducer(state, VisStateActions.updateVisData(datasets, options, {})); + let newReducer = applyActions(reducer, state, [ + { + action: VisStateActions.updateVisData, + payload: [datasets, options, {}] + } + ]); // add new polygon feature newReducer = reducer(newReducer, VisStateActions.setFeatures([mockPolygonFeature])); @@ -4901,7 +4963,12 @@ test('#visStateReducer -> POLYGON: Create polygon filter', t => { t.equal(newReducer.layerData[1].data.length, 0, 'Layer Point 2 show show 0 points'); // Adding a new dataset - creates extra 4 layers - newReducer = reducer(newReducer, VisStateActions.updateVisData(datasets, options, {})); + newReducer = applyActions(reducer, newReducer, [ + { + action: VisStateActions.updateVisData, + payload: [datasets, options, {}] + } + ]); t.equal(newReducer.layerData[4].data.length, 4, 'Layer Point 5 should full data'); @@ -5038,7 +5105,12 @@ test('#visStateReducer -> POLYGON: Toggle filter feature', t => { }; // visStateUpdateVisDataUpdater - creates 4 layers - let newReducer = reducer(state, VisStateActions.updateVisData(datasets, options, {})); + let newReducer = applyActions(reducer, state, [ + { + action: VisStateActions.updateVisData, + payload: [datasets, options, {}] + } + ]); newReducer = reducer(newReducer, VisStateActions.addLayer()); @@ -5208,7 +5280,12 @@ test('#visStateReducer -> POLYGON: delete polygon filter', t => { }; // visStateUpdateVisDataUpdater - creates 4 layers - let newReducer = reducer(state, VisStateActions.updateVisData(datasets, options, {})); + let newReducer = applyActions(reducer, state, [ + { + action: VisStateActions.updateVisData, + payload: [datasets, options, {}] + } + ]); newReducer = reducer(newReducer, VisStateActions.addLayer()); @@ -5598,9 +5675,10 @@ test('#visStateReducer -> LOAD_FILES', async t => { } const nextState = reducer(initialState, VisStateActions.loadFiles(mockFiles)); - const [task1, ...more] = drainTasksForTesting(); - t.equal(more.length, 0, 'should ceate 1 task'); + const tasks = drainTasksForTesting(); + t.equal(tasks.length, 2, 'should ceate 2 tasks'); + const task1 = tasks[1]; const expectedTask1 = { type: 'LOAD_FILE_TASK', diff --git a/test/node/utils/data-processor-test.js b/test/node/utils/data-processor-test.js index e59cb368be..2715851e89 100644 --- a/test/node/utils/data-processor-test.js +++ b/test/node/utils/data-processor-test.js @@ -32,14 +32,14 @@ import { processRowObject } from '@kepler.gl/processors'; +import {validateInputData, createDataContainer} from '@kepler.gl/utils'; + import { ACCEPTED_ANALYZER_TYPES, analyzerTypeToFieldType, - getSampleForTypeAnalyze, - validateInputData, getFieldsFromData, - createDataContainer -} from '@kepler.gl/utils'; + getSampleForTypeAnalyze +} from '@kepler.gl/common-utils'; import {formatCsv} from '@kepler.gl/reducers'; diff --git a/test/node/utils/index.js b/test/node/utils/index.js index 2a857792ad..5ee5b38a3c 100644 --- a/test/node/utils/index.js +++ b/test/node/utils/index.js @@ -4,6 +4,7 @@ import './data-utils-test'; import './data-processor-test'; import './kepler-table-test'; +import './kepler-table-utils-test'; import './data-container-test'; import './filter-utils-test'; import './gpu-filter-utils-test'; diff --git a/test/node/utils/kepler-table-test.js b/test/node/utils/kepler-table-test.js index 833d7b0fe8..10fba3e369 100644 --- a/test/node/utils/kepler-table-test.js +++ b/test/node/utils/kepler-table-test.js @@ -6,12 +6,13 @@ import moment from 'moment'; import testData, {numericRangesCsv, testFields} from 'test/fixtures/test-csv-data'; import {preciseRound, getFilterFunction} from '@kepler.gl/utils'; -import {createNewDataEntry, findPointFieldPairs} from '@kepler.gl/table'; - +import {findPointFieldPairs} from '@kepler.gl/table'; import {processCsvData} from '@kepler.gl/processors'; -import {cmpFields} from '../../helpers/comparison-utils'; import {FILTER_TYPES} from '@kepler.gl/constants'; +import {cmpFields} from '../../helpers/comparison-utils'; +import {createNewDataEntryMock} from '../../helpers/table-utils'; + function testGetTimeFieldDomain(table, t) { const test_cases = [ { @@ -215,15 +216,16 @@ function testGetFilterFunction({fields, dataContainer}, t) { ); } -test('KeplerTable -> getColumnFilterDomain -> time', t => { +test('KeplerTable -> getColumnFilterDomain -> time', async t => { const expectedFields = testFields; const data = processCsvData(testData); - const newDataEntry = createNewDataEntry({ - info: {id: 'test'}, - data - }); - const dataset = newDataEntry.test; + const dataset = ( + await createNewDataEntryMock({ + info: {id: 'test'}, + data + }) + ).test; cmpFields(t, expectedFields, dataset.fields, dataset.id); testGetTimeFieldDomain(dataset, t); testGetFilterFunction(dataset, t); @@ -233,11 +235,12 @@ test('KeplerTable -> getColumnFilterDomain -> time', t => { test('KeplerTable -> getColumnFilterDomain -> numeric', async t => { const data = processCsvData(numericRangesCsv); - const newDataEntry = createNewDataEntry({ - info: {id: 'test'}, - data - }); - const dataset = newDataEntry.test; + const dataset = ( + await createNewDataEntryMock({ + info: {id: 'test'}, + data + }) + ).test; testGetNumericFieldStep(dataset, t); diff --git a/test/node/utils/kepler-table-utils-test.js b/test/node/utils/kepler-table-utils-test.js new file mode 100644 index 0000000000..3d934f61b5 --- /dev/null +++ b/test/node/utils/kepler-table-utils-test.js @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +// Copyright contributors to the kepler.gl project + +import test from 'tape'; + +import {createNewDataEntry} from '@kepler.gl/table'; + +test('Dataset Utils -> createNewDataEntry', t => { + const task = createNewDataEntry({ + info: {id: 'test', color: [0, 92, 255]}, + data: { + rows: [[10], [20]], + fields: [] + } + }); + + t.equal(task.label, 'CREATE_TABLE_TASK', 'should create CREATE_TABLE_TASK task'); + t.equal(task.type, 'CREATE_TABLE_TASK', 'should create CREATE_TABLE_TASK task'); + t.deepEqual( + task.payload, + { + info: {id: 'test', color: [0, 92, 255]}, + color: [0, 92, 255], + opts: {}, + data: {rows: [[10], [20]], fields: [], cols: undefined} + }, + 'should create correct CREATE_TABLE_TASK task payload' + ); + + t.end(); +}); diff --git a/test/node/utils/layer-utils-test.js b/test/node/utils/layer-utils-test.js index 740b785ef3..61e2cb012a 100644 --- a/test/node/utils/layer-utils-test.js +++ b/test/node/utils/layer-utils-test.js @@ -14,9 +14,10 @@ const {PointLayer, ArcLayer, GeojsonLayer, LineLayer} = KeplerGlLayers; import {wktCsv} from 'test/fixtures/test-csv-data'; import {cmpLayers} from 'test/helpers/comparison-utils'; import {getNextColorMakerValue} from 'test/helpers/layer-utils'; +import {createNewDataEntryMock} from 'test/helpers/table-utils'; import tripGeojson, {timeStampDomain, tripBounds} from 'test/fixtures/trip-geojson'; import {geoJsonWithStyle} from 'test/fixtures/geojson'; -import {KeplerTable, findPointFieldPairs, createNewDataEntry} from '@kepler.gl/table'; +import {KeplerTable, findPointFieldPairs} from '@kepler.gl/table'; import {createDataContainer} from '@kepler.gl/utils'; test('layerUtils -> findDefaultLayer.1', t => { @@ -282,7 +283,9 @@ test('layerUtils -> findDefaultLayer.2', t => { info: { id: dataId, label: 'sf_zip_geo' - }, + } + }); + dataset.importData({ data: { rows: [ [ @@ -531,7 +534,9 @@ test('layerUtils -> findDefaultLayer:GeojsonLayer', t => { const dataset = new KeplerTable({ info: { label: 'sf_zip_geo' - }, + } + }); + dataset.importData({ data: { rows: [ [ @@ -811,7 +816,7 @@ test('layerUtils -> findDefaultLayer: TripLayer', t => { t.end(); }); -test('layerUtils -> findDefaultLayer: TripLayer.1 -> no ts', t => { +test('layerUtils -> findDefaultLayer: TripLayer.1 -> no ts', async t => { // change 3rd coordinate to string const modified = tripGeojson.features.map(f => ({ ...f, @@ -826,7 +831,7 @@ test('layerUtils -> findDefaultLayer: TripLayer.1 -> no ts', t => { features: modified }; - const dataset = createNewDataEntry({ + const dataset = await createNewDataEntryMock({ info: {id: 'taro'}, data: processGeojson(noTripGeojson) }); @@ -839,7 +844,7 @@ test('layerUtils -> findDefaultLayer: TripLayer.1 -> no ts', t => { t.end(); }); -test('layerUtils -> findDefaultLayer: TripLayer.1 -> ts as string', t => { +test('layerUtils -> findDefaultLayer: TripLayer.1 -> ts as string', async t => { const tripData = { type: 'FeatureCollection', features: [ @@ -858,7 +863,7 @@ test('layerUtils -> findDefaultLayer: TripLayer.1 -> ts as string', t => { ] }; - const dataset = createNewDataEntry({ + const dataset = await createNewDataEntryMock({ info: {id: 'taro'}, data: processGeojson(tripData) }); diff --git a/test/node/utils/util-test.js b/test/node/utils/util-test.js index a90bb00af8..0bc7d450f8 100644 --- a/test/node/utils/util-test.js +++ b/test/node/utils/util-test.js @@ -8,9 +8,9 @@ import { camelize, capitalizeFirstLetter, getError, - set, - toArray + set } from '@kepler.gl/utils'; +import {toArray} from '@kepler.gl/common-utils'; import test from 'tape'; test('Utils -> set', t => { diff --git a/yarn.lock b/yarn.lock index 3ce66ec1ed..f6627c194f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2991,16 +2991,16 @@ __metadata: languageName: node linkType: hard -"@kepler.gl/actions@npm:3.0.0, @kepler.gl/actions@workspace:src/actions": +"@kepler.gl/actions@npm:3.1.0-alpha.0, @kepler.gl/actions@workspace:src/actions": version: 0.0.0-use.local resolution: "@kepler.gl/actions@workspace:src/actions" dependencies: "@deck.gl/core": "npm:^8.9.27" - "@kepler.gl/cloud-providers": "npm:3.0.0" - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/layers": "npm:3.0.0" - "@kepler.gl/processors": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" + "@kepler.gl/cloud-providers": "npm:3.1.0-alpha.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/layers": "npm:3.1.0-alpha.0" + "@kepler.gl/processors": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" "@reduxjs/toolkit": "npm:^1.7.2" "@types/lodash.curry": "npm:^4.1.7" "@types/react-redux": "npm:^7.1.23" @@ -3013,30 +3013,42 @@ __metadata: languageName: unknown linkType: soft -"@kepler.gl/ai-assistant@npm:3.0.0, @kepler.gl/ai-assistant@workspace:src/ai-assistant": +"@kepler.gl/ai-assistant@npm:3.1.0-alpha.0, @kepler.gl/ai-assistant@workspace:src/ai-assistant": version: 0.0.0-use.local resolution: "@kepler.gl/ai-assistant@workspace:src/ai-assistant" dependencies: - "@kepler.gl/components": "npm:3.0.0" - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/layers": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" - "@kepler.gl/utils": "npm:3.0.0" + "@kepler.gl/components": "npm:3.1.0-alpha.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/layers": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" + "@kepler.gl/utils": "npm:3.1.0-alpha.0" global: "npm:^4.3.0" react-ai-assist: "npm:0.0.14" languageName: unknown linkType: soft -"@kepler.gl/cloud-providers@npm:3.0.0, @kepler.gl/cloud-providers@workspace:src/cloud-providers": +"@kepler.gl/cloud-providers@npm:3.1.0-alpha.0, @kepler.gl/cloud-providers@workspace:src/cloud-providers": version: 0.0.0-use.local resolution: "@kepler.gl/cloud-providers@workspace:src/cloud-providers" dependencies: - "@kepler.gl/types": "npm:3.0.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" react: "npm:^18.2.0" languageName: unknown linkType: soft -"@kepler.gl/components@npm:3.0.0, @kepler.gl/components@workspace:src/components": +"@kepler.gl/common-utils@npm:3.1.0-alpha.0, @kepler.gl/common-utils@workspace:src/common-utils": + version: 0.0.0-use.local + resolution: "@kepler.gl/common-utils@workspace:src/common-utils" + dependencies: + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" + d3-array: "npm:^2.8.0" + global: "npm:^4.3.0" + type-analyzer: "npm:0.4.0" + languageName: unknown + linkType: soft + +"@kepler.gl/components@npm:3.1.0-alpha.0, @kepler.gl/components@workspace:src/components": version: 0.0.0-use.local resolution: "@kepler.gl/components@workspace:src/components" dependencies: @@ -3047,19 +3059,20 @@ __metadata: "@dnd-kit/sortable": "npm:^7.0.2" "@dnd-kit/utilities": "npm:^3.2.1" "@floating-ui/react": "npm:0.25.1" - "@kepler.gl/actions": "npm:3.0.0" - "@kepler.gl/cloud-providers": "npm:3.0.0" - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/effects": "npm:3.0.0" - "@kepler.gl/layers": "npm:3.0.0" - "@kepler.gl/localization": "npm:3.0.0" - "@kepler.gl/processors": "npm:3.0.0" - "@kepler.gl/reducers": "npm:3.0.0" - "@kepler.gl/schemas": "npm:3.0.0" - "@kepler.gl/styles": "npm:3.0.0" - "@kepler.gl/table": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" - "@kepler.gl/utils": "npm:3.0.0" + "@kepler.gl/actions": "npm:3.1.0-alpha.0" + "@kepler.gl/cloud-providers": "npm:3.1.0-alpha.0" + "@kepler.gl/common-utils": "npm:3.1.0-alpha.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/effects": "npm:3.1.0-alpha.0" + "@kepler.gl/layers": "npm:3.1.0-alpha.0" + "@kepler.gl/localization": "npm:3.1.0-alpha.0" + "@kepler.gl/processors": "npm:3.1.0-alpha.0" + "@kepler.gl/reducers": "npm:3.1.0-alpha.0" + "@kepler.gl/schemas": "npm:3.1.0-alpha.0" + "@kepler.gl/styles": "npm:3.1.0-alpha.0" + "@kepler.gl/table": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" + "@kepler.gl/utils": "npm:3.1.0-alpha.0" "@mapbox/mapbox-sdk": "npm:^0.15.3" "@nebula.gl/edit-modes": "npm:1.0.2-alpha.1" "@tippyjs/react": "npm:^4.2.0" @@ -3137,11 +3150,11 @@ __metadata: languageName: unknown linkType: soft -"@kepler.gl/constants@npm:3.0.0, @kepler.gl/constants@workspace:src/constants": +"@kepler.gl/constants@npm:3.1.0-alpha.0, @kepler.gl/constants@workspace:src/constants": version: 0.0.0-use.local resolution: "@kepler.gl/constants@workspace:src/constants" dependencies: - "@kepler.gl/types": "npm:3.0.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" "@types/d3-scale": "npm:^3.2.2" "@types/keymirror": "npm:^0.1.1" colorbrewer: "npm:^1.5.0" @@ -3151,7 +3164,7 @@ __metadata: languageName: unknown linkType: soft -"@kepler.gl/deckgl-arrow-layers@npm:3.0.0, @kepler.gl/deckgl-arrow-layers@workspace:src/deckgl-arrow-layers": +"@kepler.gl/deckgl-arrow-layers@npm:3.1.0-alpha.0, @kepler.gl/deckgl-arrow-layers@workspace:src/deckgl-arrow-layers": version: 0.0.0-use.local resolution: "@kepler.gl/deckgl-arrow-layers@workspace:src/deckgl-arrow-layers" dependencies: @@ -3169,7 +3182,7 @@ __metadata: languageName: unknown linkType: soft -"@kepler.gl/deckgl-layers@npm:3.0.0, @kepler.gl/deckgl-layers@workspace:src/deckgl-layers": +"@kepler.gl/deckgl-layers@npm:3.1.0-alpha.0, @kepler.gl/deckgl-layers@workspace:src/deckgl-layers": version: 0.0.0-use.local resolution: "@kepler.gl/deckgl-layers@workspace:src/deckgl-layers" dependencies: @@ -3178,9 +3191,9 @@ __metadata: "@deck.gl/core": "npm:^8.9.27" "@deck.gl/geo-layers": "npm:^8.9.27" "@deck.gl/layers": "npm:^8.9.27" - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" - "@kepler.gl/utils": "npm:3.0.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" + "@kepler.gl/utils": "npm:3.1.0-alpha.0" "@luma.gl/constants": "npm:^8.5.20" "@luma.gl/core": "npm:^8.5.20" "@mapbox/geo-viewport": "npm:^0.4.1" @@ -3198,21 +3211,21 @@ __metadata: languageName: unknown linkType: soft -"@kepler.gl/effects@npm:3.0.0, @kepler.gl/effects@workspace:src/effects": +"@kepler.gl/effects@npm:3.1.0-alpha.0, @kepler.gl/effects@workspace:src/effects": version: 0.0.0-use.local resolution: "@kepler.gl/effects@workspace:src/effects" dependencies: "@deck.gl/core": "npm:^8.9.27" - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" - "@kepler.gl/utils": "npm:3.0.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" + "@kepler.gl/utils": "npm:3.1.0-alpha.0" "@luma.gl/core": "npm:^8.5.20" "@luma.gl/shadertools": "npm:^8.5.20" suncalc: "npm:^1.9.0" languageName: unknown linkType: soft -"@kepler.gl/layers@npm:3.0.0, @kepler.gl/layers@workspace:src/layers": +"@kepler.gl/layers@npm:3.1.0-alpha.0, @kepler.gl/layers@workspace:src/layers": version: 0.0.0-use.local resolution: "@kepler.gl/layers@workspace:src/layers" dependencies: @@ -3222,13 +3235,14 @@ __metadata: "@deck.gl/geo-layers": "npm:^8.9.27" "@deck.gl/layers": "npm:^8.9.27" "@deck.gl/mesh-layers": "npm:^8.9.27" - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/deckgl-arrow-layers": "npm:3.0.0" - "@kepler.gl/deckgl-layers": "npm:3.0.0" - "@kepler.gl/localization": "npm:3.0.0" - "@kepler.gl/table": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" - "@kepler.gl/utils": "npm:3.0.0" + "@kepler.gl/common-utils": "npm:3.1.0-alpha.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/deckgl-arrow-layers": "npm:3.1.0-alpha.0" + "@kepler.gl/deckgl-layers": "npm:3.1.0-alpha.0" + "@kepler.gl/localization": "npm:3.1.0-alpha.0" + "@kepler.gl/table": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" + "@kepler.gl/utils": "npm:3.1.0-alpha.0" "@loaders.gl/arrow": "npm:^4.3.2" "@loaders.gl/core": "npm:^4.3.2" "@loaders.gl/gis": "npm:^4.3.2" @@ -3265,7 +3279,7 @@ __metadata: languageName: unknown linkType: soft -"@kepler.gl/localization@npm:3.0.0, @kepler.gl/localization@workspace:src/localization": +"@kepler.gl/localization@npm:3.1.0-alpha.0, @kepler.gl/localization@workspace:src/localization": version: 0.0.0-use.local resolution: "@kepler.gl/localization@workspace:src/localization" dependencies: @@ -3275,15 +3289,17 @@ __metadata: languageName: unknown linkType: soft -"@kepler.gl/processors@npm:3.0.0, @kepler.gl/processors@workspace:src/processors": +"@kepler.gl/processors@npm:3.1.0-alpha.0, @kepler.gl/processors@workspace:src/processors": version: 0.0.0-use.local resolution: "@kepler.gl/processors@workspace:src/processors" dependencies: "@danmarshall/deckgl-typings": "npm:4.9.22" - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/schemas": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" - "@kepler.gl/utils": "npm:3.0.0" + "@kepler.gl/common-utils": "npm:3.1.0-alpha.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/schemas": "npm:3.1.0-alpha.0" + "@kepler.gl/table": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" + "@kepler.gl/utils": "npm:3.1.0-alpha.0" "@loaders.gl/arrow": "npm:^4.3.2" "@loaders.gl/core": "npm:^4.3.2" "@loaders.gl/csv": "npm:^4.3.2" @@ -3298,24 +3314,25 @@ __metadata: languageName: unknown linkType: soft -"@kepler.gl/reducers@npm:3.0.0, @kepler.gl/reducers@workspace:src/reducers": +"@kepler.gl/reducers@npm:3.1.0-alpha.0, @kepler.gl/reducers@workspace:src/reducers": version: 0.0.0-use.local resolution: "@kepler.gl/reducers@workspace:src/reducers" dependencies: - "@kepler.gl/actions": "npm:3.0.0" - "@kepler.gl/cloud-providers": "npm:3.0.0" - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/deckgl-arrow-layers": "npm:3.0.0" - "@kepler.gl/deckgl-layers": "npm:3.0.0" - "@kepler.gl/effects": "npm:3.0.0" - "@kepler.gl/layers": "npm:3.0.0" - "@kepler.gl/localization": "npm:3.0.0" - "@kepler.gl/processors": "npm:3.0.0" - "@kepler.gl/schemas": "npm:3.0.0" - "@kepler.gl/table": "npm:3.0.0" - "@kepler.gl/tasks": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" - "@kepler.gl/utils": "npm:3.0.0" + "@kepler.gl/actions": "npm:3.1.0-alpha.0" + "@kepler.gl/cloud-providers": "npm:3.1.0-alpha.0" + "@kepler.gl/common-utils": "npm:3.1.0-alpha.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/deckgl-arrow-layers": "npm:3.1.0-alpha.0" + "@kepler.gl/deckgl-layers": "npm:3.1.0-alpha.0" + "@kepler.gl/effects": "npm:3.1.0-alpha.0" + "@kepler.gl/layers": "npm:3.1.0-alpha.0" + "@kepler.gl/localization": "npm:3.1.0-alpha.0" + "@kepler.gl/processors": "npm:3.1.0-alpha.0" + "@kepler.gl/schemas": "npm:3.1.0-alpha.0" + "@kepler.gl/table": "npm:3.1.0-alpha.0" + "@kepler.gl/tasks": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" + "@kepler.gl/utils": "npm:3.1.0-alpha.0" "@loaders.gl/loader-utils": "npm:^4.3.2" "@turf/bbox": "npm:^6.0.1" "@types/lodash.clonedeep": "npm:^4.5.7" @@ -3343,16 +3360,17 @@ __metadata: languageName: unknown linkType: soft -"@kepler.gl/schemas@npm:3.0.0, @kepler.gl/schemas@workspace:src/schemas": +"@kepler.gl/schemas@npm:3.1.0-alpha.0, @kepler.gl/schemas@workspace:src/schemas": version: 0.0.0-use.local resolution: "@kepler.gl/schemas@workspace:src/schemas" dependencies: - "@kepler.gl/ai-assistant": "npm:3.0.0" - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/layers": "npm:3.0.0" - "@kepler.gl/table": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" - "@kepler.gl/utils": "npm:3.0.0" + "@kepler.gl/ai-assistant": "npm:3.1.0-alpha.0" + "@kepler.gl/common-utils": "npm:3.1.0-alpha.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/layers": "npm:3.1.0-alpha.0" + "@kepler.gl/table": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" + "@kepler.gl/utils": "npm:3.1.0-alpha.0" "@loaders.gl/loader-utils": "npm:^4.3.2" "@types/keymirror": "npm:^0.1.1" "@types/lodash.clonedeep": "npm:^4.5.7" @@ -3364,54 +3382,57 @@ __metadata: languageName: unknown linkType: soft -"@kepler.gl/styles@npm:3.0.0, @kepler.gl/styles@workspace:src/styles": +"@kepler.gl/styles@npm:3.1.0-alpha.0, @kepler.gl/styles@workspace:src/styles": version: 0.0.0-use.local resolution: "@kepler.gl/styles@workspace:src/styles" dependencies: - "@kepler.gl/constants": "npm:3.0.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" "@types/styled-components": "npm:^5.1.25" styled-components: "npm:^4.1.3" languageName: unknown linkType: soft -"@kepler.gl/table@npm:3.0.0, @kepler.gl/table@workspace:src/table": +"@kepler.gl/table@npm:3.1.0-alpha.0, @kepler.gl/table@workspace:src/table": version: 0.0.0-use.local resolution: "@kepler.gl/table@workspace:src/table" dependencies: - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/layers": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" - "@kepler.gl/utils": "npm:3.0.0" + "@kepler.gl/common-utils": "npm:3.1.0-alpha.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/layers": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" + "@kepler.gl/utils": "npm:3.1.0-alpha.0" "@types/d3-array": "npm:^2.8.0" "@types/lodash.uniq": "npm:^4.5.7" d3-array: "npm:^2.8.0" global: "npm:^4.3.0" lodash.uniq: "npm:^4.0.1" moment: "npm:^2.10.6" + react-palm: "npm:^3.3.8" languageName: unknown linkType: soft -"@kepler.gl/tasks@npm:3.0.0, @kepler.gl/tasks@workspace:src/tasks": +"@kepler.gl/tasks@npm:3.1.0-alpha.0, @kepler.gl/tasks@workspace:src/tasks": version: 0.0.0-use.local resolution: "@kepler.gl/tasks@workspace:src/tasks" dependencies: - "@kepler.gl/processors": "npm:3.0.0" + "@kepler.gl/processors": "npm:3.1.0-alpha.0" react-palm: "npm:^3.3.8" languageName: unknown linkType: soft -"@kepler.gl/types@npm:3.0.0, @kepler.gl/types@workspace:src/types": +"@kepler.gl/types@npm:3.1.0-alpha.0, @kepler.gl/types@workspace:src/types": version: 0.0.0-use.local resolution: "@kepler.gl/types@workspace:src/types" languageName: unknown linkType: soft -"@kepler.gl/utils@npm:3.0.0, @kepler.gl/utils@workspace:src/utils": +"@kepler.gl/utils@npm:3.1.0-alpha.0, @kepler.gl/utils@workspace:src/utils": version: 0.0.0-use.local resolution: "@kepler.gl/utils@workspace:src/utils" dependencies: - "@kepler.gl/constants": "npm:3.0.0" - "@kepler.gl/types": "npm:3.0.0" + "@kepler.gl/common-utils": "npm:3.1.0-alpha.0" + "@kepler.gl/constants": "npm:3.1.0-alpha.0" + "@kepler.gl/types": "npm:3.1.0-alpha.0" "@luma.gl/constants": "npm:^8.5.20" "@luma.gl/core": "npm:^8.5.20" "@mapbox/geo-viewport": "npm:^0.4.1" @@ -19973,7 +19994,7 @@ __metadata: "@deck.gl/test-utils": "npm:^8.9.27" "@hubble.gl/core": "npm:1.2.0-alpha.6" "@hubble.gl/react": "npm:1.2.0-alpha.6" - "@kepler.gl/components": "npm:3.0.0" + "@kepler.gl/components": "npm:3.1.0-alpha.0" "@loaders.gl/polyfills": "npm:^4.3.2" "@luma.gl/test-utils": "npm:^8.5.20" "@nebula.gl/layers": "npm:1.0.2-alpha.1"