diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.html b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.html index e8d116154f2..8bbca2a5316 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.html +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/profiler.component.html @@ -19,7 +19,7 @@
-

- {{ selectionLabel() }} / {{ frameCount() }} -

+

{{ selectionLabel() }} / {{ frameCount() }}

- + - -
-
+ +
+
- - + diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/frame-selector/frame-selector.component.scss b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/frame-selector/frame-selector.component.scss index 14f6c6e1102..3cb43076fd5 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/frame-selector/frame-selector.component.scss +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/frame-selector/frame-selector.component.scss @@ -1,64 +1,90 @@ @use '../../../../../styles/typography'; -.bar-graph-container { - padding: 2px; +:host { + position: relative; height: var(--max-bar-height); + flex: 0 0 var(--max-bar-height); + background-color: var(--octonary-contrast); + border-bottom: 1px solid var(--color-separator); display: flex; flex-direction: row; align-items: flex-end; justify-content: center; align-items: center; - margin-bottom: 10px; + padding: 0.5rem; + gap: 0.375rem; - .txt-frames { - @extend %body-bold-01; - padding: 0px; - margin: 0px 10px; - width: 150px; + .selected-frame { + @extend %body-01; + position: absolute; + top: 0.5rem; + right: 2rem; + margin: 0; + padding: 0.1rem 0.375rem; text-align: center; + background: var(--dynamic-transparent-01); + backdrop-filter: blur(10px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + border-radius: 0.75rem; + pointer-events: none; + z-index: 1; } .bar-container { - max-width: calc(100vw - 150px); align-items: baseline; overflow-x: auto; width: 100%; height: 100%; + flex: 1; + cursor: pointer; - ::ng-deep .cdk-virtual-scroll-content-wrapper { - display: flex; + &.drag-scrolling { + /* We use an animation for the cursor to avoid + applying the style when we perform a regular click. */ + animation: grabbing-cursor 1 forwards; + animation-delay: 200ms; } &::-webkit-scrollbar { display: none; } - .frame-bar { - margin-left: 2.5px; - margin-right: 2.5px; - margin-top: 2px; + ::ng-deep .cdk-virtual-scroll-content-wrapper { + display: flex; + column-gap: 2.5px; + } - &:hover { + .frame-bar { + position: relative; + + &::before { + content: ''; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + z-index: -1; + } + + &:hover::before { background-color: var(--septenary-contrast); } - &.selected { - margin-left: 0; - margin-right: 0; - margin-top: 0; - padding-left: 0.5px; - padding-right: 0.5px; - + &.selected::before { background-color: var(--dynamic-blue-01); - border: 2px solid var(--dynamic-blue-01); } } } +} - button { - margin-bottom: 5px; +@keyframes grabbing-cursor { + from { + cursor: default; + } + to { + cursor: grabbing; } } diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/frame-selector/frame-selector.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/frame-selector/frame-selector.component.ts index 5b9955a247e..43df0af3a81 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/frame-selector/frame-selector.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/frame-selector/frame-selector.component.ts @@ -18,7 +18,9 @@ import { ElementRef, inject, input, + linkedSignal, output, + Signal, signal, viewChild, } from '@angular/core'; @@ -28,13 +30,27 @@ import {TabUpdate} from '../../../tab-update/index'; import {GraphNode} from '../record-formatter/record-formatter'; import {MatIcon} from '@angular/material/icon'; import {MatTooltip} from '@angular/material/tooltip'; -import {MatCard} from '@angular/material/card'; -import {NgStyle} from '@angular/common'; -import {MatIconButton} from '@angular/material/button'; +import {DecimalPipe, NgStyle} from '@angular/common'; import {ProfilerFrame} from '../../../../../../../protocol'; +import {ButtonComponent} from '../../../../shared/button/button.component'; -const ITEM_WIDTH = 25; +const ITEM_GAP = 2.5; +const ITEM_WIDTH = 18; const MAX_HEIGHT = 100; +const DRAG_SCROLL_SPEED = 2; + +/** Returns a linked signal that resets on source change. */ +function framesBoundSignal(source: Signal, defaultValue: T) { + return linkedSignal({ + source, + computation: (source, prev) => { + if (source.length !== prev?.source.length) { + return defaultValue; + } + return prev.value; + }, + }); +} @Component({ selector: 'ng-frame-selector', @@ -44,14 +60,14 @@ const MAX_HEIGHT = 100; :host { --max-bar-height: ${MAX_HEIGHT}px } `, imports: [ - MatCard, MatTooltip, MatIcon, - MatIconButton, CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf, NgStyle, + ButtonComponent, + DecimalPipe, ], }) export class FrameSelectorComponent { @@ -64,9 +80,11 @@ export class FrameSelectorComponent { readonly viewport = viewChild.required(CdkVirtualScrollViewport); - readonly startFrameIndex = signal(-1); - readonly endFrameIndex = signal(-1); - readonly selectedFrameIndexes = signal(new Set()); + readonly startFrameIndex = framesBoundSignal(this.frames, -1); + readonly endFrameIndex = framesBoundSignal(this.frames, -1); + readonly selectedFrameIndexes = framesBoundSignal>(this.frames, new Set()); + + readonly dragScrolling = signal(false); readonly frameCount = computed(() => this.frames().length); readonly disableNextFrameButton = computed( () => this.endFrameIndex() >= this.frameCount() - 1 || this.selectedFrameIndexes().size > 1, @@ -84,7 +102,7 @@ export class FrameSelectorComponent { private _viewportScrollState = {scrollLeft: 0, xCoordinate: 0, isDragScrolling: false}; - readonly itemWidth = ITEM_WIDTH; + readonly itemWidth = ITEM_WIDTH + ITEM_GAP; private readonly maxFrameDuration = computed(() => this.frames().reduce((acc: number, frame: ProfilerFrame) => Math.max(acc, frame.duration), 0), @@ -212,10 +230,12 @@ export class FrameSelectorComponent { } stopDragScrolling(): void { + this.dragScrolling.set(false); this._viewportScrollState.isDragScrolling = false; } startDragScroll(event: MouseEvent): void { + this.dragScrolling.set(true); this._viewportScrollState = { xCoordinate: event.clientX, scrollLeft: this.viewport().elementRef.nativeElement.scrollLeft, @@ -228,10 +248,9 @@ export class FrameSelectorComponent { return; } - const dragScrollSpeed = 2; const dx = event.clientX - this._viewportScrollState.xCoordinate; this.viewport().elementRef.nativeElement.scrollLeft = - this._viewportScrollState.scrollLeft - dx * dragScrollSpeed; + this._viewportScrollState.scrollLeft - dx * DRAG_SCROLL_SPEED; } private getBarStyles(frame: ProfilerFrame, multiplicationFactor: number): GraphNode { @@ -243,9 +262,7 @@ export class FrameSelectorComponent { const style = { 'background-image': `-webkit-linear-gradient(bottom, ${backgroundColor} ${colorPercentage}%, transparent ${colorPercentage}%)`, - cursor: 'pointer', - 'min-width': '25px', - width: '25px', + width: ITEM_WIDTH + 'px', height: MAX_HEIGHT + 'px', }; const toolTip = `${frame.source} TimeSpent: ${frame.duration.toFixed(3)}ms`; @@ -254,12 +271,12 @@ export class FrameSelectorComponent { private getColorByFrameRate(framerate: number): string { if (framerate >= 60) { - return '#d6f0d1'; - } else if (framerate < 60 && framerate >= 30) { - return '#f2dca2'; - } else if (framerate < 30 && framerate >= 15) { - return '#f9cc9d'; + return 'var(--dynamic-green-01)'; + } else if (60 > framerate && framerate >= 30) { + return 'var(--dynamic-yellow-01)'; + } else if (30 > framerate && framerate >= 15) { + return 'var(--dynamic-red-03)'; } - return '#fad1d1'; + return 'var(--dynamic-red-01)'; } } diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.html b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.html index 8a29f8f13e2..f3424aa4e07 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.html +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.html @@ -1,7 +1,10 @@ - + diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.scss b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.scss index 6c2e07618bc..c024208a74d 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.scss +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.scss @@ -3,4 +3,8 @@ justify-content: space-between; padding: 0.5rem; border-bottom: 1px solid var(--color-separator); + + input { + width: 10rem; + } } diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.ts index 7c77dbbb5bf..988e0161ddb 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline-controls/recording-timeline-controls.component.ts @@ -6,16 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, output} from '@angular/core'; +import {ChangeDetectionStrategy, Component, output, signal} from '@angular/core'; import {ButtonComponent} from '../../../../shared/button/button.component'; +const FILTER_PLACEHOLDER = 'Filter'; + @Component({ selector: 'ng-recording-timeline-controls', templateUrl: './recording-timeline-controls.component.html', styleUrls: ['./recording-timeline-controls.component.scss'], imports: [ButtonComponent], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class RecordingTimelineControlsComponent { - readonly exportProfile = output(); - readonly filter = output(); + protected readonly exportProfile = output(); + protected readonly filter = output(); + protected readonly filterPlaceholder = signal(FILTER_PLACEHOLDER); + protected readonly FILTER_PLACEHOLDER = FILTER_PLACEHOLDER; } diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline.component.html b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline.component.html index ecf640c2ae7..50fee4c6a75 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline.component.html +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/recording-timeline/recording-timeline.component.html @@ -3,25 +3,28 @@ @if (!hasFrames() && !visualizing()) { } + @if (!hasFrames() && visualizing()) {

There's no information to show.

} + @if (hasFrames()) { +} + +@if (hasFrames() && !frame) { +

Select a bar to preview a particular change detection cycle.

+} @else if (hasFrames() && frame) { -} -@if (hasFrames() && !frame) { -

Select a bar to preview a particular change detection cycle.

-} @else if (hasFrames() && frame) { - @if (estimatedFrameRate() >= 60 && record()) { - - } - @if (estimatedFrameRate() < 60 && record()) { - - } - @if (estimatedFrameRate() < 60 && record()) { -