Skip to content

Commit

Permalink
refactor(devtools): use signal apis in visualizer and devtools-tabs (a…
Browse files Browse the repository at this point in the history
…ngular#57192)

Refactor the visualizer and devtools-tabs components to use signal apis, in future we can make the components onPush and zoneless

PR Close angular#57192
  • Loading branch information
sheikalthaf authored and thePunderWoman committed Sep 5, 2024
1 parent e3919e7 commit 4fa25cf
Show file tree
Hide file tree
Showing 15 changed files with 231 additions and 243 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<nav class="devtools-nav" #navBar mat-tab-nav-bar [color]="'accent'" [tabPanel]="tabPanel">
<nav class="devtools-nav" #navBar mat-tab-nav-bar mat-stretch-tabs="false" [disablePagination]="true" [color]="'accent'" [tabPanel]="tabPanel">
<div id="nav-buttons">
<button (click)="toggleInspector()" matTooltip="Inspect element">
<mat-icon [class.inspector-active]="inspectorRunning"> pin_end </mat-icon>
<mat-icon [class.inspector-active]="inspectorRunning()"> pin_end </mat-icon>
</button>
<button [matMenuTriggerFor]="menu" matTooltip="Open settings">
<mat-icon> settings </mat-icon>
Expand All @@ -25,8 +25,8 @@
}
</select>

@for (tab of tabs; track $index) {
<a class="mat-tab-link" mat-tab-link (click)="changeTab(tab)" [active]="activeTab === tab">
@for (tab of tabs(); track $index) {
<a class="mat-tab-link" mat-tab-link (click)="changeTab(tab)" [active]="activeTab() === tab">
{{ tab }}
</a>
}
Expand All @@ -39,10 +39,10 @@
{{ angularVersion() }}
</span>
} @else {
<span
<span
id="version-number"
matTooltip="
Angular Devtools supports Angular versions 12 and above. Some DevTools features may be available in
Angular Devtools supports Angular versions 12 and above. Some DevTools features may be available in
older versions of Angular, but it is not officially supported.
"
class="unsupported-version"
Expand All @@ -51,7 +51,7 @@
</span>
}

| DevTools: {{ extensionVersion }}
| DevTools: {{ extensionVersion() }}
</section>
}
</nav>
Expand All @@ -60,35 +60,38 @@
@if (!applicationEnvironment.frameSelectorEnabled || frameManager.selectedFrame !== null) {
<div class="tab-content">
<ng-directive-explorer
[showCommentNodes]="showCommentNodes"
[isHydrationEnabled]="isHydrationEnabled"
[class.hidden]="activeTab !== 'Components'"
[showCommentNodes]="showCommentNodes()"
[isHydrationEnabled]="isHydrationEnabled()"
[class.hidden]="activeTab() !== 'Components'"
(toggleInspector)="toggleInspector()"
/>
<ng-profiler [class.hidden]="activeTab !== 'Profiler'"/>
<ng-router-tree [routes]="routes" [class.hidden]="activeTab !== 'Router Tree'"/>
<ng-injector-tree [class.hidden]="activeTab !== 'Injector Tree'"/>
<ng-profiler [class.hidden]="activeTab() !== 'Profiler'"/>
<ng-router-tree [routes]="routes()" [class.hidden]="activeTab() !== 'Router Tree'"/>
<ng-injector-tree [class.hidden]="activeTab() !== 'Injector Tree'"/>
</div>
}
</mat-tab-nav-panel>

<mat-menu #menu="matMenu">
@if (!profilingNotificationsSupported) {
<div mat-menu-item disableRipple (click)="$event.stopPropagation(); toggleTimingAPI()">
<mat-slide-toggle [checked]="timingAPIEnabled">
Enable timing API
<div (click)="$event.stopPropagation()">
@if (!profilingNotificationsSupported) {
<label mat-menu-item disableRipple>
<mat-slide-toggle [checked]="timingAPIEnabled()" (change)="toggleTimingAPI()">
Enable timing API
</mat-slide-toggle>
</label>
}
<label mat-menu-item disableRipple>
@let currentTheme = themeService.currentTheme();
<mat-slide-toggle [checked]="currentTheme === 'dark-theme'" (click)="themeService.toggleDarkMode(currentTheme === 'light-theme')">
Dark Mode
</mat-slide-toggle>
</div>
}
<div mat-menu-item disableRipple (click)="$event.stopPropagation(); themeService.toggleDarkMode(currentTheme === 'light-theme')">
<mat-slide-toggle [checked]="currentTheme === 'dark-theme'">
Dark Mode
</mat-slide-toggle>
</div>
<div mat-menu-item disableRipple (click)="$event.stopPropagation(); showCommentNodes = $event.checked">
<mat-slide-toggle [checked]="showCommentNodes">
Show comment nodes
</mat-slide-toggle>
</label>
<label mat-menu-item disableRipple>
<mat-slide-toggle [checked]="showCommentNodes()" (change)="showCommentNodes.set($event.checked)">
Show comment nodes
</mat-slide-toggle>
</label>
</div>
</mat-menu>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {
AfterViewInit,
Component,
computed,
EventEmitter,
inject,
input,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {Component, computed, inject, input, output, signal} from '@angular/core';
import {MatIcon} from '@angular/material/icon';
import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu';
import {MatSlideToggle} from '@angular/material/slide-toggle';
Expand Down Expand Up @@ -60,79 +48,61 @@ type Tabs = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree';
],
providers: [TabUpdate],
})
export class DevToolsTabsComponent implements OnInit, AfterViewInit {
@Input() isHydrationEnabled = false;

@Output() frameSelected = new EventEmitter<Frame>();
@ViewChild(DirectiveExplorerComponent) directiveExplorer!: DirectiveExplorerComponent;
@ViewChild('navBar', {static: true}) navbar!: MatTabNav;

applicationEnvironment = inject(ApplicationEnvironment);
activeTab: Tabs = 'Components';
inspectorRunning = false;
routerTreeEnabled = false;
showCommentNodes = false;
timingAPIEnabled = false;
profilingNotificationsSupported = Boolean(
(window.chrome?.devtools as any)?.performance?.onProfilingStarted,
);
export class DevToolsTabsComponent {
readonly isHydrationEnabled = input(false);
readonly frameSelected = output<Frame>();

currentTheme!: Theme;
routes: Route[] = [];
readonly applicationEnvironment = inject(ApplicationEnvironment);
readonly activeTab = signal<Tabs>('Components');
readonly inspectorRunning = signal(false);
readonly showCommentNodes = signal(false);
readonly timingAPIEnabled = signal(false);

frameManager = inject(FrameManager);
readonly routes = signal<Route[]>([]);
readonly frameManager = inject(FrameManager);

readonly tabs = computed<Tabs[]>(() => {
const alwaysShown: Tabs[] = ['Components', 'Profiler', 'Injector Tree'];
return this.routes().length === 0 ? alwaysShown : [...alwaysShown, 'Router Tree'];
});

profilingNotificationsSupported = Boolean(
(window.chrome?.devtools as any)?.performance?.onProfilingStarted,
);
TOP_LEVEL_FRAME_ID = TOP_LEVEL_FRAME_ID;

angularVersion = input<string | undefined>(undefined);
majorAngularVersion = computed(() => {
readonly angularVersion = input<string | undefined>(undefined);
readonly majorAngularVersion = computed(() => {
const version = this.angularVersion();
if (!version) {
return -1;
}
return parseInt(version.toString().split('.')[0], 10);
});

extensionVersion = 'Development Build';
readonly extensionVersion = signal('Development Build');

constructor(
public tabUpdate: TabUpdate,
public themeService: ThemeService,
private _messageBus: MessageBus<Events>,
) {
this.themeService.currentTheme
.pipe(takeUntilDestroyed())
.subscribe((theme) => (this.currentTheme = theme));
public tabUpdate = inject(TabUpdate);
public themeService = inject(ThemeService);
private _messageBus = inject<MessageBus<Events>>(MessageBus);

constructor() {
this._messageBus.on('updateRouterTree', (routes) => {
this.routes = routes || [];
this.routes.set(routes || []);
});
}

emitSelectedFrame(frameId: string): void {
const frame = this.frameManager.frames.find((frame) => frame.id === parseInt(frameId, 10));
this.frameSelected.emit(frame);
}

ngOnInit(): void {
this.navbar.stretchTabs = false;

if (chrome !== undefined && chrome.runtime !== undefined) {
this.extensionVersion = chrome.runtime.getManifest().version;
if (typeof chrome !== 'undefined' && chrome.runtime !== undefined) {
this.extensionVersion.set(chrome.runtime.getManifest().version);
}
}

get tabs(): Tabs[] {
const alwaysShown: Tabs[] = ['Components', 'Profiler', 'Injector Tree'];
return this.routes.length === 0 ? alwaysShown : [...alwaysShown, 'Router Tree'];
}

ngAfterViewInit(): void {
this.navbar.disablePagination = true;
emitSelectedFrame(frameId: string): void {
const frame = this.frameManager.frames.find((frame) => frame.id === parseInt(frameId, 10));
this.frameSelected.emit(frame!);
}

changeTab(tab: Tabs): void {
this.activeTab = tab;
this.activeTab.set(tab);
this.tabUpdate.notify();
if (tab === 'Router Tree') {
this._messageBus.emit('getRoutes');
Expand All @@ -145,7 +115,7 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
}

emitInspectorEvent(): void {
if (this.inspectorRunning) {
if (this.inspectorRunning()) {
this._messageBus.emit('inspectorStart');
} else {
this._messageBus.emit('inspectorEnd');
Expand All @@ -154,12 +124,12 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
}

toggleInspectorState(): void {
this.inspectorRunning = !this.inspectorRunning;
this.inspectorRunning.update((state) => !state);
}

toggleTimingAPI(): void {
this.timingAPIEnabled = !this.timingAPIEnabled;
this.timingAPIEnabled
this.timingAPIEnabled.update((state) => !state);
this.timingAPIEnabled()
? this._messageBus.emit('enableTimingAPI')
: this._messageBus.emit('disableTimingAPI');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ describe('DevtoolsTabsComponent', () => {
});

it('toggles inspector flag', () => {
expect(comp.inspectorRunning).toBe(false);
expect(comp.inspectorRunning()).toBe(false);
comp.toggleInspectorState();
expect(comp.inspectorRunning).toBe(true);
expect(comp.inspectorRunning()).toBe(true);
comp.toggleInspectorState();
expect(comp.inspectorRunning).toBe(false);
expect(comp.inspectorRunning()).toBe(false);
});

it('emits inspector event', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="wrapper" @stagger>
@for (bar of internalData; track $index) {
@for (bar of internalData(); track $index) {
<div
@appear
(click)="barClick.emit(originalData[$index])"
(click)="barClick.emit(data()[$index])"
class="bar"
[style.backgroundColor]="color"
[style.backgroundColor]="color()"
[style.width.%]="bar.width"
[matTooltip]="bar.text">
<span>{{ bar.text }}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
transition,
trigger,
} from '@angular/animations';
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {ChangeDetectionStrategy, Component, computed, input, output} from '@angular/core';

import {BargraphNode} from '../record-formatter/bargraph-formatter/bargraph-formatter';
import {MatTooltip} from '@angular/material/tooltip';
Expand All @@ -41,28 +41,29 @@ interface BarData {
],
standalone: true,
imports: [MatTooltip],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BarChartComponent {
@Input()
set data(nodes: BargraphNode[]) {
this.originalData = nodes;
this.internalData = [];
readonly data = input<BargraphNode[]>([]);

readonly internalData = computed(() => {
const nodes = this.data() ?? [];
const values: BarData[] = [];
const max = nodes.reduce((a: number, c) => Math.max(a, c.value), -Infinity);
for (const node of nodes) {
this.internalData.push({
values.push({
label: node.label,
count: node.count ?? 1,
width: (node.value / max) * 100,
time: node.value,
text: createBarText(node),
});
}
}
@Input({required: true}) color!: string;
@Output() barClick = new EventEmitter<BargraphNode>();
return values;
});

originalData!: BargraphNode[];
internalData: BarData[] = [];
readonly color = input.required<string>();
readonly barClick = output<BargraphNode>();
}

export function createBarText(bar: BargraphNode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div class="level-profile-wrapper">
<ng-bar-chart (barClick)="selectNode($event)" [color]="barColor" [data]="profileRecords"> </ng-bar-chart>
<ng-bar-chart (barClick)="selectNode($event)" [color]="barColor()" [data]="profileRecords()"> </ng-bar-chart>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Component, EventEmitter, Input, OnDestroy, Output} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {ChangeDetectionStrategy, Component, computed, inject, input, output} from '@angular/core';
import {ProfilerFrame} from 'protocol';

import {Theme, ThemeService} from '../../../../theme-service';
import {ThemeService} from '../../../../theme-service';
import {BarGraphFormatter, BargraphNode} from '../record-formatter/bargraph-formatter/index';

import {formatDirectiveProfile} from './profile-formatter';
Expand All @@ -23,25 +22,20 @@ import {BarChartComponent} from './bar-chart.component';
styleUrls: ['./bargraph-visualizer.component.scss'],
standalone: true,
imports: [BarChartComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BargraphVisualizerComponent {
barColor!: string;
profileRecords!: BargraphNode[];
public themeService = inject(ThemeService);
readonly barColor = computed(() => {
return this.themeService.currentTheme() === 'dark-theme' ? '#073d69' : '#cfe8fc';
});

@Output() nodeSelect = new EventEmitter<SelectedEntry>();
readonly nodeSelect = output<SelectedEntry>();

private _formatter = new BarGraphFormatter();
private readonly _formatter = new BarGraphFormatter();
frame = input.required<ProfilerFrame>();

@Input()
set frame(data: ProfilerFrame) {
this.profileRecords = this._formatter.formatFrame(data);
}

constructor(public themeService: ThemeService) {
this.themeService.currentTheme.pipe(takeUntilDestroyed()).subscribe((theme) => {
this.barColor = theme === 'dark-theme' ? '#073d69' : '#cfe8fc';
});
}
profileRecords = computed(() => this._formatter.formatFrame(this.frame()));

formatEntryData(bargraphNode: BargraphNode): SelectedDirective[] {
return formatDirectiveProfile(bargraphNode.directives ?? []);
Expand Down
Loading

0 comments on commit 4fa25cf

Please sign in to comment.