Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat]: video export with hubble.gl WIP #2482

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,6 @@
"umd"
],
"dependencies": {
"@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-alpha.1",
"@loaders.gl/polyfills": "^4.1.0-alpha.2",
"@types/mapbox__geo-viewport": "^0.4.1",
Expand Down
29 changes: 29 additions & 0 deletions src/actions/src/ui-state-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,35 @@ export const setExportImageSetting: (
(newSetting: SetExportImageSettingUpdaterAction['payload']) => ({payload: newSetting})
);

/** SET_EXPORT_VIDEO_SETTING */
export type SetExportVideoSettingUpdaterAction = {
payload: {
mediaType?: string;
cameraPreset?: string;
fileName?: string;
resolution?: string;
durationMs?: number;
};
};

/**
* Set `exportVideo` settings: mediaType, cameraPreset, fileName, resolution, durationMs
* @memberof uiStateActions
* @param newSetting - field(s) to change, e.g. {mediaType: 'gif'}
* @public
*/
export const setExportVideoSetting: (
newSetting: SetExportVideoSettingUpdaterAction['payload']
) => Merge<
SetExportVideoSettingUpdaterAction,
{type: typeof ActionTypes.SET_EXPORT_VIDEO_SETTING}
> = createAction(
ActionTypes.SET_EXPORT_VIDEO_SETTING,
(newSetting: SetExportVideoSettingUpdaterAction['payload']) => ({
payload: newSetting
})
);

/**
* Start exporting image flow
* @memberof uiStateActions
Expand Down
3 changes: 3 additions & 0 deletions src/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@
],
"dependencies": {
"@deck.gl/core": "^8.9.27",
"@deck.gl/mapbox": "^8.9.27",
"@deck.gl/react": "^8.9.27",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^6.0.1",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@floating-ui/react": "0.25.1",
"@hubble.gl/core": "1.3.7",
"@hubble.gl/react": "1.3.7",
"@kepler.gl/actions": "3.0.0-alpha.1",
"@kepler.gl/cloud-providers": "3.0.0-alpha.1",
"@kepler.gl/constants": "3.0.0-alpha.1",
Expand Down
7 changes: 6 additions & 1 deletion src/components/src/bottom-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import React, {forwardRef, useMemo, useCallback} from 'react';
import styled, {withTheme} from 'styled-components';

import {FILTER_VIEW_TYPES} from '@kepler.gl/constants';
import {FILTER_VIEW_TYPES, EXPORT_VIDEO_ID} from '@kepler.gl/constants';
import {hasPortableWidth, isSideFilter} from '@kepler.gl/utils';
import {media, breakPointValues} from '@kepler.gl/styles';
import {TimeRangeFilter} from '@kepler.gl/types';
Expand Down Expand Up @@ -88,6 +88,7 @@ export default function BottomWidgetFactory(
datasets,
filters,
animationConfig,
toggleModal,
visStateActions,
containerW,
uiState,
Expand Down Expand Up @@ -137,6 +138,8 @@ export default function BottomWidgetFactory(
// animation controller needs to call reset on it
const filter = (animatedFilter as TimeRangeFilter) || filters[enlargedFilterIdx];

const exportAnimation = useCallback(() => toggleModal(EXPORT_VIDEO_ID), [toggleModal]);

const onClose = useCallback(
() => visStateActions.setFilterView(enlargedFilterIdx, FILTER_VIEW_TYPES.side),
[visStateActions, enlargedFilterIdx]
Expand All @@ -159,6 +162,7 @@ export default function BottomWidgetFactory(
<LayerAnimationControl
updateAnimationSpeed={visStateActions.updateLayerAnimationSpeed}
toggleAnimation={visStateActions.toggleLayerAnimation}
exportAnimation={exportAnimation}
isAnimatable={!animatedFilter}
isAnimating={isAnimating}
resetAnimation={resetAnimation}
Expand Down Expand Up @@ -188,6 +192,7 @@ export default function BottomWidgetFactory(
setFilterAnimationTime={setTimelineValue}
setFilterAnimationWindow={visStateActions.setFilterAnimationWindow}
toggleAnimation={visStateActions.toggleFilterAnimation}
exportAnimation={exportAnimation}
updateAnimationSpeed={visStateActions.updateFilterAnimationSpeed}
resetAnimation={resetAnimation}
isAnimatable={!animationConfig || !animationConfig.isAnimating}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export type AnimationControlProps = {
updateAnimationSpeed?: (val: number) => void;
setAnimationWindow?: (id: string) => void;
toggleAnimation: () => void;
exportAnimation?: () => void;
resetAnimation?: () => void;
setTimelineValue: (value: number[]) => void;
showTimeDisplay?: boolean;
Expand Down Expand Up @@ -118,6 +119,7 @@ function AnimationControlFactory(
isAnimating,
resetAnimation,
toggleAnimation,
exportAnimation,
updateAnimationSpeed,
setTimelineValue,
setAnimationWindow,
Expand Down Expand Up @@ -153,6 +155,7 @@ function AnimationControlFactory(
isAnimating={isAnimating}
pauseAnimation={toggleAnimation}
resetAnimation={resetAnimation}
exportAnimation={exportAnimation}
speed={speed}
updateAnimationSpeed={updateAnimationSpeed}
setFilterAnimationWindow={setAnimationWindow}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {Tooltip} from '../styled-components';
import IconButton from '../icon-button';

const DELAY_SHOW = 500;

function ExportVideoControlFactory() {
const ExportVideoControl = ({
showAnimationWindowControl,
btnStyle,
buttonHeight,
playbackIcons,
exportAnimation
}) => {
return showAnimationWindowControl || !exportAnimation ? null : (
<IconButton
data-tip
data-for="animate-export"
className="playback-control-button"
onClick={exportAnimation}
{...btnStyle}
>
<playbackIcons.export height={buttonHeight} />
<Tooltip id="animate-export" place="top" delayShow={DELAY_SHOW} effect="solid">
<FormattedMessage id="tooltip.export" />
</Tooltip>
</IconButton>
);
};

return ExportVideoControl;
}

export default ExportVideoControlFactory;
16 changes: 12 additions & 4 deletions src/components/src/common/animation-control/playback-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import AnimationWindowControlFactory, {AnimationItem} from './animation-window-c
import ResetControlFactory from './reset-control';
import PlayControlFactory from './play-control';
import SpeedControlFactory from './speed-control';
import ExportVideoControlFactory from './export-video-control';

const DEFAULT_BUTTON_HEIGHT = '20px';

Expand Down Expand Up @@ -83,6 +84,7 @@ interface PlaybackControlsProps {
pauseAnimation?: () => void;
resetAnimation?: () => void;
startAnimation: () => void;
exportAnimation?: () => void;
playbackIcons?: typeof DEFAULT_ICONS;
animationItems?: {[key: string]: AnimationItem};
buttonStyle?: string;
Expand All @@ -97,22 +99,25 @@ PlaybackControlsFactory.deps = [
WindowActionControlFactory,
AnimationWindowControlFactory,
ResetControlFactory,
PlayControlFactory
PlayControlFactory,
ExportVideoControlFactory
];

function PlaybackControlsFactory(
AnimationSpeedSlider: ReturnType<typeof AnimationSpeedSliderFactory>,
WindowActionControl,
AnimationWindowControl,
ResetControl,
PlayControl
PlayControl,
ExportVideoControl
) {
const PLAYBACK_CONTROLS_DEFAULT_ACTION_COMPONENTS = [
PlayControl,
SpeedControlFactory(AnimationSpeedSlider),
ResetControl,
WindowActionControl,
AnimationWindowControl
AnimationWindowControl,
ExportVideoControl
];

// eslint-disable-next-line complexity
Expand All @@ -127,6 +132,7 @@ function PlaybackControlsFactory(
pauseAnimation,
resetAnimation,
startAnimation,
exportAnimation,
playbackIcons,
animationItems,
buttonStyle,
Expand Down Expand Up @@ -175,6 +181,7 @@ function PlaybackControlsFactory(
pauseAnimation={pauseAnimation}
resetAnimation={resetAnimation}
startAnimation={startAnimation}
exportAnimation={exportAnimation}
playbackIcons={playbackIcons}
isSpeedControlVisible={isSpeedControlVisible}
speed={speed}
Expand All @@ -194,7 +201,8 @@ function PlaybackControlsFactory(
isAnimatable: true,
pauseAnimation: nop,
resetAnimation: nop,
startAnimation: nop
startAnimation: nop,
exportAnimation: nop
};

return PlaybackControls;
Expand Down
10 changes: 8 additions & 2 deletions src/components/src/common/time-range-slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {HistogramBin, LineChart, Timeline} from '@kepler.gl/types';
import AnimationControlFactory from './animation-control/animation-control';

const animationControlWidth = 176;
const animationControlExportWidth = 19;

interface StyledSliderContainerProps {
isEnlarged?: boolean;
Expand All @@ -53,6 +54,7 @@ type TimeRangeSliderProps = {
animationWindow: string;
resetAnimation?: () => void;
toggleAnimation: () => void;
exportAnimation?: () => void;
updateAnimationSpeed?: (val: number) => void;
setFilterAnimationWindow?: (id: string) => void;
onChange: (v: number[]) => void;
Expand Down Expand Up @@ -113,15 +115,17 @@ export default function TimeRangeSliderFactory(
updateAnimationSpeed,
setFilterAnimationWindow,
toggleAnimation,
exportAnimation,
onChange,
timeline
} = props;

const throttledOnchange = useMemo(() => throttle(onChange, 20), [onChange]);
const width = animationControlWidth + (exportAnimation ? animationControlExportWidth : 0);

const style = useMemo(
() => ({
width: isEnlarged ? `calc(100% - ${animationControlWidth}px)` : '100%'
width: isEnlarged ? `calc(100% - ${width}px)` : '100%'
}),
[isEnlarged]
);
Expand Down Expand Up @@ -167,20 +171,22 @@ export default function TimeRangeSliderFactory(
updateAnimationSpeed={updateAnimationSpeed}
setTimelineValue={throttledOnchange}
setAnimationWindow={setFilterAnimationWindow}
exportAnimation={exportAnimation}
showTimeDisplay={false}
timeline={timeline}
/>
)}
{isEnlarged && !isMinified ? (
<PlaybackControls
isAnimatable={isAnimatable}
width={animationControlWidth}
width={width}
speed={speed}
animationWindow={animationWindow}
updateAnimationSpeed={updateAnimationSpeed}
setFilterAnimationWindow={setFilterAnimationWindow}
pauseAnimation={toggleAnimation}
resetAnimation={resetAnimation}
exportAnimation={exportAnimation}
isAnimating={isAnimating}
startAnimation={toggleAnimation}
/>
Expand Down
2 changes: 2 additions & 0 deletions src/components/src/filters/time-range-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ function TimeRangeFilterFactory(TimeRangeSlider: ReturnType<typeof TimeRangeSlid
setFilter,
isAnimatable,
toggleAnimation,
exportAnimation,
hideTimeTitle,
timeline
}) => (
<TimeRangeSlider
{...timeRangeSliderFieldsSelector(filter)}
onChange={setFilter}
toggleAnimation={toggleAnimation}
exportAnimation={exportAnimation}
isAnimatable={isAnimatable}
hideTimeTitle={hideTimeTitle}
timeline={timeline}
Expand Down
6 changes: 6 additions & 0 deletions src/components/src/filters/time-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ function TimeWidgetFactory(
setFilterAnimationTime,
onClose,
resetAnimation,
exportAnimation,
isAnimatable,
updateAnimationSpeed,
toggleAnimation,
Expand All @@ -62,6 +63,10 @@ function TimeWidgetFactory(
]);

const _toggleAnimation = useCallback(() => toggleAnimation(index), [toggleAnimation, index]);
const _exportAnimation = useCallback(() => exportAnimation(/* index */), [
exportAnimation,
index
]);

const _onToggleMinify = useCallback(() => setMinified(!isMinified), [setMinified, isMinified]);

Expand Down Expand Up @@ -93,6 +98,7 @@ function TimeWidgetFactory(
{...timeRangeSliderFieldsSelector(filter)}
onChange={timeSliderOnChange}
toggleAnimation={_toggleAnimation}
exportAnimation={_exportAnimation}
updateAnimationSpeed={_updateAnimationSpeed}
setFilterAnimationWindow={_setFilterAnimationWindow}
hideTimeTitle={showTimeDisplay}
Expand Down
2 changes: 2 additions & 0 deletions src/components/src/filters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type TimeRangeFilterProps = {
hideTimeTitle: boolean;
setFilter: (v: number[]) => void;
toggleAnimation: () => void;
exportAnimation?: () => void;
timeline: Timeline;
};

Expand Down Expand Up @@ -72,6 +73,7 @@ export type TimeWidgetProps = {
showTimeDisplay: boolean;
isAnimatable: boolean;
resetAnimation: () => void;
exportAnimation: () => void;
onClose: () => void;
setFilterAnimationTime: ActionHandler<typeof setFilterAnimationTime>;
updateAnimationSpeed: ActionHandler<typeof updateFilterAnimationSpeed>;
Expand Down
1 change: 1 addition & 0 deletions src/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export {default as WindowActionControlFactory} from './common/animation-control/
export {default as PlayControlFactory} from './common/animation-control/play-control';
export {default as ResetControlFactory} from './common/animation-control/reset-control';
export {default as SpeedControlFactory} from './common/animation-control/speed-control';
export {default as ExportVideoControlFactory} from './common/animation-control/export-video-control';
export {default as AnimationWindowControlFactory} from './common/animation-control/animation-window-control';
export {default as FloatingTimeDisplayFactory} from './common/animation-control/floating-time-display';
export {default as AnimationSpeedSliderFactory} from './common/animation-control/animation-speed-slider';
Expand Down
Loading
Loading