UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

222 lines (199 loc) 7.72 kB
// Copyright (c) 2020 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import {handleActions} from 'redux-actions'; import {_actionFor, _updateProperty} from '../actions/action-wrapper'; import {keplerGlInit} from '../actions/actions'; import {coreReducerFactory} from './core'; import ActionTypes from 'constants/action-types'; // INITIAL_STATE const initialCoreState = {}; export function provideInitialState(initialState) { const coreReducer = coreReducerFactory(initialState); const handleRegisterEntry = ( state, {payload: {id, mint, mapboxApiAccessToken, mapboxApiUrl, mapStylesReplaceDefault}} ) => { // by default, always create a mint state even if the same id already exist // if state.id exist and mint=false, keep the existing state const previousState = state[id] && mint === false ? state[id] : undefined; return { // register entry to kepler.gl passing in mapbox config to mapStyle ...state, [id]: coreReducer( previousState, keplerGlInit({mapboxApiAccessToken, mapboxApiUrl, mapStylesReplaceDefault}) ) }; }; const handleDeleteEntry = (state, {payload: id}) => Object.keys(state).reduce( (accu, curr) => ({ ...accu, ...(curr === id ? {} : {[curr]: state[curr]}) }), {} ); const handleRenameEntry = (state, {payload: {oldId, newId}}) => Object.keys(state).reduce( (accu, curr) => ({ ...accu, ...{[curr === oldId ? newId : curr]: state[curr]} }), {} ); return (state = initialCoreState, action) => { // update child states Object.keys(state).forEach(id => { const updateItemState = coreReducer(state[id], _actionFor(id, action)); state = _updateProperty(state, id, updateItemState); }); // perform additional state reducing (e.g. switch action.type etc...) return handleActions( { [ActionTypes.REGISTER_ENTRY]: handleRegisterEntry, [ActionTypes.DELETE_ENTRY]: handleDeleteEntry, [ActionTypes.RENAME_ENTRY]: handleRenameEntry }, initialCoreState )(state, action); }; } const _keplerGlReducer = provideInitialState(); function mergeInitialState(saved = {}, provided = {}) { const keys = ['mapState', 'mapStyle', 'visState', 'uiState']; // shallow merge each reducer return keys.reduce( (accu, key) => ({ ...accu, ...(saved[key] && provided[key] ? {[key]: {...saved[key], ...provided[key]}} : {[key]: saved[key] || provided[key] || {}}) }), {} ); } function decorate(target, savedInitialState = {}) { const targetInitialState = savedInitialState; /** * Returns a kepler.gl reducer that will also pass each action through additional reducers spiecified. * The parameter should be either a reducer map or a reducer function. * The state passed into the additional action handler is the instance state. * It will include all the subreducers `visState`, `uiState`, `mapState` and `mapStyle`. * `.plugin` is only meant to be called once when mounting the keplerGlReducer to the store. * **Note** This is an advanced option to give you more freedom to modify the internal state of the kepler.gl instance. * You should only use this to adding additional actions instead of replacing default actions. * * @mixin keplerGlReducer.plugin * @memberof keplerGlReducer * @param {Object|Function} customReducer - A reducer map or a reducer * @public * @example * const myKeplerGlReducer = keplerGlReducer * .plugin({ * // 1. as reducer map * HIDE_AND_SHOW_SIDE_PANEL: (state, action) => ({ * ...state, * uiState: { * ...state.uiState, * readOnly: !state.uiState.readOnly * } * }) * }) * .plugin(handleActions({ * // 2. as reducer * 'HIDE_MAP_CONTROLS': (state, action) => ({ * ...state, * uiState: { * ...state.uiState, * mapControls: hiddenMapControl * } * }) * }, {})); */ target.plugin = function plugin(customReducer) { if (typeof customReducer === 'object') { // if only provided a reducerMap, wrap it in a reducer customReducer = handleActions(customReducer, {}); } // use 'function' keyword to enable 'this' return decorate((state = {}, action = {}) => { let nextState = this(state, action); // for each entry in the staten Object.keys(nextState).forEach(id => { // update child states nextState = _updateProperty( nextState, id, customReducer(nextState[id], _actionFor(id, action)) ); }); return nextState; }); }; /** * Return a reducer that initiated with custom initial state. * The parameter should be an object mapping from `subreducer` name to custom subreducer state, * which will be shallow **merged** with default initial state. * * Default subreducer state: * - [`visState`](./vis-state.md#INITIAL_VIS_STATE) * - [`mapState`](./map-state.md#INITIAL_MAP_STATE) * - [`mapStyle`](./map-style.md#INITIAL_MAP_STYLE) * - [`uiState`](./ui-state.md#INITIAL_UI_STATE) * @mixin keplerGlReducer.initialState * @memberof keplerGlReducer * @param {Object} iniSt - custom state to be merged with default initial state * @public * @example * const myKeplerGlReducer = keplerGlReducer * .initialState({ * uiState: {readOnly: true} * }); */ target.initialState = function initialState(iniSt) { const merged = mergeInitialState(targetInitialState, iniSt); const targetReducer = provideInitialState(merged); return decorate(targetReducer, merged); }; return target; } /** * Kepler.gl reducer to be mounted to your store. You can mount `keplerGlReducer` at property `keplerGl`, if you choose * to mount it at another address e.g. `foo` you will need to specify it when you mount `KeplerGl` component in your app with `getState: state => state.foo` * @public * @example * import keplerGlReducer from 'kepler.gl/reducers'; * import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; * import {taskMiddleware} from 'react-palm/tasks'; * * const initialState = {}; * const reducers = combineReducers({ * // <-- mount kepler.gl reducer in your app * keplerGl: keplerGlReducer, * * // Your other reducers here * app: appReducer * }); * * // using createStore * export default createStore(reducer, initialState, applyMiddleware(taskMiddleware)); */ const keplerGlReducer = decorate(_keplerGlReducer); export default keplerGlReducer;