refactor(devtools): save user settings in the settings store (#62429)

Save user settings in the `SettingsStore`.

NOTE: The theme is omitted since the change is not trivial and it will be handled in a separate PR.

PR Close #62429
This commit is contained in:
hawkgs
2025-07-15 15:52:20 +03:00
committed by Kristiyan Kostadinov
parent 1cdb54559d
commit cb8d30a293
13 changed files with 97 additions and 55 deletions
@@ -9,5 +9,6 @@ ng_project(
"//devtools/projects/ng-devtools/src/lib",
"//devtools/projects/ng-devtools/src/lib/application-environment",
"//devtools/projects/ng-devtools/src/lib/application-operations",
"//devtools/projects/ng-devtools/src/lib/application-providers:settings",
],
)
@@ -66,7 +66,7 @@ ts_test_library(
"//devtools/projects/ng-devtools/src/lib/application-environment",
"//devtools/projects/ng-devtools/src/lib/application-providers:window",
"//devtools/projects/ng-devtools/src/lib/application-services/test-utils:app_operations_mock",
"//devtools/projects/ng-devtools/src/lib/application-services/test-utils:settings_store_mock",
"//devtools/projects/ng-devtools/src/lib/application-services/test-utils:settings_mock",
"//devtools/projects/protocol",
],
)
@@ -12,9 +12,27 @@ import {SettingsStore} from './settings_store';
export class Settings {
private readonly settingsStore = inject(SettingsStore);
readonly dummy = this.settingsStore.create({
key: 'dummy',
readonly showCommentNodes = this.settingsStore.create({
key: 'show_comment_nodes',
category: 'general',
initialValue: true,
initialValue: false,
});
readonly routerGraphEnabled = this.settingsStore.create({
key: 'router_graph_enabled',
category: 'general',
initialValue: false,
});
readonly timingAPIEnabled = this.settingsStore.create({
key: 'timing_api_enabled',
category: 'general',
initialValue: false,
});
readonly signalGraphEnabled = this.settingsStore.create({
key: 'signal_graph_enabled',
category: 'general',
initialValue: false,
});
}
@@ -3,12 +3,13 @@ load("//devtools/tools:defaults.bzl", "ts_project")
package(default_visibility = ["//devtools:__subpackages__"])
ts_project(
name = "settings_store_mock",
name = "settings_mock",
srcs = [
"settings_store_mock.ts",
"settings_mock.ts",
],
deps = [
"//:node_modules/@angular/core",
"//devtools/projects/ng-devtools/src/lib/application-services:settings",
"//devtools/projects/ng-devtools/src/lib/application-services:settings_store",
],
)
@@ -0,0 +1,33 @@
/**
* @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 {Provider, signal, WritableSignal} from '@angular/core';
import {Settings} from '../settings';
import {SettingsStore} from '../settings_store';
export class SettingsMock extends Settings {
routerGraphEnabled = signal(false);
showCommentNodes = signal(false);
signalGraphEnabled = signal(false);
timingAPIEnabled = signal(false);
}
export const SETTINGS_MOCK: Provider[] = [
{
provide: SettingsStore,
useClass: class {
create(config: unknown): WritableSignal<unknown> {
return signal<unknown>(null);
}
},
},
{
provide: Settings,
useClass: SettingsMock,
},
];
@@ -1,25 +0,0 @@
/**
* @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 {Provider, signal} from '@angular/core';
import {SettingsStore} from '../settings_store';
export class SettingsStoreMock {
private readonly signals = new Map<string, unknown>();
get(key: string, initialValue: unknown) {
const value = this.signals.get(key) ?? signal(initialValue);
this.signals.set(key, value);
return value;
}
}
export const SETTINGS_STORE_MOCK: Provider = {
provide: SettingsStore,
useClass: SettingsStoreMock,
};
@@ -27,6 +27,7 @@ ng_project(
"//:node_modules/rxjs",
"//devtools/projects/ng-devtools/src/lib/application-environment",
"//devtools/projects/ng-devtools/src/lib/application-services:frame_manager",
"//devtools/projects/ng-devtools/src/lib/application-services:settings",
"//devtools/projects/ng-devtools/src/lib/application-services:theme",
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer",
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree:injector_tree",
@@ -51,6 +52,7 @@ ts_test_library(
"//devtools/projects/ng-devtools/src/lib/application-environment",
"//devtools/projects/ng-devtools/src/lib/application-services:frame_manager",
"//devtools/projects/ng-devtools/src/lib/application-services:theme",
"//devtools/projects/ng-devtools/src/lib/application-services/test-utils:settings_mock",
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer",
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/tab-update",
"//devtools/projects/protocol",
@@ -130,7 +130,7 @@
[checked]="routerGraphEnabled()"
(change)="setRouterGraph($event.checked)"
/>
<span class="ng-mat-menu-label-text">Enable Router Graph</span>
<span class="ng-mat-menu-label-text">Enable Router Tree</span>
</label>
}
@if (supportedApis().signals) {
@@ -40,6 +40,7 @@ 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';
type Tab = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree' | 'Transfer State';
@@ -67,23 +68,29 @@ type Tab = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree' | 'Transf
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DevToolsTabsComponent {
readonly applicationEnvironment = inject(ApplicationEnvironment);
readonly frameManager = inject(FrameManager);
readonly themeService = inject(ThemeService);
private readonly tabUpdate = inject(TabUpdate);
private readonly messageBus = inject<MessageBus<Events>>(MessageBus);
private readonly settings = inject(Settings);
readonly isHydrationEnabled = input(false);
readonly supportedApis = input.required<SupportedApis>();
readonly frameSelected = output<Frame>();
readonly applicationEnvironment = inject(ApplicationEnvironment);
readonly activeTab = signal<Tab>('Components');
readonly inspectorRunning = signal(false);
readonly showCommentNodes = signal(false);
readonly routerGraphEnabled = signal(false);
readonly timingAPIEnabled = signal(false);
readonly signalGraphEnabled = signal(false);
protected readonly showCommentNodes = this.settings.showCommentNodes;
protected readonly routerGraphEnabled = this.settings.routerGraphEnabled;
protected readonly timingAPIEnabled = this.settings.timingAPIEnabled;
protected readonly signalGraphEnabled = this.settings.signalGraphEnabled;
readonly transferStateTabEnabled = signal(false);
readonly componentExplorerView = signal<ComponentExplorerView | null>(null);
readonly providers = signal<SerializedProviderRecord[]>([]);
readonly routes = signal<Route[]>([]);
readonly frameManager = inject(FrameManager);
readonly snapToRoot = signal(false);
@@ -123,27 +130,23 @@ export class DevToolsTabsComponent {
readonly extensionVersion = signal('dev-build');
public tabUpdate = inject(TabUpdate);
public themeService = inject(ThemeService);
private _messageBus = inject<MessageBus<Events>>(MessageBus);
constructor() {
this._messageBus.on('updateRouterTree', (routes: any[]) => {
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', () => {
this.messageBus.on('selectComponent', () => {
if (this.activeTab() !== 'Components') {
this.changeTab('Components');
}
});
this._messageBus.on('latestComponentExplorerView', (view: ComponentExplorerView) => {
this.messageBus.on('latestComponentExplorerView', (view: ComponentExplorerView) => {
this.componentExplorerView.set(view);
});
this._messageBus.on(
this.messageBus.on(
'latestInjectorProviders',
(_: SerializedInjector, providers: SerializedProviderRecord[]) => {
this.providers.set(providers);
@@ -165,7 +168,7 @@ export class DevToolsTabsComponent {
this.activeTab.set(tab);
this.tabUpdate.notify(tab);
if (tab === 'Router Tree') {
this._messageBus.emit('getRoutes');
this.messageBus.emit('getRoutes');
this.snapToRoot.set(true);
}
}
@@ -177,10 +180,10 @@ export class DevToolsTabsComponent {
emitInspectorEvent(): void {
if (this.inspectorRunning()) {
this._messageBus.emit('inspectorStart');
this.messageBus.emit('inspectorStart');
} else {
this._messageBus.emit('inspectorEnd');
this._messageBus.emit('removeHighlightOverlay');
this.messageBus.emit('inspectorEnd');
this.messageBus.emit('removeHighlightOverlay');
}
}
@@ -191,8 +194,8 @@ export class DevToolsTabsComponent {
toggleTimingAPI(): void {
this.timingAPIEnabled.update((state) => !state);
this.timingAPIEnabled()
? this._messageBus.emit('enableTimingAPI')
: this._messageBus.emit('disableTimingAPI');
? this.messageBus.emit('enableTimingAPI')
: this.messageBus.emit('disableTimingAPI');
}
protected setRouterGraph(enabled: boolean): void {
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Component} from '@angular/core';
import {Component, signal} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {MatMenuModule} from '@angular/material/menu';
import {MatTooltip} from '@angular/material/tooltip';
@@ -20,6 +20,7 @@ import {DevToolsTabsComponent} from './devtools-tabs.component';
import {TabUpdate} from './tab-update/index';
import {DirectiveExplorerComponent} from './directive-explorer/directive-explorer.component';
import {FrameManager} from '../application-services/frame_manager';
import {SETTINGS_MOCK} from '../application-services/test-utils/settings_mock';
@Component({
selector: 'ng-directive-explorer',
@@ -41,6 +42,7 @@ describe('DevtoolsTabsComponent', () => {
imports: [MatTooltip, MatMenuModule, DevToolsTabsComponent],
providers: [
TabUpdate,
SETTINGS_MOCK,
{provide: ThemeService, useFactory: () => ({currentTheme: new Subject<Theme>()})},
{provide: MessageBus, useValue: messageBusMock},
{provide: ApplicationEnvironment, useValue: applicationEnvironmentMock},
@@ -13,3 +13,4 @@
export {DevToolsComponent} from './lib/devtools.component';
export * from './lib/application-operations';
export * from './lib/application-environment';
export * from './lib/application-providers/settings_provider';
@@ -7,7 +7,7 @@
*/
import {ApplicationConfig, provideZonelessChangeDetection} from '@angular/core';
import {ApplicationEnvironment, ApplicationOperations} from '../../../ng-devtools';
import {ApplicationEnvironment, ApplicationOperations, provideSettings} from '../../../ng-devtools';
import {ChromeApplicationEnvironment} from './chrome-application-environment';
import {ChromeApplicationOperations} from './chrome-application-operations';
@@ -39,5 +39,6 @@ export const appConfig: ApplicationConfig = {
return new PriorityAwareMessageBus(new ChromeMessageBus(port));
},
},
provideSettings(),
],
};
+6 -1
View File
@@ -12,7 +12,11 @@ import {
provideAppInitializer,
} from '@angular/core';
import {provideRouter} from '@angular/router';
import {ApplicationEnvironment, ApplicationOperations} from '../../projects/ng-devtools';
import {
ApplicationEnvironment,
ApplicationOperations,
provideSettings,
} from '../../projects/ng-devtools';
import {DemoApplicationEnvironment} from '../demo-application-environment';
import {DemoApplicationOperations} from '../demo-application-operations';
@@ -45,5 +49,6 @@ export const appConfig: ApplicationConfig = {
},
// We simulate a transfer state created by the server-side rendering.
provideAppInitializer(async () => serializeTransferState()),
provideSettings(),
],
};