Skip to content

Commit

Permalink
Add more hooks and improve isolation of kepler.gl within components (#88
Browse files Browse the repository at this point in the history
)

* Add more hooks and improve isolation of kepler within components

- Remove kepler state selectors from within Stage component.
- Create hooks for kepler keyframes and frame preparation.
- Add deckProps and staticMapProps override
- TODO: Remove kepler state selectors StageMap and StageContainer too.
- TODO: timeline reducer contains kepler-specific keyframe state. Looking to be more flexible.
  • Loading branch information
chrisgervang authored May 17, 2021
1 parent 8eabc09 commit 197920d
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 116 deletions.
15 changes: 10 additions & 5 deletions examples/worldview/src/app/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@ import styled, {ThemeProvider} from 'styled-components';
import {IntlProvider} from 'react-intl';
import {messages} from 'kepler.gl/localization';
import {theme} from 'kepler.gl/styles';
import {Stage} from '../features/stage/Stage';
import {useSelector} from 'react-redux';
import {InjectKeplerUI} from '@hubble.gl/react';
import {LoadingSpinner} from 'kepler.gl/components';
const KEPLER_UI = {
LoadingSpinner
};

import {useKepler} from '../features/kepler/hooks';
import {Stage} from '../features/stage/Stage';
import {useKepler, useKeplerKeyframes, usePrepareKeplerFrame} from '../features/kepler/hooks';

import {useNewYorkScene} from '../scenes/newYork';

Expand Down Expand Up @@ -77,10 +76,16 @@ const WindowSize = styled.div`

const App = ({}) => {
const ready = useSelector(state => state.hubbleGl.map.ready);
useKepler();
// TODO: kepler can be mounted at any location in redux.
const keplerLayers = useSelector(
state => state.keplerGl.map && state.keplerGl.map.visState.layers
);
const getKeyframes = useKeplerKeyframes(keplerLayers);
const prepareFrame = usePrepareKeplerFrame(keplerLayers);
// const state = useSelector(state => state);
// console.log(state);

useKepler();
useNewYorkScene();

return (
Expand All @@ -92,7 +97,7 @@ const App = ({}) => {
{ready && (
<>
<div style={{height: 1080, margin: 16}}>
<Stage />
<Stage getKeyframes={getKeyframes} prepareFrame={prepareFrame} />
</div>
</>
)}
Expand Down
74 changes: 71 additions & 3 deletions examples/worldview/src/features/kepler/hooks.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {useEffect} from 'react';
import {registerEntry} from 'kepler.gl/actions';
import {AUTH_TOKENS} from '../../constants';
import {useEffect, useCallback} from 'react';
import {registerEntry, setFilter, layerVisConfigChange} from 'kepler.gl/actions';
import {FilterValueKeyframes, Keyframes} from '@hubble.gl/core';
import {useDispatch, useSelector} from 'react-redux';

import {filterKeyframeSelector, layerKeyframeSelector} from '../timeline/timelineSlice';
import {AUTH_TOKENS} from '../../constants';
import {updateViewState} from '../stage/mapSlice';

export const useKepler = () => {
Expand Down Expand Up @@ -40,3 +42,69 @@ export const useKeplerMapState = () => {
}
}, [keplerMapState]);
};

export const useKeplerKeyframes = keplerLayers => {
const filterKeyframe = useSelector(filterKeyframeSelector);
const layerKeyframe = useSelector(layerKeyframeSelector);

const getKeplerKeyframes = useCallback(() => {
let keyframes = {};

if (Object.keys(layerKeyframe).length > 0) {
keyframes = Object.entries(layerKeyframe).reduce((acc, [key, value]) => {
// TODO: Use layer ID instead of label.
const matchedLayer = keplerLayers.find(layer => layer.config.label === value.label);
if (matchedLayer) {
const features = Object.keys(matchedLayer.config.visConfig);
acc[key] = new Keyframes({...value, features});
} else {
throw new Error(`Error making kepler layer keyframe. Layer not found: '${value.label}'`);
}
return acc;
}, keyframes);
}
// console.log(keyframes, keplerLayers);
if (filterKeyframe) {
// TODO: Support more than one filter.
keyframes.hubble_timeFilter = new FilterValueKeyframes(filterKeyframe);
}
return keyframes;
}, [filterKeyframe, layerKeyframe, keplerLayers]);

return getKeplerKeyframes;
};

export const usePrepareKeplerFrame = keplerLayers => {
const dispatch = useDispatch();

const prepareFrame = useCallback(
scene => {
// console.log(scene)
// console.log(scene.keyframes.timeFilter.getFrame())
// Filter Frame
if (scene.keyframes.hubble_timeFilter) {
const frame = scene.keyframes.hubble_timeFilter.getFrame();
dispatch(
setFilter(scene.keyframes.hubble_timeFilter.filterId, 'value', [frame.left, frame.right])
);
}

// Vis Config Frame
keplerLayers.forEach(layer => {
// TODO: Use layer ID instead of label.
const keyframe = scene.keyframes[layer.config.label];
if (keyframe) {
// console.log(layer)
const frame = keyframe.getFrame();
// console.log(frame)
dispatch(layerVisConfigChange(layer, frame));
}
});

// Note: Map State is kept in sync using plugin.
},
[keplerLayers]
);

return prepareFrame;
};
2 changes: 2 additions & 0 deletions examples/worldview/src/features/renderer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ export {
busySelector,
durationSelector
} from './rendererSlice';

export {useRenderHandler, usePreviewHandler} from './hooks';
99 changes: 23 additions & 76 deletions examples/worldview/src/features/stage/Stage.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,7 @@ import {
import {AutoSizer} from 'react-virtualized';
import {WithKeplerUI} from '@hubble.gl/react';
import StageContainer from './StageContainer';
import {CameraKeyframes, FilterValueKeyframes, Keyframes} from '@hubble.gl/core';
import {
cameraKeyframeSelector,
filterKeyframeSelector,
layerKeyframeSelector
} from '../timeline/timelineSlice';
import {setFilter, layerVisConfigChange} from 'kepler.gl/actions';
import {updateViewState} from './mapSlice';
import {useCameraKeyframes, usePrepareCameraFrame} from '../timeline/hooks';

const StageBottomToolbar = ({playing, onPreview}) => {
return (
Expand Down Expand Up @@ -100,80 +93,28 @@ const StageMapBox = ({height, width, children}) => (
</div>
);

export const Stage = ({}) => {
export const Stage = ({
getCameraKeyframes = undefined,
getKeyframes,
prepareFrame,
deckProps = undefined,
staticMapProps = undefined
}) => {
const rendererBusy = useSelector(busySelector);
const duration = useSelector(durationSelector);
const dispatch = useDispatch();

const cameraKeyframe = useSelector(cameraKeyframeSelector);
const dimension = useSelector(dimensionSelector);
const getCameraKeyframes = useCallback(() => {
const camera = new CameraKeyframes({
...cameraKeyframe,
width: dimension.width,
height: dimension.height
});
return camera;
}, [cameraKeyframe]);

const filterKeyframe = useSelector(filterKeyframeSelector);
const layerKeyframe = useSelector(layerKeyframeSelector);

const keplerLayers = useSelector(
state => state.keplerGl.map && state.keplerGl.map.visState.layers
);

const getKeyframes = useCallback(() => {
let keyframes = {};

if (Object.keys(layerKeyframe).length > 0) {
keyframes = Object.entries(layerKeyframe).reduce((acc, [key, value]) => {
const matchedLayer = keplerLayers.find(layer => layer.config.label === value.label);
if (matchedLayer) {
// console.log("Matched!", matchedLayer)
const features = Object.keys(matchedLayer.config.visConfig);
acc[key] = new Keyframes({...value, features});
}
return acc;
}, keyframes);
}
if (!getCameraKeyframes) {
getCameraKeyframes = useCameraKeyframes();
}

// console.log(keyframes, keplerLayers);

if (filterKeyframe) {
keyframes.hubble_timeFilter = new FilterValueKeyframes(filterKeyframe);
}
return keyframes;
}, [filterKeyframe, layerKeyframe]);

const prepareFrame = useCallback(
const prepareCameraFrame = usePrepareCameraFrame();
const combinedPrepareFrame = useCallback(
scene => {
// console.log(scene)
// console.log(scene.keyframes.timeFilter.getFrame())

// Filter Frame
if (scene.keyframes.hubble_timeFilter) {
const frame = scene.keyframes.hubble_timeFilter.getFrame();
dispatch(
setFilter(scene.keyframes.hubble_timeFilter.filterId, 'value', [frame.left, frame.right])
);
}

// Vis Config Frame
keplerLayers.forEach(layer => {
const keyframe = scene.keyframes[layer.config.label];
if (keyframe) {
// console.log(layer)
const frame = keyframe.getFrame();
// console.log(frame)
dispatch(layerVisConfigChange(layer, frame));
}
});

// Map State
dispatch(updateViewState(scene.keyframes.camera.getFrame()));
prepareFrame(scene);
prepareCameraFrame(scene);
},
[getKeyframes]
[prepareFrame]
);

const handlePreview = useCallback(() => {
Expand Down Expand Up @@ -209,7 +150,13 @@ export const Stage = ({}) => {
{({mapHeight, mapWidth, availableHeight, availableWidth}) => (
<StageMapBox width={availableWidth} height={availableHeight}>
{/* <div style={{width: mapWidth, height: mapHeight, backgroundColor: 'green'}} /> */}
<StageContainer width={mapWidth} height={mapHeight} prepareFrame={prepareFrame} />
<StageContainer
width={mapWidth}
height={mapHeight}
prepareFrame={combinedPrepareFrame}
deckProps={deckProps}
staticMapProps={staticMapProps}
/>
<StageMapOverlay
rendererBusy={rendererBusy}
duration={duration}
Expand Down
41 changes: 10 additions & 31 deletions examples/worldview/src/features/stage/StageContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,13 @@ import {DeckAdapter, DeckScene} from '@hubble.gl/core';
import {StageMap} from './StageMap';
import {connect} from 'react-redux';

import {setupRenderer, dimensionSelector, busySelector, durationSelector} from '../renderer';
import {setupRenderer, dimensionSelector, durationSelector} from '../renderer';

import {updateViewState, viewStateSelector} from './mapSlice';

export class StageContainer extends Component {
constructor(props) {
super(props);

// this.setMediaType = this.setMediaType.bind(this);
// this.setCameraPreset = this.setCameraPreset.bind(this);
// this.setFileName = this.setFileName.bind(this);
// this.setResolution = this.setResolution.bind(this);
this.getDeckScene = this.getDeckScene.bind(this);

const {glContext, dispatch} = props;
Expand All @@ -61,43 +56,28 @@ export class StageContainer extends Component {
});
}

setStateAndNotify(update) {
const {
props: {onSettingsChange},
state
} = this;
this.setState({...state, ...update});

if (onSettingsChange) {
const {mediaType, cameraPreset, fileName, resolution, durationMs} = state;
onSettingsChange({
mediaType,
cameraPreset,
fileName,
resolution,
durationMs,
...update
});
}
}

render() {
const {
width,
height,
mapData,
// state
mapData,
viewState,
dimension,
// actions
dispatch,
prepareFrame
// own props
width,
height,
prepareFrame,
deckProps,
staticMapProps
} = this.props;

const {adapter} = this.state;

return (
<StageMap
deckProps={deckProps}
staticMapProps={staticMapProps}
// UI Props
width={width}
height={height}
Expand All @@ -121,7 +101,6 @@ StageContainer.defaultProps = {

const mapStateToProps = state => {
return {
busy: busySelector(state),
dimension: dimensionSelector(state),
viewState: viewStateSelector(state),
duration: durationSelector(state),
Expand Down
4 changes: 3 additions & 1 deletion examples/worldview/src/features/stage/StageMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export class StageMap extends Component {
}

render() {
const {adapter, viewState, width, height, setViewState} = this.props;
const {adapter, viewState, width, height, setViewState, deckProps, staticMapProps} = this.props;

const deckStyle = {
width: '100%',
Expand All @@ -232,6 +232,7 @@ export class StageMap extends Component {
onViewStateChange={({viewState: vs}) => setViewState(vs)}
// onClick={visStateActions.onLayerClick}
{...adapter.getProps({deckRef: this.deckRef, setReady: () => {}})}
{...deckProps}
>
{this.state.glContext && (
<StaticMap
Expand All @@ -240,6 +241,7 @@ export class StageMap extends Component {
preventStyleDiffing={true}
gl={this.state.glContext}
onLoad={this._onMapLoad}
{...staticMapProps}
/>
)}
</DeckGL>
Expand Down
30 changes: 30 additions & 0 deletions examples/worldview/src/features/timeline/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {useCallback} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {CameraKeyframes} from '@hubble.gl/core';

import {cameraKeyframeSelector} from './timelineSlice';
import {dimensionSelector} from '../renderer';
import {updateViewState} from '../stage/mapSlice';

export function useCameraKeyframes() {
const cameraKeyframe = useSelector(cameraKeyframeSelector);
const dimension = useSelector(dimensionSelector);
const getCameraKeyframes = useCallback(() => {
const camera = new CameraKeyframes({
...cameraKeyframe,
width: dimension.width,
height: dimension.height
});
return camera;
}, [cameraKeyframe]);

return getCameraKeyframes;
}

export function usePrepareCameraFrame() {
const dispatch = useDispatch();
const prepareFrame = useCallback(scene => {
dispatch(updateViewState(scene.keyframes.camera.getFrame()));
});
return prepareFrame;
}

0 comments on commit 197920d

Please sign in to comment.