mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
79519409cd
Previous the router tree was an opt-in feature that required manual enablement in settings.
Now the router tree is enabled by default whenever the application supports it and routes are detected.
(cherry picked from commit 35046d3dba)
206 lines
6.5 KiB
TypeScript
206 lines
6.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
import {
|
|
ChangeDetectionStrategy,
|
|
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';
|
|
import {MatTabLink, MatTabNav, MatTabNavPanel} from '@angular/material/tabs';
|
|
import {MatTooltip} from '@angular/material/tooltip';
|
|
import {
|
|
ComponentExplorerView,
|
|
Events,
|
|
MessageBus,
|
|
Route,
|
|
SerializedInjector,
|
|
SerializedProviderRecord,
|
|
} from '../../../../protocol';
|
|
|
|
import {ApplicationEnvironment, Frame, TOP_LEVEL_FRAME_ID} from '../application-environment/index';
|
|
import {FrameManager} from '../application-services/frame_manager';
|
|
import {ThemeService} from '../application-services/theme_service';
|
|
|
|
import {DirectiveExplorerComponent} from './directive-explorer/directive-explorer.component';
|
|
import {InjectorTreeComponent} from './injector-tree/injector-tree.component';
|
|
import {ProfilerComponent} from './profiler/profiler.component';
|
|
import {RouterTreeComponent} from './router-tree/router-tree.component';
|
|
import {TransferStateComponent} from './transfer-state/transfer-state.component';
|
|
import {TabUpdate} from './tab-update/index';
|
|
import {Settings} from '../application-services/settings';
|
|
import {SUPPORTED_APIS} from '../application-providers/supported_apis';
|
|
import {ButtonComponent} from '../shared/button/button.component';
|
|
|
|
type Tab = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree' | 'Transfer State';
|
|
|
|
@Component({
|
|
selector: 'ng-devtools-tabs',
|
|
templateUrl: './devtools-tabs.component.html',
|
|
styleUrls: ['./devtools-tabs.component.scss'],
|
|
imports: [
|
|
MatTabNav,
|
|
MatTabNavPanel,
|
|
MatTooltip,
|
|
MatIcon,
|
|
MatMenu,
|
|
MatMenuItem,
|
|
MatMenuTrigger,
|
|
MatTabLink,
|
|
DirectiveExplorerComponent,
|
|
ProfilerComponent,
|
|
RouterTreeComponent,
|
|
InjectorTreeComponent,
|
|
TransferStateComponent,
|
|
MatSlideToggle,
|
|
ButtonComponent,
|
|
],
|
|
providers: [TabUpdate],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
})
|
|
export class DevToolsTabsComponent {
|
|
public readonly frameManager = inject(FrameManager);
|
|
protected readonly themeService = inject(ThemeService);
|
|
private readonly tabUpdate = inject(TabUpdate);
|
|
protected readonly messageBus = inject<MessageBus<Events>>(MessageBus);
|
|
protected readonly settings = inject(Settings);
|
|
protected readonly applicationEnvironment = inject(ApplicationEnvironment);
|
|
protected readonly supportedApis = inject(SUPPORTED_APIS);
|
|
|
|
protected readonly isHydrationEnabled = input(false);
|
|
readonly frameSelected = output<Frame>();
|
|
|
|
readonly inspectorRunning = signal(false);
|
|
|
|
protected readonly showCommentNodes = this.settings.showCommentNodes;
|
|
protected readonly timingAPIEnabled = this.settings.timingAPIEnabled;
|
|
protected readonly signalGraphEnabled = () => this.supportedApis().signals;
|
|
protected readonly transferStateEnabled = this.settings.transferStateEnabled;
|
|
protected readonly activeTab = this.settings.activeTab;
|
|
|
|
protected readonly componentExplorerView = signal<ComponentExplorerView | null>(null);
|
|
protected readonly providers = signal<SerializedProviderRecord[]>([]);
|
|
readonly routes = signal<Route[]>([]);
|
|
|
|
protected readonly tabs = computed<Tab[]>(() => {
|
|
const supportedApis = this.supportedApis();
|
|
const tabs: Tab[] = ['Components'];
|
|
|
|
if (supportedApis.profiler) {
|
|
tabs.push('Profiler');
|
|
}
|
|
if (supportedApis.dependencyInjection) {
|
|
tabs.push('Injector Tree');
|
|
}
|
|
if (supportedApis.routes && this.routes().length > 0) {
|
|
tabs.push('Router Tree');
|
|
}
|
|
if (supportedApis.transferState && this.transferStateEnabled()) {
|
|
tabs.push('Transfer State');
|
|
}
|
|
|
|
return tabs;
|
|
});
|
|
|
|
protected readonly profilingNotificationsSupported = Boolean(
|
|
(window.chrome?.devtools as any)?.performance?.onProfilingStarted,
|
|
);
|
|
protected readonly TOP_LEVEL_FRAME_ID = TOP_LEVEL_FRAME_ID;
|
|
|
|
protected readonly angularVersion = input<string | undefined>();
|
|
readonly majorAngularVersion = computed(() => {
|
|
const version = this.angularVersion();
|
|
if (!version) {
|
|
return -1;
|
|
}
|
|
return parseInt(version.toString().split('.')[0], 10);
|
|
});
|
|
|
|
protected readonly extensionVersion = signal('dev-build');
|
|
|
|
constructor() {
|
|
this.messageBus.on('updateRouterTree', (routes: any[]) => {
|
|
this.routes.set(routes || []);
|
|
});
|
|
|
|
// Change the tab to Components, if an element is selected via the inspector.
|
|
this.messageBus.on('selectComponent', () => {
|
|
if (this.activeTab() !== 'Components') {
|
|
this.changeTab('Components');
|
|
}
|
|
});
|
|
|
|
this.messageBus.on('latestComponentExplorerView', (view: ComponentExplorerView) => {
|
|
this.componentExplorerView.set(view);
|
|
});
|
|
|
|
this.messageBus.on(
|
|
'latestInjectorProviders',
|
|
(_: SerializedInjector, providers: SerializedProviderRecord[]) => {
|
|
this.providers.set(providers);
|
|
},
|
|
);
|
|
|
|
if (typeof chrome !== 'undefined' && chrome.runtime !== undefined) {
|
|
this.extensionVersion.set(chrome.runtime.getManifest().version);
|
|
}
|
|
}
|
|
|
|
emitSelectedFrame(event: Event): void {
|
|
const frameId = (event.target as HTMLInputElement).value;
|
|
const frame = this.frameManager.frames().find((frame) => frame.id === parseInt(frameId, 10));
|
|
this.frameSelected.emit(frame!);
|
|
}
|
|
|
|
changeTab(tab: Tab): void {
|
|
this.activeTab.set(tab);
|
|
this.tabUpdate.notify(tab);
|
|
if (tab === 'Router Tree') {
|
|
this.messageBus.emit('getRoutes');
|
|
}
|
|
}
|
|
|
|
toggleInspector(): void {
|
|
this.toggleInspectorState();
|
|
this.emitInspectorEvent();
|
|
}
|
|
|
|
emitInspectorEvent(): void {
|
|
if (this.inspectorRunning()) {
|
|
this.messageBus.emit('inspectorStart');
|
|
} else {
|
|
this.messageBus.emit('inspectorEnd');
|
|
this.messageBus.emit('removeHighlightOverlay');
|
|
}
|
|
}
|
|
|
|
toggleInspectorState(): void {
|
|
this.inspectorRunning.update((state) => !state);
|
|
}
|
|
|
|
toggleTimingAPI(): void {
|
|
this.timingAPIEnabled.update((state) => !state);
|
|
this.timingAPIEnabled()
|
|
? this.messageBus.emit('enableTimingAPI')
|
|
: this.messageBus.emit('disableTimingAPI');
|
|
}
|
|
|
|
protected setTransferStateTab(enabled: boolean): void {
|
|
this.transferStateEnabled.set(enabled);
|
|
if (!enabled && this.activeTab() === 'Transfer State') {
|
|
this.activeTab.set('Components');
|
|
}
|
|
}
|
|
}
|