refactor(devtools): improve profiler recorder controls (#62010)

Reorganize the template and reduce the vertical space taken by the controls.

PR Close #62010
This commit is contained in:
hawkgs
2025-06-11 18:01:27 +03:00
committed by Pawel Kozlowski
parent 579bf948d7
commit bd3dc192fd
8 changed files with 116 additions and 106 deletions
@@ -44,6 +44,7 @@ ng_project(
"//:node_modules/@angular/material",
"//:node_modules/rxjs",
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline:timeline_rjs",
"//devtools/projects/ng-devtools/src/lib/shared/button:button_rjs",
"//devtools/projects/protocol:protocol_rjs",
],
)
@@ -1,65 +1,66 @@
<div class="profiler-wrapper">
<mat-card>
@if (state() === 'idle') {
<div class="recorder-controls">
@switch (state()) {
@case ('idle') {
<button
mat-icon-button
color="primary"
ng-button
btnType="icon"
(click)="startRecording()"
class="profiler-control start-recording-button"
matTooltip="Start recording"
aria-label="Start recording"
>
<mat-icon> circle </mat-icon>
</button>
} @else if (state() === 'recording') {
<button
mat-icon-button
(click)="stopRecording()"
class="profiler-control recording-button"
matTooltip="Stop recording"
aria-label="Stop recording"
>
<mat-icon> circle </mat-icon>
</button>
} @else if (state() === 'visualizing') {
<button
mat-icon-button
color="primary"
(click)="discardRecording()"
class="profiler-control discard-button"
matTooltip="Clear recording"
aria-label="Clear recording"
>
<mat-icon> not_interested </mat-icon>
</button>
}
<p class="instructions" [class.hidden]="state() !== 'idle'">
<span>
Click the record button to start a new recording, or upload a JSON file containing profiler
data.
</span>
<br />
<span>
<p class="instructions">
<span>
Click the record button to start a new recording, or upload a JSON file containing
profiler data.
</span>
<br />
<input
type="file"
(change)="importProfilerResults($event)"
placeholder="Upload file"
accept=".json"
/>
</span>
</p>
<p class="instructions" [class.hidden]="state() !== 'recording'">
Interact to preview change detection. Clicking stop ends this Profiler recording.
</p>
<p class="instructions" [class.hidden]="state() !== 'visualizing'">
Click Save Profile to save your recording or click refresh to clear the current recording.
</p>
</mat-card>
<div id="profiler-content-wrapper">
@if (state() !== 'idle') {
<div class="visualization">
<ng-recording-timeline [stream]="stream" (exportProfile)="exportProfilerResults()" />
</div>
</p>
}
</div>
@case ('recording') {
<button
ng-button
btnType="icon"
(click)="stopRecording()"
class="recording-button"
matTooltip="Stop recording"
aria-label="Stop recording"
>
<mat-icon> circle </mat-icon>
</button>
<p class="instructions">
Interact to preview change detection. Clicking stop ends this Profiler recording.
</p>
}
@case ('visualizing') {
<button
ng-button
btnType="icon"
(click)="discardRecording()"
matTooltip="Clear recording"
aria-label="Clear recording"
>
<mat-icon> not_interested </mat-icon>
</button>
<p class="instructions">
Click Save Profile to save your recording or click refresh to clear the current recording.
</p>
}
}
</div>
@if (state() !== 'idle') {
<div class="recording">
<ng-recording-timeline [stream]="stream" (exportProfile)="exportProfilerResults()" />
</div>
}
@@ -1,55 +1,47 @@
@use '../../../styles/typography';
:host,
.profiler-wrapper {
width: 100%;
height: calc(100% - 30px);
p {
margin: 0;
}
}
.mat-icon {
font-size: 18px;
top: 4px;
position: relative;
}
.profiler-control {
cursor: pointer;
&.recording-button {
color: var(--dynamic-red-01);
}
}
.instructions {
@extend %body-01;
&.hidden {
display: none;
}
input,
span {
margin-top: 5px;
cursor: pointer;
}
}
#profiler-content-wrapper {
margin: 0;
height: calc(100% - 30px);
width: 100%;
}
.visualization {
margin: 0;
:host {
display: flex;
flex-direction: column;
height: 100%;
}
.mdc-card {
flex-direction: row;
align-items: center;
.recorder-controls {
display: flex;
align-items: flex-start;
gap: 0.25rem;
border-bottom: 1px solid var(--color-separator);
button {
$icon-size: 18px;
margin: 0.625rem;
color: var(--secondary-contrast);
height: $icon-size;
&.recording-button {
color: var(--dynamic-red-01);
}
mat-icon {
width: $icon-size;
height: $icon-size;
font-size: $icon-size;
}
}
.instructions {
@extend %body-01;
padding-block: 0.7rem;
margin: 0;
input {
margin-top: 0.5rem;
cursor: pointer;
}
}
}
.recording {
flex: 1;
overflow: hidden;
}
}
@@ -8,16 +8,15 @@
import {Component, inject, signal} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {MatIcon} from '@angular/material/icon';
import {MatTooltip} from '@angular/material/tooltip';
import {Events, MessageBus, ProfilerFrame} from '../../../../../protocol';
import {Subject} from 'rxjs';
import {FileApiService} from './file-api-service';
import {ProfilerImportDialogComponent} from './profiler-import-dialog.component';
import {TimelineComponent} from './timeline/timeline.component';
import {MatIcon} from '@angular/material/icon';
import {MatTooltip} from '@angular/material/tooltip';
import {MatIconButton} from '@angular/material/button';
import {MatCard} from '@angular/material/card';
import {ButtonComponent} from '../../shared/button/button.component';
type State = 'idle' | 'recording' | 'visualizing';
@@ -28,7 +27,7 @@ const PROFILER_VERSION = 1;
selector: 'ng-profiler',
templateUrl: './profiler.component.html',
styleUrls: ['./profiler.component.scss'],
imports: [MatCard, MatIconButton, MatTooltip, MatIcon, TimelineComponent],
imports: [MatTooltip, MatIcon, TimelineComponent, ButtonComponent],
})
export class ProfilerComponent {
readonly state = signal<State>('idle');
@@ -4,6 +4,8 @@
display: block;
overflow: auto;
height: 100%;
padding-bottom: 1rem;
::ng-deep {
.as-split-gutter-icon {
display: none;
@@ -15,4 +15,14 @@
background: var(--dynamic-blue-02);
color: var(--septenary-contrast);
}
&.type-icon {
background: transparent;
border: none;
padding: 0;
&.size-compact {
padding: 0;
}
}
}
@@ -8,7 +8,7 @@
import {ChangeDetectionStrategy, Component, input} from '@angular/core';
type ButtonType = 'primary'; // To be extended in future
type ButtonType = 'primary' | 'icon';
type ButtonSize = 'standard' | 'compact';
@Component({
@@ -19,6 +19,7 @@ type ButtonSize = 'standard' | 'compact';
host: {
class: 'ng-button',
'[class.type-primary]': `btnType() === 'primary'`,
'[class.type-icon]': `btnType() === 'icon'`,
'[class.size-compact]': `size() === 'compact'`,
},
})
@@ -148,3 +148,7 @@ mat-form-field {
);
}
}
[hidden] {
display: none !important;
}