From 7a162723a6d351e1c9a203d3dcbac5c5b0c6b06e Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 24 Jun 2021 13:58:51 -0400 Subject: [PATCH] [Maps] timeslider play button (#103147) (#103313) * [Maps] timeslider play button * cancel subscription on unmount * change playback speed to 1750 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Nathan Reese --- .../map_container/map_container.tsx | 4 +- .../timeslider/timeslider.tsx | 60 +++++++++++++++++++ .../maps/public/embeddable/map_embeddable.tsx | 2 + .../routes/map_page/map_app/map_app.tsx | 1 + .../map_app/wait_until_time_layers_load.ts | 7 ++- 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index 788094ee1ab5c7..0bdf462cca4b3a 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -13,6 +13,7 @@ import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; import { Filter } from 'src/plugins/data/public'; import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public'; +import { Observable } from 'rxjs'; import { MBMap } from '../mb_map'; import { RightSideControls } from '../right_side_controls'; import { Timeslider } from '../timeslider'; @@ -47,6 +48,7 @@ export interface Props { description?: string; settings: MapSettings; layerList: ILayer[]; + waitUntilTimeLayersLoad$: Observable; } interface State { @@ -223,7 +225,7 @@ export class MapContainer extends Component { - + void; isTimesliderOpen: boolean; timeRange: TimeRange; + waitForTimesliceToLoad$: Observable; } interface State { + isPaused: boolean; max: number; min: number; range: number; @@ -44,6 +48,8 @@ export function Timeslider(props: Props) { class KeyedTimeslider extends Component { private _isMounted: boolean = false; + private _timeoutId: number | undefined; + private _subscription: Subscription | undefined; constructor(props: Props) { super(props); @@ -59,6 +65,7 @@ class KeyedTimeslider extends Component { const timeslice: [number, number] = [min, max]; this.state = { + isPaused: true, max, min, range: interval, @@ -68,6 +75,7 @@ class KeyedTimeslider extends Component { } componentWillUnmount() { + this._onPause(); this._isMounted = false; } @@ -118,6 +126,44 @@ class KeyedTimeslider extends Component { } }, 300); + _onPlay = () => { + this.setState({ isPaused: false }); + this._playNextFrame(); + }; + + _onPause = () => { + this.setState({ isPaused: true }); + if (this._subscription) { + this._subscription.unsubscribe(); + this._subscription = undefined; + } + if (this._timeoutId) { + clearTimeout(this._timeoutId); + this._timeoutId = undefined; + } + }; + + _playNextFrame() { + // advance to next frame + this._onNext(); + + // use waitForTimesliceToLoad$ observable to wait until next frame loaded + // .pipe(first()) waits until the first value is emitted from an observable and then automatically unsubscribes + this._subscription = this.props.waitForTimesliceToLoad$.pipe(first()).subscribe(() => { + if (this.state.isPaused) { + return; + } + + // use timeout to display frame for small time period before moving to next frame + this._timeoutId = window.setTimeout(() => { + if (this.state.isPaused) { + return; + } + this._playNextFrame(); + }, 1750); + }); + } + render() { return (
@@ -154,6 +200,20 @@ class KeyedTimeslider extends Component { defaultMessage: 'Next time window', })} /> +
diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 509cece671dd6d..4670285aa9eff8 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -73,6 +73,7 @@ import { SavedMap } from '../routes/map_page'; import { getIndexPatternsFromIds } from '../index_pattern_util'; import { getMapAttributeService } from '../map_attribute_service'; import { isUrlDrilldown, toValueClickDataFormat } from '../trigger_actions/trigger_utils'; +import { waitUntilTimeLayersLoad$ } from '../routes/map_page/map_app/wait_until_time_layers_load'; import { MapByValueInput, @@ -345,6 +346,7 @@ export class MapEmbeddable renderTooltipContent={this._renderTooltipContent} title={this.getTitle()} description={this.getDescription()} + waitUntilTimeLayersLoad$={waitUntilTimeLayersLoad$(this._savedMap.getStore())} /> , diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx index 92459ed28ab91e..5231aab5d11948 100644 --- a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx @@ -455,6 +455,7 @@ export class MapApp extends React.Component { addFilters={this._addFilter} title={this.props.savedMap.getAttributes().title} description={this.props.savedMap.getAttributes().description} + waitUntilTimeLayersLoad$={waitUntilTimeLayersLoad$(this.props.savedMap.getStore())} /> diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/wait_until_time_layers_load.ts b/x-pack/plugins/maps/public/routes/map_page/map_app/wait_until_time_layers_load.ts index 7e08e49863fdf5..1258539456c630 100644 --- a/x-pack/plugins/maps/public/routes/map_page/map_app/wait_until_time_layers_load.ts +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/wait_until_time_layers_load.ts @@ -6,7 +6,7 @@ */ import { from } from 'rxjs'; -import { debounceTime, first, switchMap } from 'rxjs/operators'; +import { debounceTime, first, map, switchMap } from 'rxjs/operators'; import { getLayerList } from '../../../selectors/map_selectors'; import { MapStore } from '../../../reducers/store'; @@ -31,6 +31,11 @@ export function waitUntilTimeLayersLoad$(store: MapStore) { .filter(({ isFilteredByGlobalTime }) => isFilteredByGlobalTime) .some(({ layer }) => layer.isLayerLoading()); return !areTimeLayersStillLoading; + }), + map(() => { + // Observable notifies subscriber when loading is finished + // Return void to not expose internal implemenation details of observabale + return; }) ); }