diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.html b/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.html
index 8e6a94fd6b4..bac99b97c05 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.html
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.html
@@ -15,11 +15,8 @@
-
-
-
-
-
+
+
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.ts b/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.ts
index 332310a2097..4127a02db79 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.ts
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.ts
@@ -1,9 +1,9 @@
-import { Component, ViewChildren, QueryList, OnInit, OnDestroy } from '@angular/core';
-import { RecordingModalComponent } from './recording/recording-modal/recording-modal.component';
+import { Component, OnInit, OnDestroy } from '@angular/core';
import { MessageBus, Events, ProfilerFrame } from 'protocol';
import { FileApiService } from '../../file-api-service';
import { MatDialog } from '@angular/material/dialog';
import { ProfilerImportDialogComponent } from './profiler-import-dialog/profiler-import-dialog.component';
+import { Subject } from 'rxjs';
type State = 'idle' | 'recording' | 'visualizing';
@@ -17,10 +17,10 @@ const PROFILER_VERSION = 1;
})
export class ProfilerComponent implements OnInit, OnDestroy {
state: State = 'idle';
- stream: ProfilerFrame[] = [];
- buffer: ProfilerFrame[] = [];
+ stream = new Subject();
- @ViewChildren(RecordingModalComponent) recordingRef: QueryList;
+ // We collect this buffer so we can have it available for export.
+ private _buffer: ProfilerFrame[] = [];
constructor(
private _fileApiService: FileApiService,
@@ -30,23 +30,26 @@ export class ProfilerComponent implements OnInit, OnDestroy {
startRecording(): void {
this.state = 'recording';
- this.recordingRef.forEach((r) => r.start());
this._messageBus.emit('startProfiling');
}
stopRecording(): void {
- this.state = 'idle';
- this.recordingRef.forEach((r) => r.stop());
+ this.state = 'visualizing';
this._messageBus.emit('stopProfiling');
+ this.stream.complete();
}
ngOnInit(): void {
this._messageBus.on('profilerResults', (remainingRecords) => {
- this._profilerFinished(remainingRecords);
+ if (remainingRecords.duration > 0 && remainingRecords.source) {
+ this.stream.next([remainingRecords]);
+ this._buffer.push(remainingRecords);
+ }
});
this._messageBus.on('sendProfilerChunk', (chunkOfRecords: ProfilerFrame) => {
- this.buffer.push(chunkOfRecords);
+ this.stream.next([chunkOfRecords]);
+ this._buffer.push(chunkOfRecords);
});
this._fileApiService.uploadedData.subscribe((importedFile) => {
@@ -69,34 +72,27 @@ export class ProfilerComponent implements OnInit, OnDestroy {
processDataDialog.afterClosed().subscribe((result) => {
if (result) {
- this._viewProfilerData(importedFile.stream);
+ this.state = 'visualizing';
+ this._buffer = importedFile.buffer;
+ setTimeout(() => this.stream.next(importedFile.buffer));
}
});
} else {
- this._viewProfilerData(importedFile.stream);
+ this.state = 'visualizing';
+ this._buffer = importedFile.buffer;
+ setTimeout(() => this.stream.next(importedFile.buffer));
}
});
}
- private _profilerFinished(remainingRecords: ProfilerFrame): void {
- const flattenedBuffer = [].concat.apply([], this.buffer);
- this._viewProfilerData([...flattenedBuffer, remainingRecords]);
- this.buffer = [];
- }
-
ngOnDestroy(): void {
this._fileApiService.uploadedData.unsubscribe();
}
- private _viewProfilerData(stream: ProfilerFrame[]): void {
- this.state = 'visualizing';
- this.stream = stream;
- }
-
exportProfilerResults(): void {
const fileToExport = {
version: PROFILER_VERSION,
- stream: this.stream,
+ buffer: this._buffer,
};
this._fileApiService.saveObjectAsJSON(fileToExport);
}
@@ -106,7 +102,8 @@ export class ProfilerComponent implements OnInit, OnDestroy {
}
discardRecording(): void {
- this.stream = [];
+ this.stream = new Subject();
this.state = 'idle';
+ this._buffer = [];
}
}
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.module.ts b/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.module.ts
index 2042b334997..277648d71b9 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.module.ts
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.module.ts
@@ -1,28 +1,17 @@
import { NgModule } from '@angular/core';
-import { MatProgressBarModule } from '@angular/material/progress-bar';
import { CommonModule } from '@angular/common';
import { MatSelectModule } from '@angular/material/select';
import { MatDialogModule } from '@angular/material/dialog';
import { FormsModule } from '@angular/forms';
import { ProfilerComponent } from './profiler.component';
-import { RecordingModalComponent } from './recording/recording-modal/recording-modal.component';
import { TimelineModule } from './recording/timeline/timeline.module';
-import { RecordingDialogComponent } from './recording/recording-modal/recording-dialog/recording-dialog.component';
import { MatButtonModule } from '@angular/material/button';
import { ProfilerImportDialogComponent } from './profiler-import-dialog/profiler-import-dialog.component';
@NgModule({
- declarations: [ProfilerComponent, RecordingModalComponent, RecordingDialogComponent, ProfilerImportDialogComponent],
- imports: [
- CommonModule,
- MatDialogModule,
- MatSelectModule,
- FormsModule,
- MatProgressBarModule,
- TimelineModule,
- MatButtonModule,
- ],
+ declarations: [ProfilerComponent, ProfilerImportDialogComponent],
+ imports: [CommonModule, MatDialogModule, MatSelectModule, FormsModule, TimelineModule, MatButtonModule],
exports: [ProfilerComponent],
entryComponents: [ProfilerImportDialogComponent],
})
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.html b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.html
index dff65cf72dc..7634562cada 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.html
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.html
@@ -1,18 +1,19 @@
-
{{ currentFrameIndex + 1 }}/{{ graphData.length }}
-
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.scss b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.scss
index 7d858b6c01c..a891e62aa7e 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.scss
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.scss
@@ -19,9 +19,14 @@
.bar-container {
max-width: calc(100vw - 150px);
- display: flex;
align-items: baseline;
overflow-x: auto;
+ width: 100%;
+ height: 100%;
+
+ ::ng-deep .cdk-virtual-scroll-content-wrapper {
+ display: flex;
+ }
&::-webkit-scrollbar {
display: none;
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.ts b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.ts
index c714c686f47..6e134561644 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.ts
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/frame-selector/frame-selector.component.ts
@@ -1,25 +1,67 @@
-import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
+import { Component, ElementRef, EventEmitter, Input, Output, ViewChild, OnDestroy } from '@angular/core';
import { GraphNode } from '../record-formatter/record-formatter';
+import { Observable, Subscription } from 'rxjs';
+import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
+
+const ITEM_WIDTH = 29;
@Component({
selector: 'ng-frame-selector',
templateUrl: './frame-selector.component.html',
styleUrls: ['./frame-selector.component.scss'],
})
-export class FrameSelectorComponent {
+export class FrameSelectorComponent implements OnDestroy {
@ViewChild('barContainer') barContainer: ElementRef;
@Input() set currentFrame(value: number) {
this.currentFrameIndex = value;
- this.barContainer?.nativeElement?.children?.[value]?.scrollIntoView({
- behavior: 'auto',
- block: 'end',
- inline: 'nearest',
- });
+ this._ensureVisible(value);
}
- @Input() graphData: GraphNode[];
- @Input() profilerFramesLength: number;
+ @Input() set graphData$(graphData: Observable) {
+ this._graphData$ = graphData;
+ this._graphDataSubscription = this._graphData$.subscribe((items) =>
+ setTimeout(() => {
+ this.viewPort.scrollToIndex(items.length);
+ })
+ );
+ }
+
+ get graphData$(): Observable {
+ return this._graphData$;
+ }
+
@Output() move = new EventEmitter();
@Output() selectFrame = new EventEmitter();
+ @ViewChild(CdkVirtualScrollViewport) viewPort: CdkVirtualScrollViewport;
currentFrameIndex: number;
+
+ get itemWidth(): number {
+ return ITEM_WIDTH;
+ }
+
+ private _graphData$: Observable;
+ private _graphDataSubscription: Subscription;
+
+ ngOnDestroy(): void {
+ if (this._graphDataSubscription) {
+ this._graphDataSubscription.unsubscribe();
+ }
+ }
+
+ private _ensureVisible(index: number): void {
+ if (!this.viewPort) {
+ return;
+ }
+ const scrollParent = this.viewPort.elementRef.nativeElement;
+ // The left most point we see an element
+ const left = scrollParent.scrollLeft;
+ // That's the right most point we currently see an element.
+ const right = left + scrollParent.offsetWidth;
+ const itemLeft = index * this.itemWidth;
+ if (itemLeft < left) {
+ scrollParent.scrollTo({ left: itemLeft });
+ } else if (right < itemLeft + this.itemWidth) {
+ scrollParent.scrollTo({ left: itemLeft - scrollParent.offsetWidth + this.itemWidth });
+ }
+ }
}
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-dialog/recording-dialog.component.html b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-dialog/recording-dialog.component.html
similarity index 62%
rename from projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-dialog/recording-dialog.component.html
rename to projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-dialog/recording-dialog.component.html
index 98d26a8f56e..71b83fbc584 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-dialog/recording-dialog.component.html
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-dialog/recording-dialog.component.html
@@ -1,5 +1,5 @@
- Recording
+ Interact with your app to preview change detection
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-dialog/recording-dialog.component.scss b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-dialog/recording-dialog.component.scss
similarity index 100%
rename from projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-dialog/recording-dialog.component.scss
rename to projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-dialog/recording-dialog.component.scss
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-dialog/recording-dialog.component.ts b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-dialog/recording-dialog.component.ts
similarity index 100%
rename from projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-dialog/recording-dialog.component.ts
rename to projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-dialog/recording-dialog.component.ts
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-modal.component.html b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-modal.component.html
similarity index 54%
rename from projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-modal.component.html
rename to projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-modal.component.html
index d95cbc94059..a16311c881c 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-modal.component.html
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-modal.component.html
@@ -1,3 +1,3 @@
-
+
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-modal.component.scss b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-modal.component.scss
similarity index 75%
rename from projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-modal.component.scss
rename to projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-modal.component.scss
index c19bcc65235..f42bc8573d5 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-modal.component.scss
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-modal.component.scss
@@ -1,4 +1,4 @@
-@import '../../../../../../../../node_modules/@angular/cdk/overlay-prebuilt.css';
+@import '../../../../../../../../../node_modules/@angular/cdk/overlay-prebuilt.css';
:host {
overflow: hidden;
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-modal.component.ts b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-modal.component.ts
similarity index 55%
rename from projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-modal.component.ts
rename to projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-modal.component.ts
index c1ba2a832e7..285f10a261f 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/recording-modal/recording-modal.component.ts
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/recording-modal/recording-modal.component.ts
@@ -5,14 +5,4 @@ import { Component } from '@angular/core';
templateUrl: './recording-modal.component.html',
styleUrls: ['./recording-modal.component.scss'],
})
-export class RecordingModalComponent {
- visible = false;
-
- stop(): void {
- this.visible = false;
- }
-
- start(): void {
- this.visible = true;
- }
-}
+export class RecordingModalComponent {}
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.html b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.html
index ac7c47d8c42..3a4e1f62382 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.html
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.html
@@ -1,6 +1,6 @@
- Export to JSON
-
+ Export to JSON
+
Flame graph
@@ -14,12 +14,14 @@
-
+
-
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.scss b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.scss
index 77cd3c98daa..4779ca773a0 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.scss
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.scss
@@ -1,3 +1,8 @@
+:host {
+ height: 60px;
+ display: block;
+}
+
.controls {
top: 0;
left: 300px;
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.ts b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.ts
index f9a7f2d6a3a..171e9750e6f 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.ts
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline-controls/timeline-controls.component.ts
@@ -8,10 +8,11 @@ import { ProfilerFrame } from 'protocol';
styleUrls: ['./timeline-controls.component.scss'],
})
export class TimelineControlsComponent {
- @Input() record: ProfilerFrame;
+ @Input() record: ProfilerFrame | undefined;
@Input() estimatedFrameRate: number;
@Input() frameColor: string;
@Input() visualizationMode: VisualizationMode;
+ @Input() empty: boolean;
@Output() changeVisualizationMode = new EventEmitter();
@Output() exportProfile = new EventEmitter();
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.html b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.html
index ea9bee87825..d50666695e4 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.html
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.html
@@ -1,26 +1,34 @@
- 0; else noRecords">
-
-
+
-
- Nothing was profiled
-
+
+ There's no information to show.
+
-
-
-
-
-
+
+
+
+
+
+ Select a bar to preview a particular change detection cycle.
+
+
+= 0"
+ [visualizationMode]="visualizationMode"
+ [frame]="frame"
+>
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.scss b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.scss
index d3bf526af5d..f32c655a37d 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.scss
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.scss
@@ -4,3 +4,16 @@
height: 100%;
display: block;
}
+
+.info {
+ font-size: 1.2em;
+ text-align: center;
+}
+
+.hidden {
+ /*
+ intentionally using visibility: hidden
+ display: none breaks the virtual scroll
+ */
+ visibility: hidden;
+}
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.ts b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.ts
index c19344c2296..c1fc43950dd 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.ts
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.component.ts
@@ -1,6 +1,8 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
+import { Component, EventEmitter, Input, Output, OnDestroy } from '@angular/core';
import { ProfilerFrame } from 'protocol';
import { GraphNode } from './record-formatter/record-formatter';
+import { Observable, Subscription, BehaviorSubject } from 'rxjs';
+import { share } from 'rxjs/operators';
export enum VisualizationMode {
FlameGraph,
@@ -14,22 +16,41 @@ const MAX_HEIGHT = 50;
selector: 'ng-recording-timeline',
templateUrl: './timeline.component.html',
styleUrls: ['./timeline.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class TimelineComponent {
- @Input() set records(data: ProfilerFrame[]) {
- this.profilerFrames = data.filter((frame) => frame.duration > 0);
- this.renderBarChart(this.profilerFrames);
+export class TimelineComponent implements OnDestroy {
+ @Input() set stream(data: Observable) {
+ if (this._subscription) {
+ this._subscription.unsubscribe();
+ }
+ this._allRecords = [];
+ this._maxDuration = -Infinity;
+ this._subscription = data.subscribe({
+ next: (frames: ProfilerFrame[]): void => {
+ this._processFrames(frames);
+ },
+ complete: (): void => {
+ this.visualizing = true;
+ },
+ });
}
- @Input() profilerFrames: ProfilerFrame[] = [];
@Output() exportProfile = new EventEmitter();
visualizationMode = VisualizationMode.BarGraph;
- graphData: GraphNode[] = [];
- currentFrameIndex = 0;
+ currentFrameIndex = -1;
+
+ private _maxDuration = -Infinity;
+ private _subscription: Subscription;
+ private _allRecords: ProfilerFrame[] = [];
+ private _graphDataSubject = new BehaviorSubject([]);
+ visualizing = false;
+ graphData$ = this._graphDataSubject.asObservable().pipe(share());
+
+ get hasFrames(): boolean {
+ return this._allRecords.length > 0;
+ }
get frame(): ProfilerFrame {
- return this.profilerFrames[this.currentFrameIndex];
+ return this._allRecords[this.currentFrameIndex];
}
estimateFrameRate(timeSpent: number): number {
@@ -39,7 +60,7 @@ export class TimelineComponent {
move(value: number): void {
const newVal = this.currentFrameIndex + value;
- if (newVal > -1 && newVal < this.profilerFrames.length) {
+ if (newVal > -1 && newVal < this._allRecords.length) {
this.currentFrameIndex = newVal;
}
}
@@ -59,26 +80,59 @@ export class TimelineComponent {
return 'red';
}
- renderBarChart(records: ProfilerFrame[]): void {
- const maxValue = records.reduce((acc: number, frame: ProfilerFrame) => Math.max(acc, frame.duration), 0);
- const multiplicationFactor = parseFloat((MAX_HEIGHT / maxValue).toFixed(2));
- this.graphData = records.map((r) => {
- const height = r.duration * multiplicationFactor;
- const colorPercentage = Math.round((height / MAX_HEIGHT) * 100);
- const backgroundColor = this.getColorByFrameRate(this.estimateFrameRate(r.duration));
+ ngOnDestroy(): void {
+ if (this._subscription) {
+ this._subscription.unsubscribe();
+ }
+ }
- const style = {
- 'margin-left': '1px',
- 'margin-right': '1px',
- background: `-webkit-linear-gradient(bottom, ${backgroundColor} ${colorPercentage}%, #f3f3f3 ${colorPercentage}%)`,
- border: '1px solid #d0d0d0',
- cursor: 'pointer',
- 'min-width': '25px',
- width: '25px',
- height: '50px',
- };
- const toolTip = `${r.source} TimeSpent: ${r.duration.toFixed(3)}ms`;
- return { style, toolTip };
- });
+ private _processFrames(frames: ProfilerFrame[]): void {
+ let regenerate = false;
+ for (const frame of frames) {
+ if (frame.duration >= this._maxDuration) {
+ regenerate = true;
+ }
+ this._allRecords.push(frame);
+ }
+ if (regenerate) {
+ this._graphDataSubject.next(this._generateBars());
+ return;
+ }
+ const multiplicationFactor = parseFloat((MAX_HEIGHT / this._maxDuration).toFixed(2));
+ frames.forEach((frame) => this._graphDataSubject.value.push(this._getBarStyles(frame, multiplicationFactor)));
+
+ // We need to pass a new reference, because the CDK virtual scroll
+ // has OnPush strategy, so it doesn't update the UI otherwise.
+ // If this turns out ot be a bottleneck, we can easily create an immutable reference.
+ this._graphDataSubject.next(this._graphDataSubject.value.slice());
+ }
+
+ private _generateBars(): GraphNode[] {
+ const maxValue = this._allRecords.reduce((acc: number, frame: ProfilerFrame) => Math.max(acc, frame.duration), 0);
+ const multiplicationFactor = parseFloat((MAX_HEIGHT / maxValue).toFixed(2));
+ this._maxDuration = Math.max(this._maxDuration, maxValue);
+ return this._allRecords.map((r) => this._getBarStyles(r, multiplicationFactor));
+ }
+
+ private _getBarStyles(
+ record: ProfilerFrame,
+ multiplicationFactor: number
+ ): { style: { [key: string]: string }; toolTip: string } {
+ const height = record.duration * multiplicationFactor;
+ const colorPercentage = Math.round((height / MAX_HEIGHT) * 100);
+ const backgroundColor = this.getColorByFrameRate(this.estimateFrameRate(record.duration));
+
+ const style = {
+ 'margin-left': '1px',
+ 'margin-right': '1px',
+ background: `-webkit-linear-gradient(bottom, ${backgroundColor} ${colorPercentage}%, #f3f3f3 ${colorPercentage}%)`,
+ border: '1px solid #d0d0d0',
+ cursor: 'pointer',
+ 'min-width': '25px',
+ width: '25px',
+ height: '50px',
+ };
+ const toolTip = `${record.source} TimeSpent: ${record.duration.toFixed(3)}ms`;
+ return { style, toolTip };
}
}
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.module.ts b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.module.ts
index 1221cd91251..b0f65ed9d14 100644
--- a/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.module.ts
+++ b/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording/timeline/timeline.module.ts
@@ -10,13 +10,27 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { MatIconModule } from '@angular/material/icon';
import { FrameSelectorComponent } from './frame-selector/frame-selector.component';
import { TimelineControlsComponent } from './timeline-controls/timeline-controls.component';
+import { ScrollingModule } from '@angular/cdk/scrolling';
+import { RecordingDialogComponent } from './recording-modal/recording-dialog/recording-dialog.component';
+import { RecordingModalComponent } from './recording-modal/recording-modal.component';
+import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { MatDialogModule } from '@angular/material/dialog';
@NgModule({
- declarations: [TimelineComponent, FrameSelectorComponent, TimelineControlsComponent],
+ declarations: [
+ TimelineComponent,
+ RecordingDialogComponent,
+ RecordingModalComponent,
+ FrameSelectorComponent,
+ TimelineControlsComponent,
+ ],
imports: [
+ ScrollingModule,
CommonModule,
FormsModule,
RecordingVisualizerModule,
+ MatDialogModule,
+ MatProgressBarModule,
MatButtonModule,
MatTooltipModule,
MatIconModule,