diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/profiler/time-travel/time-travel.component.css b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/profiler/time-travel/time-travel.component.css index 223e7d3f3b4..cb874a0fd65 100644 --- a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/profiler/time-travel/time-travel.component.css +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/profiler/time-travel/time-travel.component.css @@ -6,20 +6,35 @@ } .controls { - position: absolute; - top: 0px; - left: 340px + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + margin: auto; + width: 85%; } -/deep/ .vis-network { - outline: none; +.controls .control-buttons { + width: 35%; +} + +.controls .control-buttons button { + } .timestamp { font-weight: 300; - position: absolute; - top: 25px; - left: 340px; +} + +.controls .status { + justify-content: space-between; + display: flex; + align-items: center; + width: 45%; +} + +/deep/ .vis-network { + outline: none; } .side-panel { diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/profiler/time-travel/time-travel.component.html b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/profiler/time-travel/time-travel.component.html index e1f3bf80887..e43ac1318be 100644 --- a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/profiler/time-travel/time-travel.component.html +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/profiler/time-travel/time-travel.component.html @@ -1,11 +1,38 @@
- Timeline:  - - +
+ Timeline:  + +

{{ currentFrame.timestamp / 1000 | number }} seconds from the start

+
+
+ + + + + +
- -

{{ currentFrame.timestamp / 1000 | number }} seconds from the start

-
; + isPlaying = false; + playLoopSubscription: Subscription; + constructor(private _visNetworkService: VisNetworkService) {} @Input() set stream(stream: ComponentRecord[]) { @@ -61,13 +65,13 @@ export class TimeTravelComponent implements OnDestroy { this._showFrame(this.timeline[0]); } - showSeparator() { - return Object.keys(this.selectedEntry.instanceState.props).length > 0 && this.selectedEntry.duration; + showSeparator(): boolean { + return Object.keys(this.selectedEntry.instanceState.props).length > 0 && !!this.selectedEntry.duration; } - move(direction: number) { + move(direction: number): void { const idx = this.slider.value + direction; - if (idx >= this.timeline.length || idx < 0) { + if (this._invalidSliderIndex(idx)) { return; } this.slider.value = idx; @@ -75,13 +79,44 @@ export class TimeTravelComponent implements OnDestroy { this._visNetworkService.setData(this.visNetwork, this.visNetworkData); } - update() { + createPlayLoopObservable(): Observable<[number, number]> { + return zip( + range(0, this.timeline.length - this.slider.value), + interval(300), + ); + } + + play(): void { + this.isPlaying = true; + this._clearPlayLoopSubscription(); + this._subscribeToPlayLoop(); + } + + pause(): void { + this.isPlaying = false; + this._clearPlayLoopSubscription(); + } + + moveToBeginningOfTimeline(): void { + this.slider.value = 0; + this._showFrame(this.timeline[0]); + this._visNetworkService.setData(this.visNetwork, this.visNetworkData); + } + + moveToEndOfTimeline(): void { + const lastIndex = this.timeline.length - 1; + this.slider.value = lastIndex; + this._showFrame(this.timeline[lastIndex]); + this._visNetworkService.setData(this.visNetwork, this.visNetworkData); + } + + update(): void { const idx = this.slider.value; this._showFrame(this.timeline[idx]); this._visNetworkService.setData(this.visNetwork, this.visNetworkData); } - onInitialize() { + onInitialize(): void { this._visNetworkService.on(this.visNetwork, 'click'); this._visNetworkService.click.subscribe((eventData: any[]) => { if (eventData[0] === this.visNetwork) { @@ -90,53 +125,87 @@ export class TimeTravelComponent implements OnDestroy { }); } - ngOnDestroy() { + ngOnDestroy(): void { + this._clearPlayLoopSubscription(); this._visNetworkService.off(this.visNetwork, 'click'); } - private _showFrame(frame: TimelineFrame | undefined) { + private _showFrame(frame: TimelineFrame | undefined): void { if (!frame) { return; } + this._prepareNodesAndEdgesForNewFrame(frame); + this._initNodesAndEdges(frame.roots); + this._updateVisNetworkDataState(); + } + private _prepareNodesAndEdgesForNewFrame(frame: TimelineFrame): void { this.currentFrame = frame; + this._resetNodesAndEdges(); + this.selectedEntry = null; + } + private _resetNodesAndEdges(): void { this.nodes = new DataSet([]); this.edges = new DataSet([]); this._nodeIdToNodes = new Map(); + } - const initNodes = (roots: TimelineNode[], parentId?: string) => { - roots.forEach(node => { - if (!node) { - return; - } + private _initNodesAndEdges(roots: TimelineNode[], parentId?: string): void { + roots.forEach(node => { + if (!node) { + return; + } - const id = uuid(); + const id = uuid(); - this._nodeIdToNodes.set(id, node); + this._nodeIdToNodes.set(id, node); - this.nodes.add({ - id, - color: node.state === TimelineNodeState.Check ? '#62D7C5' : '#5727E5', - label: node.name, - font: { - color: node.state === TimelineNodeState.Check ? '#000000' : '#ffffff', - }, - }); - - if (parentId) { - this.edges.add({ - from: parentId, - to: id - }); - } - - initNodes(node.children, id); + this.nodes.add({ + id, + color: node.state === TimelineNodeState.Check ? '#62D7C5' : '#5727E5', + label: node.name, + font: { + color: node.state === TimelineNodeState.Check ? '#000000' : '#ffffff', + }, }); - }; - initNodes(frame.roots); - this.selectedEntry = null; + if (parentId) { + this.edges.add({ + from: parentId, + to: id + }); + } + + this._initNodesAndEdges(node.children, id); + }); + } + + private _updateVisNetworkDataState(): void { this.visNetworkData = { nodes: this.nodes, edges: this.edges }; } + + private _invalidSliderIndex(index: number): boolean { + return index >= this.timeline.length || index < 0; + } + + private _clearPlayLoopSubscription(): void { + if (this.playLoopSubscription) { + this.playLoopSubscription.unsubscribe(); + } + } + + private _subscribeToPlayLoop(): void { + this.playLoopSubscription = this.createPlayLoopObservable().subscribe(snapshot => { + if (this._reachedEndOfTimeLine()) { + this.pause(); + } else { + this.move(1); + } + }); + } + + private _reachedEndOfTimeLine(): boolean { + return (this.timeline.length - 1) - this.slider.value === 0; + } }