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;
+ }
}