From 4f3ad984669c554b90e49fbe9b3795d95033780f Mon Sep 17 00:00:00 2001 From: hawkgs Date: Wed, 15 Jan 2025 14:42:01 +0200 Subject: [PATCH] refactor(devtools): styles management (#59589) - Move all styles to ng-devtools/src/styles. - Create a BrowserService that detects the browsers and adds it as a class to the body. Move global browser styles. - Create theme mixins that incorporate the browser type into them. - Refactor some of the affected code along with the introduced changes. PR Close #59589 --- devtools/BUILD.bazel | 15 --- .../projects/demo-standalone/src/BUILD.bazel | 30 ++--- .../src/app/devtools-app/BUILD.bazel | 2 +- .../devtools-app/devtools-app.component.ts | 2 +- .../projects/demo-standalone/src/styles.scss | 2 +- .../src/styles/chrome_styles.scss | 1 - .../src/styles/firefox_styles.scss | 1 - .../projects/ng-devtools/src/lib/BUILD.bazel | 59 ++-------- .../src/lib/application-providers/BUILD.bazel | 11 ++ .../application-providers/window_provider.ts | 16 +++ .../src/lib/application-services/BUILD.bazel | 60 ++++++++++ .../browser_styles_service.ts | 76 ++++++++++++ .../browser_styles_service_spec.ts | 77 +++++++++++++ .../frame_manager.ts | 2 +- .../frame_manager_spec.ts | 2 +- .../lib/application-services/theme_service.ts | 48 ++++++++ .../theme_service_spec.ts | 109 ++++++++++++++++++ .../src/lib/devtools-tabs/BUILD.bazel | 12 +- .../devtools-tabs/devtools-tabs.component.ts | 4 +- .../lib/devtools-tabs/devtools-tabs.spec.ts | 4 +- .../directive-explorer/BUILD.bazel | 6 +- .../directive-explorer.component.ts | 2 +- .../directive-explorer.spec.ts | 2 +- .../property-tab/property-view/BUILD.bazel | 2 +- .../injector-providers/BUILD.bazel | 2 +- .../bargraph-formatter/BUILD.bazel | 2 +- .../flamegraph-formatter/BUILD.bazel | 4 +- .../flamegraph-formatter.ts | 2 +- .../timeline/recording-visualizer/BUILD.bazel | 2 +- .../bargraph-visualizer.component.ts | 2 +- .../flamegraph-visualizer.component.ts | 2 +- .../ng-devtools/src/lib/devtools.component.ts | 30 ++--- .../ng-devtools/src/lib/devtools_spec.ts | 2 +- .../ng-devtools/src/lib/theme-service.ts | 42 ------- .../ng-devtools/src/styles/BUILD.bazel | 42 +++++++ .../ng-devtools/src/styles/_browser.scss | 15 +++ .../ng-devtools/src/styles/_colors.scss | 1 + .../ng-devtools/src/styles/_global.scss} | 4 + .../ng-devtools/src/styles/_theme.scss | 30 +++++ .../ng-devtools/src/styles/chrome.scss | 1 + .../ng-devtools/src/styles/firefox.scss | 1 + .../projects/shell-browser/src/BUILD.bazel | 30 ++--- .../shell-browser/src/app/app.config.ts | 2 +- .../projects/shell-browser/src/styles.scss | 2 +- .../src/styles/chrome_styles.scss | 1 - .../src/styles/firefox_styles.scss | 1 - devtools/src/BUILD.bazel | 30 ++--- devtools/src/app/devtools-app/BUILD.bazel | 2 +- .../app/devtools-app/devtools-app.routes.ts | 2 +- devtools/src/styles.scss | 2 +- devtools/src/styles/chrome_styles.scss | 1 - devtools/src/styles/firefox_styles.scss | 1 - 52 files changed, 588 insertions(+), 215 deletions(-) delete mode 100644 devtools/projects/demo-standalone/src/styles/chrome_styles.scss delete mode 100644 devtools/projects/demo-standalone/src/styles/firefox_styles.scss create mode 100644 devtools/projects/ng-devtools/src/lib/application-providers/BUILD.bazel create mode 100644 devtools/projects/ng-devtools/src/lib/application-providers/window_provider.ts create mode 100644 devtools/projects/ng-devtools/src/lib/application-services/BUILD.bazel create mode 100644 devtools/projects/ng-devtools/src/lib/application-services/browser_styles_service.ts create mode 100644 devtools/projects/ng-devtools/src/lib/application-services/browser_styles_service_spec.ts rename devtools/projects/ng-devtools/src/lib/{ => application-services}/frame_manager.ts (98%) rename devtools/projects/ng-devtools/src/lib/{ => application-services}/frame_manager_spec.ts (99%) create mode 100644 devtools/projects/ng-devtools/src/lib/application-services/theme_service.ts create mode 100644 devtools/projects/ng-devtools/src/lib/application-services/theme_service_spec.ts delete mode 100644 devtools/projects/ng-devtools/src/lib/theme-service.ts create mode 100644 devtools/projects/ng-devtools/src/styles/BUILD.bazel create mode 100644 devtools/projects/ng-devtools/src/styles/_browser.scss create mode 100644 devtools/projects/ng-devtools/src/styles/_colors.scss rename devtools/{styles.scss => projects/ng-devtools/src/styles/_global.scss} (88%) create mode 100644 devtools/projects/ng-devtools/src/styles/_theme.scss create mode 100644 devtools/projects/ng-devtools/src/styles/chrome.scss create mode 100644 devtools/projects/ng-devtools/src/styles/firefox.scss delete mode 100644 devtools/projects/shell-browser/src/styles/chrome_styles.scss delete mode 100644 devtools/projects/shell-browser/src/styles/firefox_styles.scss delete mode 100644 devtools/src/styles/chrome_styles.scss delete mode 100644 devtools/src/styles/firefox_styles.scss diff --git a/devtools/BUILD.bazel b/devtools/BUILD.bazel index f4003bd6d6e..9e0065b59d3 100644 --- a/devtools/BUILD.bazel +++ b/devtools/BUILD.bazel @@ -1,4 +1,3 @@ -load("@io_bazel_rules_sass//:defs.bzl", "npm_sass_library", "sass_library") load("//tools:defaults.bzl", "ts_config") package(default_visibility = ["//visibility:public"]) @@ -8,14 +7,6 @@ exports_files([ "cypress.json", ]) -npm_sass_library( - name = "material_sass_deps", - deps = [ - "@npm//@angular/cdk", - "@npm//@angular/material", - ], -) - ts_config( name = "tsconfig_spec", src = "tsconfig.spec.json", @@ -23,9 +14,3 @@ ts_config( "//devtools:tsconfig.json", ], ) - -sass_library( - name = "global_styles", - srcs = ["styles.scss"], - deps = [":material_sass_deps"], -) diff --git a/devtools/projects/demo-standalone/src/BUILD.bazel b/devtools/projects/demo-standalone/src/BUILD.bazel index 2ebea2d4daa..1f991e6e911 100644 --- a/devtools/projects/demo-standalone/src/BUILD.bazel +++ b/devtools/projects/demo-standalone/src/BUILD.bazel @@ -1,8 +1,9 @@ -load("//devtools/tools:ng_module.bzl", "ng_module") -load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") load("@build_bazel_rules_nodejs//:index.bzl", "pkg_web") -load("//tools:defaults.bzl", "esbuild", "http_server") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") +load("//devtools/tools:ng_module.bzl", "ng_module") load("//devtools/tools/esbuild:index.bzl", "LINKER_PROCESSED_FW_PACKAGES") +load("//tools:defaults.bzl", "esbuild", "http_server") package(default_visibility = ["//visibility:public"]) @@ -11,17 +12,19 @@ sass_binary( src = "styles.scss", include_paths = ["external/npm/node_modules"], sourcemap = False, - deps = ["//devtools:global_styles"], + deps = ["//devtools/projects/ng-devtools/src/styles:global"], ) -sass_binary( - name = "firefox_styles", - src = "styles/firefox_styles.scss", -) - -sass_binary( - name = "chrome_styles", - src = "styles/chrome_styles.scss", +copy_to_directory( + name = "browser_specific_styles", + srcs = [ + "//devtools/projects/ng-devtools/src/styles:chrome", + "//devtools/projects/ng-devtools/src/styles:firefox", + ], + out = "styles", + replace_prefixes = { + "devtools/projects/ng-devtools/src/styles": "", + }, ) ng_module( @@ -56,9 +59,8 @@ exports_files(["index.html"]) filegroup( name = "dev_app_static_files", srcs = [ - ":chrome_styles", + ":browser_specific_styles", ":demo_styles", - ":firefox_styles", ":index.html", "//packages/zone.js/bundles:zone.umd.js", ], diff --git a/devtools/projects/demo-standalone/src/app/devtools-app/BUILD.bazel b/devtools/projects/demo-standalone/src/app/devtools-app/BUILD.bazel index a81b0fbb426..2d292a19b00 100644 --- a/devtools/projects/demo-standalone/src/app/devtools-app/BUILD.bazel +++ b/devtools/projects/demo-standalone/src/app/devtools-app/BUILD.bazel @@ -7,7 +7,7 @@ ng_module( srcs = ["devtools-app.component.ts"], deps = [ "//devtools/projects/ng-devtools", - "//devtools/projects/ng-devtools/src/lib:frame_manager", + "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", "//devtools/projects/protocol", "//devtools/src:iframe_message_bus", "//packages/common", diff --git a/devtools/projects/demo-standalone/src/app/devtools-app/devtools-app.component.ts b/devtools/projects/demo-standalone/src/app/devtools-app/devtools-app.component.ts index adc938a4d23..d414d51d6c5 100644 --- a/devtools/projects/demo-standalone/src/app/devtools-app/devtools-app.component.ts +++ b/devtools/projects/demo-standalone/src/app/devtools-app/devtools-app.component.ts @@ -11,7 +11,7 @@ import {Events, MessageBus, PriorityAwareMessageBus} from 'protocol'; import {IFrameMessageBus} from '../../../../../src/iframe-message-bus'; import {DevToolsComponent} from 'ng-devtools'; -import {FrameManager} from '../../../../../projects/ng-devtools/src/lib/frame_manager'; +import {FrameManager} from '../../../../../projects/ng-devtools/src/lib/application-services/frame_manager'; @Component({ imports: [DevToolsComponent], diff --git a/devtools/projects/demo-standalone/src/styles.scss b/devtools/projects/demo-standalone/src/styles.scss index b5f91b71cf6..adbeab311bc 100644 --- a/devtools/projects/demo-standalone/src/styles.scss +++ b/devtools/projects/demo-standalone/src/styles.scss @@ -1 +1 @@ -@use '../../../styles.scss' as devtools; +@use '../../../projects/ng-devtools/src/styles/global'; diff --git a/devtools/projects/demo-standalone/src/styles/chrome_styles.scss b/devtools/projects/demo-standalone/src/styles/chrome_styles.scss deleted file mode 100644 index 9959545b62f..00000000000 --- a/devtools/projects/demo-standalone/src/styles/chrome_styles.scss +++ /dev/null @@ -1 +0,0 @@ -/** Specific style for Chrome browser */ diff --git a/devtools/projects/demo-standalone/src/styles/firefox_styles.scss b/devtools/projects/demo-standalone/src/styles/firefox_styles.scss deleted file mode 100644 index af7608ad155..00000000000 --- a/devtools/projects/demo-standalone/src/styles/firefox_styles.scss +++ /dev/null @@ -1 +0,0 @@ -/** Specific style for Firefox browser */ diff --git a/devtools/projects/ng-devtools/src/lib/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/BUILD.bazel index 052e8cd15bf..a944a6d1e7a 100644 --- a/devtools/projects/ng-devtools/src/lib/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/BUILD.bazel @@ -1,7 +1,7 @@ -load("//devtools/tools:ng_module.bzl", "ng_module") load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") -load("//devtools/tools:typescript.bzl", "ts_test_library") load("//devtools/tools:defaults.bzl", "karma_web_test_suite") +load("//devtools/tools:ng_module.bzl", "ng_module") +load("//devtools/tools:typescript.bzl", "ts_test_library") package(default_visibility = ["//visibility:public"]) @@ -15,8 +15,6 @@ ng_module( srcs = glob( include = ["*.ts"], exclude = [ - "theme-service.ts", - "frame_manager.ts", "*_spec.ts", ], ), @@ -25,12 +23,13 @@ ng_module( ":devtools_component_styles", ], deps = [ - ":frame_manager", - ":theme", + "//devtools/projects/ng-devtools/src/lib/application-providers:window", + "//devtools/projects/ng-devtools/src/lib/application-services:browser_styles", + "//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/devtools-tabs", "//devtools/projects/protocol", "//packages/animations", - "//packages/common", "//packages/core", "//packages/forms", "//packages/platform-browser-dynamic", @@ -45,8 +44,8 @@ ts_test_library( name = "devtools_test", srcs = ["devtools_spec.ts"], deps = [ - ":frame_manager", ":lib", + "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", "//devtools/projects/ng-devtools/src/lib/devtools-tabs", "//devtools/projects/protocol", "//packages/core", @@ -60,47 +59,3 @@ karma_web_test_suite( ":devtools_test", ], ) - -ng_module( - name = "frame_manager", - srcs = glob( - include = ["frame_manager.ts"], - ), - deps = [ - "//devtools/projects/ng-devtools/src/lib/application-environment", - "//devtools/projects/protocol", - "//packages/core", - ], -) - -ts_test_library( - name = "test_frame_manager_lib", - srcs = [ - "frame_manager_spec.ts", - ], - deps = [ - ":frame_manager", - "//devtools/projects/ng-devtools/src/lib/application-environment", - "//devtools/projects/protocol", - "//packages/core/testing", - ], -) - -karma_web_test_suite( - name = "test_frame_manager", - deps = [ - ":test_frame_manager_lib", - ], -) - -ng_module( - name = "theme", - srcs = glob( - include = ["theme-service.ts"], - ), - deps = [ - "//packages/core", - "@npm//@types", - "@npm//rxjs", - ], -) diff --git a/devtools/projects/ng-devtools/src/lib/application-providers/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/application-providers/BUILD.bazel new file mode 100644 index 00000000000..f4c3db266c8 --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/application-providers/BUILD.bazel @@ -0,0 +1,11 @@ +load("//devtools/tools:ng_module.bzl", "ng_module") + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "window", + srcs = ["window_provider.ts"], + deps = [ + "//packages/core", + ], +) diff --git a/devtools/projects/ng-devtools/src/lib/application-providers/window_provider.ts b/devtools/projects/ng-devtools/src/lib/application-providers/window_provider.ts new file mode 100644 index 00000000000..764d14ea3d8 --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/application-providers/window_provider.ts @@ -0,0 +1,16 @@ +/** + * @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 {InjectionToken, Provider} from '@angular/core'; + +export const WINDOW = new InjectionToken('WINDOW'); + +export const WINDOW_PROVIDER: Provider = { + provide: WINDOW, + useValue: window, +}; diff --git a/devtools/projects/ng-devtools/src/lib/application-services/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/application-services/BUILD.bazel new file mode 100644 index 00000000000..ada3c0e7e2f --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/application-services/BUILD.bazel @@ -0,0 +1,60 @@ +load("//devtools/tools:defaults.bzl", "karma_web_test_suite") +load("//devtools/tools:ng_module.bzl", "ng_module") +load("//devtools/tools:typescript.bzl", "ts_test_library") + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "browser_styles", + srcs = ["browser_styles_service.ts"], + deps = [ + "//packages/common", + "//packages/core", + "@npm//@angular/cdk", + ], +) + +ng_module( + name = "frame_manager", + srcs = ["frame_manager.ts"], + deps = [ + "//devtools/projects/ng-devtools/src/lib/application-environment", + "//devtools/projects/protocol", + "//packages/core", + ], +) + +ng_module( + name = "theme", + srcs = ["theme_service.ts"], + deps = [ + "//devtools/projects/ng-devtools/src/lib/application-providers:window", + "//packages/common", + "//packages/core", + "@npm//@types", + "@npm//rxjs", + ], +) + +ts_test_library( + name = "test_application_services_lib", + srcs = glob(["*_spec.ts"]), + deps = [ + ":browser_styles", + ":frame_manager", + ":theme", + "//devtools/projects/ng-devtools/src/lib/application-environment", + "//devtools/projects/ng-devtools/src/lib/application-providers:window", + "//devtools/projects/protocol", + "//packages/common", + "//packages/core/testing", + "@npm//@angular/cdk", + ], +) + +karma_web_test_suite( + name = "test_application_services", + deps = [ + ":test_application_services_lib", + ], +) diff --git a/devtools/projects/ng-devtools/src/lib/application-services/browser_styles_service.ts b/devtools/projects/ng-devtools/src/lib/application-services/browser_styles_service.ts new file mode 100644 index 00000000000..2a2f1d8ea34 --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/application-services/browser_styles_service.ts @@ -0,0 +1,76 @@ +/** + * @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 {inject, Injectable, RendererFactory2} from '@angular/core'; +import {DOCUMENT} from '@angular/common'; +import {Platform} from '@angular/cdk/platform'; + +// Update for with newly supported browser +export type Browser = 'chrome' | 'firefox' | 'unknown'; + +// If for some reason we are unable to properly +// detect the browser, we are defaulting +// to Chrome's styles. + +// Keep class names in sync with _theme.scss and _browser.scss +const BROWSER_CLASS_NAME: {[key in Browser]: string} = { + 'chrome': 'chrome-ui', + 'firefox': 'firefox-ui', + 'unknown': 'chrome-ui', +}; + +const BROWSER_STYLES: {[key in Browser]: string} = { + 'chrome': 'chrome.css', + 'firefox': 'firefox.css', + 'unknown': 'chrome.css', +}; + +@Injectable({ + providedIn: 'root', +}) +export class BrowserStylesService { + private readonly doc = inject(DOCUMENT); + private readonly rendererFactory = inject(RendererFactory2); + private readonly renderer = this.rendererFactory.createRenderer(null, null); + private readonly platform = inject(Platform); + + private readonly browser = this.detectBrowser(); + + initBrowserSpecificStyles() { + this.addBrowserUiClass(); + this.loadBrowserStyle(); + } + + /** Add the browser class to the document body */ + private addBrowserUiClass() { + const browserClass = BROWSER_CLASS_NAME[this.browser]; + this.renderer.addClass(this.doc.body, browserClass); + } + + /** Load browser-specific styles */ + private loadBrowserStyle() { + const fileName = BROWSER_STYLES[this.browser]; + const head = this.doc.getElementsByTagName('head')[0]; + + const style = this.renderer.createElement('link'); + style.rel = 'stylesheet'; + style.href = `./styles/${fileName}`; + + head.appendChild(style); + } + + private detectBrowser(): Browser { + if (this.platform.BLINK) { + return 'chrome'; + } + if (this.platform.FIREFOX) { + return 'firefox'; + } + return 'unknown'; + } +} diff --git a/devtools/projects/ng-devtools/src/lib/application-services/browser_styles_service_spec.ts b/devtools/projects/ng-devtools/src/lib/application-services/browser_styles_service_spec.ts new file mode 100644 index 00000000000..a8062c9ffeb --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/application-services/browser_styles_service_spec.ts @@ -0,0 +1,77 @@ +/** + * @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 {TestBed} from '@angular/core/testing'; +import {DOCUMENT} from '@angular/common'; +import {Platform} from '@angular/cdk/platform'; +import {Browser, BrowserStylesService} from './browser_styles_service'; + +function configureTestingModuleWithPlatformMock(mock: Partial) { + TestBed.configureTestingModule({ + providers: [ + { + provide: Platform, + useValue: mock, + }, + ], + }); +} + +function checkForBrowserSpecificStyles(browser: Browser) { + const doc = TestBed.inject(DOCUMENT); + + // Keep in sync with the service. + return { + hasClass: doc.body.classList.contains(browser + '-ui'), + hasStylesheet: !!doc.head.querySelector(`link[href="./styles/${browser}.css"]`), + }; +} + +describe('BrowserStylesService', () => { + it('should initialize browser-specific styles for Chrome', () => { + configureTestingModuleWithPlatformMock({ + BLINK: true, + }); + + const service = TestBed.inject(BrowserStylesService); + service.initBrowserSpecificStyles(); + + const {hasClass, hasStylesheet} = checkForBrowserSpecificStyles('chrome'); + + expect(hasClass).toBeTrue(); + expect(hasStylesheet).toBeTrue(); + }); + + it('should initialize browser-specific styles for Firefox', () => { + configureTestingModuleWithPlatformMock({ + FIREFOX: true, + }); + + const service = TestBed.inject(BrowserStylesService); + service.initBrowserSpecificStyles(); + + const {hasClass, hasStylesheet} = checkForBrowserSpecificStyles('firefox'); + + expect(hasClass).toBeTrue(); + expect(hasStylesheet).toBeTrue(); + }); + + it('should default to Chrome UI, if the browser is not supported', () => { + configureTestingModuleWithPlatformMock({ + WEBKIT: true, + }); + + const service = TestBed.inject(BrowserStylesService); + service.initBrowserSpecificStyles(); + + const {hasClass, hasStylesheet} = checkForBrowserSpecificStyles('chrome'); + + expect(hasClass).toBeTrue(); + expect(hasStylesheet).toBeTrue(); + }); +}); diff --git a/devtools/projects/ng-devtools/src/lib/frame_manager.ts b/devtools/projects/ng-devtools/src/lib/application-services/frame_manager.ts similarity index 98% rename from devtools/projects/ng-devtools/src/lib/frame_manager.ts rename to devtools/projects/ng-devtools/src/lib/application-services/frame_manager.ts index 1825a0c7447..7eb81cdfbba 100644 --- a/devtools/projects/ng-devtools/src/lib/frame_manager.ts +++ b/devtools/projects/ng-devtools/src/lib/application-services/frame_manager.ts @@ -9,7 +9,7 @@ import {Injectable, inject, signal, computed} from '@angular/core'; import {Events, MessageBus} from 'protocol'; -import {Frame, TOP_LEVEL_FRAME_ID} from './application-environment'; +import {Frame, TOP_LEVEL_FRAME_ID} from '../application-environment'; @Injectable() export class FrameManager { diff --git a/devtools/projects/ng-devtools/src/lib/frame_manager_spec.ts b/devtools/projects/ng-devtools/src/lib/application-services/frame_manager_spec.ts similarity index 99% rename from devtools/projects/ng-devtools/src/lib/frame_manager_spec.ts rename to devtools/projects/ng-devtools/src/lib/application-services/frame_manager_spec.ts index 9d983a4d75c..62d4954ce28 100644 --- a/devtools/projects/ng-devtools/src/lib/frame_manager_spec.ts +++ b/devtools/projects/ng-devtools/src/lib/application-services/frame_manager_spec.ts @@ -9,7 +9,7 @@ import {Events, MessageBus} from 'protocol'; import {FrameManager} from './frame_manager'; import {TestBed} from '@angular/core/testing'; -import {Frame} from './application-environment'; +import {Frame} from '../application-environment'; describe('FrameManager', () => { let frameManager: FrameManager; diff --git a/devtools/projects/ng-devtools/src/lib/application-services/theme_service.ts b/devtools/projects/ng-devtools/src/lib/application-services/theme_service.ts new file mode 100644 index 00000000000..479aa6caef1 --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/application-services/theme_service.ts @@ -0,0 +1,48 @@ +/** + * @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 {inject, Injectable, Renderer2, RendererFactory2, signal} from '@angular/core'; +import {DOCUMENT} from '@angular/common'; +import {WINDOW} from '../application-providers/window_provider'; + +export type Theme = 'dark-theme' | 'light-theme'; + +// Keep class names in sync with _theme.scss and _global.scss +const DARK_THEME_CLASS = 'dark-theme'; +const LIGHT_THEME_CLASS = 'light-theme'; + +@Injectable() +export class ThemeService { + private win = inject(WINDOW); + private doc = inject(DOCUMENT); + private renderer: Renderer2; + readonly currentTheme = signal(LIGHT_THEME_CLASS); + + constructor(private _rendererFactory: RendererFactory2) { + this.renderer = this._rendererFactory.createRenderer(null, null); + this.toggleDarkMode(this.prefersDarkMode); + } + + private get prefersDarkMode(): boolean { + return this.win.matchMedia && this.win.matchMedia('(prefers-color-scheme: dark)').matches; + } + + toggleDarkMode(isDark: boolean): void { + const removeClass = isDark ? LIGHT_THEME_CLASS : DARK_THEME_CLASS; + const addClass = !isDark ? LIGHT_THEME_CLASS : DARK_THEME_CLASS; + this.renderer.removeClass(this.doc.body, removeClass); + this.renderer.addClass(this.doc.body, addClass); + this.currentTheme.set(addClass); + } + + initializeThemeWatcher(): void { + this.win.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { + this.toggleDarkMode(this.prefersDarkMode); + }); + } +} diff --git a/devtools/projects/ng-devtools/src/lib/application-services/theme_service_spec.ts b/devtools/projects/ng-devtools/src/lib/application-services/theme_service_spec.ts new file mode 100644 index 00000000000..d443df19ec2 --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/application-services/theme_service_spec.ts @@ -0,0 +1,109 @@ +/** + * @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 {TestBed} from '@angular/core/testing'; +import {DOCUMENT} from '@angular/common'; +import {ThemeService} from './theme_service'; +import {WINDOW} from '../application-providers/window_provider'; + +function configureTestingModuleWithWindowMock(mock: Partial) { + TestBed.configureTestingModule({ + providers: [ + { + provide: WINDOW, + useValue: mock, + }, + ThemeService, + ], + }); +} + +function mockSystemTheme(initialTheme: 'light' | 'dark' = 'light') { + // Set the initial theme. + let currMediaString = `(prefers-color-scheme: ${initialTheme})`; + let matchMediaListener: (() => void) | null = null; + + return { + /** Alter the system theme */ + switchTheme: (theme: 'light' | 'dark') => { + currMediaString = `(prefers-color-scheme: ${theme})`; + if (matchMediaListener) { + matchMediaListener(); + } + }, + /** matchMedia mock */ + matchMedia: (mediaString: string): MediaQueryList => + ({ + matches: mediaString === currMediaString, + addEventListener: (e: string, cb: () => {}) => { + matchMediaListener = cb; + }, + }) as MediaQueryList, + }; +} + +describe('ThemeService', () => { + it(`should enable light mode, if it's the preferred/system one`, () => { + configureTestingModuleWithWindowMock({ + matchMedia: mockSystemTheme('light').matchMedia, + }); + + const service = TestBed.inject(ThemeService); + const doc = TestBed.inject(DOCUMENT); + + expect(service.currentTheme()).toEqual('light-theme'); + expect(doc.body.classList.contains('light-theme')).toBeTrue(); + }); + + it(`should enable dark mode, if it's the preferred/system one`, () => { + configureTestingModuleWithWindowMock({ + matchMedia: mockSystemTheme('dark').matchMedia, + }); + + const service = TestBed.inject(ThemeService); + const doc = TestBed.inject(DOCUMENT); + + expect(service.currentTheme()).toEqual('dark-theme'); + expect(doc.body.classList.contains('dark-theme')).toBeTrue(); + }); + + it('should toggle dark mode', () => { + configureTestingModuleWithWindowMock({ + matchMedia: mockSystemTheme('light').matchMedia, + }); + + const service = TestBed.inject(ThemeService); + // Toggle dark mode. + service.toggleDarkMode(true); + + const doc = TestBed.inject(DOCUMENT); + + expect(service.currentTheme()).toEqual('dark-theme'); + expect(doc.body.classList.contains('dark-theme')).toBeTrue(); + }); + + it('should update the theme automatically, if the system one changes', () => { + const {switchTheme, matchMedia} = mockSystemTheme('light'); + configureTestingModuleWithWindowMock({matchMedia}); + + const service = TestBed.inject(ThemeService); + // Initialize the watcher. + service.initializeThemeWatcher(); + + const docClassList = TestBed.inject(DOCUMENT).body.classList; + + expect(service.currentTheme()).toEqual('light-theme'); + expect(docClassList.contains('light-theme')).toBeTrue(); + + // This should simulate a system theme change, as if the user did it on OS level. + switchTheme('dark'); + + expect(service.currentTheme()).toEqual('dark-theme'); + expect(docClassList.contains('dark-theme')).toBeTrue(); + }); +}); diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/BUILD.bazel index 8b7f7e58976..751c31e7825 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/BUILD.bazel @@ -1,7 +1,7 @@ -load("//devtools/tools:ng_module.bzl", "ng_module") load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") -load("//devtools/tools:typescript.bzl", "ts_test_library") load("//devtools/tools:defaults.bzl", "karma_web_test_suite") +load("//devtools/tools:ng_module.bzl", "ng_module") +load("//devtools/tools:typescript.bzl", "ts_test_library") package(default_visibility = ["//visibility:public"]) @@ -20,9 +20,9 @@ ng_module( ":devtools_tabs_component_styles", ], deps = [ - "//devtools/projects/ng-devtools/src/lib:frame_manager", - "//devtools/projects/ng-devtools/src/lib:theme", "//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/devtools-tabs/directive-explorer", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree:injector_tree", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler", @@ -43,9 +43,9 @@ ts_test_library( srcs = ["devtools-tabs.spec.ts"], deps = [ ":devtools-tabs", - "//devtools/projects/ng-devtools/src/lib:frame_manager", - "//devtools/projects/ng-devtools/src/lib:theme", "//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/devtools-tabs/directive-explorer", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/tab-update", "//devtools/projects/protocol", diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts index 7a014a2542c..2fe50a0a0cf 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts @@ -15,8 +15,8 @@ import {MatTooltip} from '@angular/material/tooltip'; import {Events, MessageBus, Route} from 'protocol'; import {ApplicationEnvironment, Frame, TOP_LEVEL_FRAME_ID} from '../application-environment/index'; -import {FrameManager} from '../frame_manager'; -import {ThemeService} from '../theme-service'; +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'; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.spec.ts index 8c981ab8991..f27d9ab3de9 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.spec.ts @@ -14,12 +14,12 @@ import {Events, MessageBus} from 'protocol'; import {Subject} from 'rxjs'; import {ApplicationEnvironment} from '../application-environment'; -import {Theme, ThemeService} from '../theme-service'; +import {Theme, ThemeService} from '../application-services/theme_service'; import {DevToolsTabsComponent} from './devtools-tabs.component'; import {TabUpdate} from './tab-update/index'; import {DirectiveExplorerComponent} from './directive-explorer/directive-explorer.component'; -import {FrameManager} from '../frame_manager'; +import {FrameManager} from '../application-services/frame_manager'; @Component({ selector: 'ng-directive-explorer', diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/BUILD.bazel index 46147c6d67b..75ec5878889 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/BUILD.bazel @@ -1,7 +1,7 @@ -load("//devtools/tools:ng_module.bzl", "ng_module") load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") -load("//devtools/tools:typescript.bzl", "ts_test_library") load("//devtools/tools:defaults.bzl", "karma_web_test_suite") +load("//devtools/tools:ng_module.bzl", "ng_module") +load("//devtools/tools:typescript.bzl", "ts_test_library") package(default_visibility = ["//visibility:public"]) @@ -43,8 +43,8 @@ ts_test_library( srcs = ["directive-explorer.spec.ts"], deps = [ ":directive-explorer", - "//devtools/projects/ng-devtools/src/lib:frame_manager", "//devtools/projects/ng-devtools/src/lib/application-operations", + "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/breadcrumbs", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest", diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.ts index 3f0e07979a7..c5d2711a01d 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.ts @@ -31,7 +31,7 @@ import { import {SplitComponent} from '../../../lib/vendor/angular-split/public_api'; import {ApplicationOperations} from '../../application-operations/index'; -import {FrameManager} from '../../frame_manager'; +import {FrameManager} from '../../application-services/frame_manager'; import {BreadcrumbsComponent} from './directive-forest/breadcrumbs/breadcrumbs.component'; import {FlatNode} from './directive-forest/component-data-source'; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts index ad0d0bc3111..3a2ca28c319 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts @@ -17,7 +17,7 @@ import {IndexedNode} from './directive-forest/index-forest'; import SpyObj = jasmine.SpyObj; import {By} from '@angular/platform-browser'; -import {FrameManager} from '../../frame_manager'; +import {FrameManager} from '../../application-services/frame_manager'; import {Component, CUSTOM_ELEMENTS_SCHEMA, output, input} from '@angular/core'; import {ElementPropertyResolver, FlatNode} from './property-resolver/element-property-resolver'; import {BreadcrumbsComponent} from './directive-forest/breadcrumbs/breadcrumbs.component'; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/BUILD.bazel index ad2c5d62769..721d0295f24 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/BUILD.bazel @@ -47,8 +47,8 @@ ng_module( "property-tab-body.component.html", ] + _STYLE_LABELS, deps = [ - "//devtools/projects/ng-devtools/src/lib:frame_manager", "//devtools/projects/ng-devtools/src/lib/application-environment", + "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection:injector_tree_visualizer", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/dependency-injection:resolution_path", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest", diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-providers/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-providers/BUILD.bazel index 31cd6c2b28e..27204f4d5f5 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-providers/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree/injector-providers/BUILD.bazel @@ -9,7 +9,7 @@ sass_binary( include_paths = [ "external/npm/node_modules", ], - deps = ["//devtools:material_sass_deps"], + deps = ["//devtools/projects/ng-devtools/src/styles:material_sass_deps"], ) ng_module( diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/bargraph-formatter/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/bargraph-formatter/BUILD.bazel index 992e093bc57..1715c6d3ebf 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/bargraph-formatter/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/bargraph-formatter/BUILD.bazel @@ -10,7 +10,7 @@ ts_library( ], ), deps = [ - "//devtools/projects/ng-devtools/src/lib:theme", + "//devtools/projects/ng-devtools/src/lib/application-services:theme", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter", "//devtools/projects/ng-devtools/src/lib/vendor/memo-decorator", "//devtools/projects/protocol", diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/BUILD.bazel index a91d4692866..99719aa8332 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/BUILD.bazel @@ -1,6 +1,6 @@ +load("//devtools/tools:defaults.bzl", "karma_web_test_suite") load("//devtools/tools:ng_module.bzl", "ng_module") load("//devtools/tools:typescript.bzl", "ts_test_library") -load("//devtools/tools:defaults.bzl", "karma_web_test_suite") package(default_visibility = ["//visibility:public"]) @@ -13,7 +13,7 @@ ng_module( exclude = ["flamegraph-formatter.spec.ts"], ), deps = [ - "//devtools/projects/ng-devtools/src/lib:theme", + "//devtools/projects/ng-devtools/src/lib/application-services:theme", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter", "//devtools/projects/protocol", "//packages/core", diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/flamegraph-formatter.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/flamegraph-formatter.ts index 407202f15b9..40ea39fbb9f 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/flamegraph-formatter.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/flamegraph-formatter.ts @@ -8,7 +8,7 @@ import {ElementProfile, ProfilerFrame} from 'protocol'; -import {Theme} from '../../../../../theme-service'; +import {Theme} from '../../../../../application-services/theme_service'; import {RecordFormatter} from '../record-formatter'; export interface FlamegraphNode { diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/BUILD.bazel index bbb89dbe6ba..e358d4e7cb8 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/BUILD.bazel @@ -45,7 +45,7 @@ ng_module( "execution-details.component.html", ] + _STYLE_LABELS, deps = [ - "//devtools/projects/ng-devtools/src/lib:theme", + "//devtools/projects/ng-devtools/src/lib/application-services:theme", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/bargraph-formatter", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/tree-map-formatter", diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/bargraph-visualizer.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/bargraph-visualizer.component.ts index 9d0a7c118e8..1c31cd7bc32 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/bargraph-visualizer.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/bargraph-visualizer.component.ts @@ -9,7 +9,7 @@ import {ChangeDetectionStrategy, Component, computed, inject, input, output} from '@angular/core'; import {ProfilerFrame} from 'protocol'; -import {ThemeService} from '../../../../theme-service'; +import {ThemeService} from '../../../../application-services/theme_service'; import {BarGraphFormatter, BargraphNode} from '../record-formatter/bargraph-formatter/index'; import {formatDirectiveProfile} from './profile-formatter'; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/flamegraph-visualizer.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/flamegraph-visualizer.component.ts index 8f2b1a890d2..18c236ceae4 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/flamegraph-visualizer.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/recording-visualizer/flamegraph-visualizer.component.ts @@ -10,7 +10,7 @@ import {ChangeDetectionStrategy, Component, computed, inject, input, output} fro import {Color, RawData} from 'ngx-flamegraph/lib/utils'; import {ProfilerFrame} from 'protocol'; -import {ThemeService} from '../../../../theme-service'; +import {ThemeService} from '../../../../application-services/theme_service'; import { FlamegraphFormatter, FlamegraphNode, diff --git a/devtools/projects/ng-devtools/src/lib/devtools.component.ts b/devtools/projects/ng-devtools/src/lib/devtools.component.ts index 00b800d161a..af46953631f 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools.component.ts @@ -8,17 +8,18 @@ import {animate, style, transition, trigger} from '@angular/animations'; import {Platform} from '@angular/cdk/platform'; -import {DOCUMENT} from '@angular/common'; import {Component, computed, inject, OnDestroy, OnInit, signal} from '@angular/core'; import {Events, MessageBus} from 'protocol'; import {interval} from 'rxjs'; -import {FrameManager} from './frame_manager'; -import {ThemeService} from './theme-service'; +import {FrameManager} from './application-services/frame_manager'; +import {ThemeService} from './application-services/theme_service'; import {MatTooltip, MatTooltipModule} from '@angular/material/tooltip'; import {DevToolsTabsComponent} from './devtools-tabs/devtools-tabs.component'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import {Frame} from './application-environment'; +import {BrowserStylesService} from './application-services/browser_styles_service'; +import {WINDOW_PROVIDER} from './application-providers/window_provider'; const DETECT_ANGULAR_ATTEMPTS = 10; @@ -53,6 +54,7 @@ const LAST_SUPPORTED_VERSION = 9; ]), ], imports: [DevToolsTabsComponent, MatTooltip, MatProgressSpinnerModule, MatTooltipModule], + providers: [WINDOW_PROVIDER, ThemeService], }) export class DevToolsComponent implements OnInit, OnDestroy { readonly AngularStatus = AngularStatus; @@ -74,13 +76,10 @@ export class DevToolsComponent implements OnInit, OnDestroy { return (majorVersion >= LAST_SUPPORTED_VERSION || majorVersion === 0) && this.ivy(); }); - private readonly _firefoxStyleName = 'firefox_styles.css'; - private readonly _chromeStyleName = 'chrome_styles.css'; private readonly _messageBus = inject>(MessageBus); private readonly _themeService = inject(ThemeService); - private readonly _platform = inject(Platform); - private readonly _document = inject(DOCUMENT); private readonly _frameManager = inject(FrameManager); + private readonly _browserStyles = inject(BrowserStylesService); private _interval$ = interval(500).subscribe((attempt) => { if (attempt === DETECT_ANGULAR_ATTEMPTS) { @@ -95,6 +94,7 @@ export class DevToolsComponent implements OnInit, OnDestroy { ngOnInit(): void { this._themeService.initializeThemeWatcher(); + this._browserStyles.initBrowserSpecificStyles(); this._messageBus.once('ngAvailability', ({version, devMode, ivy, hydration}) => { this.angularStatus.set(version ? AngularStatus.EXISTS : AngularStatus.DOES_NOT_EXIST); @@ -104,22 +104,6 @@ export class DevToolsComponent implements OnInit, OnDestroy { this._interval$.unsubscribe(); this.hydration.set(hydration); }); - - const browserStyleName = this._platform.FIREFOX - ? this._firefoxStyleName - : this._chromeStyleName; - this._loadStyle(browserStyleName); - } - - /** Add a style file in header based on fileName */ - private _loadStyle(styleName: string) { - const head = this._document.getElementsByTagName('head')[0]; - - const style = this._document.createElement('link'); - style.rel = 'stylesheet'; - style.href = `./styles/${styleName}`; - - head.appendChild(style); } ngOnDestroy(): void { diff --git a/devtools/projects/ng-devtools/src/lib/devtools_spec.ts b/devtools/projects/ng-devtools/src/lib/devtools_spec.ts index 6005ce7f752..c48f7e0843c 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools_spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools_spec.ts @@ -8,7 +8,7 @@ import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {FrameManager} from './frame_manager'; +import {FrameManager} from './application-services/frame_manager'; import {DevToolsComponent} from './devtools.component'; import {DevToolsTabsComponent} from './devtools-tabs/devtools-tabs.component'; import {MessageBus} from 'protocol'; diff --git a/devtools/projects/ng-devtools/src/lib/theme-service.ts b/devtools/projects/ng-devtools/src/lib/theme-service.ts deleted file mode 100644 index d7c485a547f..00000000000 --- a/devtools/projects/ng-devtools/src/lib/theme-service.ts +++ /dev/null @@ -1,42 +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 {Injectable, Renderer2, RendererFactory2, signal} from '@angular/core'; - -export type Theme = 'dark-theme' | 'light-theme'; - -@Injectable({ - providedIn: 'root', -}) -export class ThemeService { - private renderer: Renderer2; - readonly currentTheme = signal('light-theme'); - - constructor(private _rendererFactory: RendererFactory2) { - this.renderer = this._rendererFactory.createRenderer(null, null); - this.toggleDarkMode(this._prefersDarkMode); - } - - toggleDarkMode(isDark: boolean): void { - const removeClass = isDark ? 'light-theme' : 'dark-theme'; - const addClass = !isDark ? 'light-theme' : 'dark-theme'; - this.renderer.removeClass(document.body, removeClass); - this.renderer.addClass(document.body, addClass); - this.currentTheme.set(addClass); - } - - initializeThemeWatcher(): void { - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { - this.toggleDarkMode(this._prefersDarkMode); - }); - } - - private get _prefersDarkMode(): boolean { - return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; - } -} diff --git a/devtools/projects/ng-devtools/src/styles/BUILD.bazel b/devtools/projects/ng-devtools/src/styles/BUILD.bazel new file mode 100644 index 00000000000..d834265d052 --- /dev/null +++ b/devtools/projects/ng-devtools/src/styles/BUILD.bazel @@ -0,0 +1,42 @@ +load("@io_bazel_rules_sass//:defs.bzl", "npm_sass_library", "sass_binary", "sass_library") + +package(default_visibility = ["//visibility:public"]) + +npm_sass_library( + name = "material_sass_deps", + deps = [ + "@npm//@angular/cdk", + "@npm//@angular/material", + ], +) + +sass_library( + name = "global", + srcs = ["_global.scss"], + deps = [":material_sass_deps"], +) + +sass_library( + name = "theme", + srcs = ["_theme.scss"], +) + +sass_library( + name = "browser", + srcs = ["_browser.scss"], +) + +sass_library( + name = "colors", + srcs = ["_colors.scss"], +) + +sass_binary( + name = "firefox", + src = "firefox.scss", +) + +sass_binary( + name = "chrome", + src = "chrome.scss", +) diff --git a/devtools/projects/ng-devtools/src/styles/_browser.scss b/devtools/projects/ng-devtools/src/styles/_browser.scss new file mode 100644 index 00000000000..98a5963424b --- /dev/null +++ b/devtools/projects/ng-devtools/src/styles/_browser.scss @@ -0,0 +1,15 @@ +/* Keep class names in sync with ThemeService */ + +/* Default. Target Chrome */ +@mixin chrome { + :host-context(.chrome-ui) { + @content; + } +} + +/* Target Firefox */ +@mixin firefox { + :host-context(.firefox-ui) { + @content; + } +} diff --git a/devtools/projects/ng-devtools/src/styles/_colors.scss b/devtools/projects/ng-devtools/src/styles/_colors.scss new file mode 100644 index 00000000000..334e90df56d --- /dev/null +++ b/devtools/projects/ng-devtools/src/styles/_colors.scss @@ -0,0 +1 @@ +$dummy: #f00; diff --git a/devtools/styles.scss b/devtools/projects/ng-devtools/src/styles/_global.scss similarity index 88% rename from devtools/styles.scss rename to devtools/projects/ng-devtools/src/styles/_global.scss index c6c789e2cce..5a74158c7aa 100644 --- a/devtools/styles.scss +++ b/devtools/projects/ng-devtools/src/styles/_global.scss @@ -1,3 +1,5 @@ +/* Global styles imported in the main styles.scss of the app wrapper (shell, demo apps). */ + @use 'sass:map'; @use 'external/npm/node_modules/@angular/material/index' as mat; @include mat.all-component-typographies(); @@ -39,6 +41,7 @@ $dark-theme: map.deep-merge( ) ); +/* Keep class name in sync with ThemeService */ .light-theme { @include mat.all-component-themes($light-theme); @@ -47,6 +50,7 @@ $dark-theme: map.deep-merge( } } +/* Keep class name in sync with ThemeService */ .dark-theme { color-scheme: dark; background: #202124; diff --git a/devtools/projects/ng-devtools/src/styles/_theme.scss b/devtools/projects/ng-devtools/src/styles/_theme.scss new file mode 100644 index 00000000000..ce075843c2d --- /dev/null +++ b/devtools/projects/ng-devtools/src/styles/_theme.scss @@ -0,0 +1,30 @@ +/* Keep class names in sync with ThemeService */ +$_dark-theme-class: 'dark-theme'; +$_light-theme-class: 'light-theme'; + +/* $browser expects 'chrome', 'firefox' or 'safari' */ +@mixin _theme($theme, $browser: '') { + @if $browser == '' { + :host-context(body.#{$theme}) { + @content; + } + } @else { + /* Keep browser class name in sync with BrowserService. */ + $browser-class: $browser + '-ui'; + :host-context(body.#{$theme}.#{$browser-class}) { + @content; + } + } +} + +@mixin dark-theme($browser: '') { + @include _theme($_dark-theme-class, $browser) { + @content; + } +} + +@mixin light-theme($browser: '') { + @include _theme($_light-theme-class, $browser) { + @content; + } +} diff --git a/devtools/projects/ng-devtools/src/styles/chrome.scss b/devtools/projects/ng-devtools/src/styles/chrome.scss new file mode 100644 index 00000000000..8b1524d5c5b --- /dev/null +++ b/devtools/projects/ng-devtools/src/styles/chrome.scss @@ -0,0 +1 @@ +/* Chrome-specific global styles */ diff --git a/devtools/projects/ng-devtools/src/styles/firefox.scss b/devtools/projects/ng-devtools/src/styles/firefox.scss new file mode 100644 index 00000000000..a338892ff93 --- /dev/null +++ b/devtools/projects/ng-devtools/src/styles/firefox.scss @@ -0,0 +1 @@ +/* Firefox-specific global styles */ diff --git a/devtools/projects/shell-browser/src/BUILD.bazel b/devtools/projects/shell-browser/src/BUILD.bazel index 9c946c6476c..2250080a714 100644 --- a/devtools/projects/shell-browser/src/BUILD.bazel +++ b/devtools/projects/shell-browser/src/BUILD.bazel @@ -1,10 +1,11 @@ -load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") +load("@bazel_skylib//rules:common_settings.bzl", "string_flag") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_web") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") load("//devtools/tools:ng_module.bzl", "ng_module") load("//devtools/tools:typescript.bzl", "ts_library") -load("//tools:defaults.bzl", "esbuild") load("//devtools/tools/esbuild:index.bzl", "LINKER_PROCESSED_FW_PACKAGES") -load("@bazel_skylib//rules:common_settings.bzl", "string_flag") +load("//tools:defaults.bzl", "esbuild") package(default_visibility = ["//visibility:public"]) @@ -13,17 +14,19 @@ sass_binary( src = "styles.scss", include_paths = ["external/npm/node_modules"], sourcemap = False, - deps = ["//devtools:global_styles"], + deps = ["//devtools/projects/ng-devtools/src/styles:global"], ) -sass_binary( - name = "shell_firefox_styles", - src = "styles/firefox_styles.scss", -) - -sass_binary( - name = "shell_chrome_styles", - src = "styles/chrome_styles.scss", +copy_to_directory( + name = "browser_specific_styles", + srcs = [ + "//devtools/projects/ng-devtools/src/styles:chrome", + "//devtools/projects/ng-devtools/src/styles:firefox", + ], + out = "styles", + replace_prefixes = { + "devtools/projects/ng-devtools/src/styles": "", + }, ) ts_library( @@ -95,10 +98,9 @@ exports_files(["index.html"]) filegroup( name = "prod_app_static_files", srcs = [ + ":browser_specific_styles", ":index.html", - ":shell_chrome_styles", ":shell_common_styles", - ":shell_firefox_styles", "//packages/zone.js/bundles:zone.umd.js", ], ) diff --git a/devtools/projects/shell-browser/src/app/app.config.ts b/devtools/projects/shell-browser/src/app/app.config.ts index a0b6673e9ea..d5c37fda24c 100644 --- a/devtools/projects/shell-browser/src/app/app.config.ts +++ b/devtools/projects/shell-browser/src/app/app.config.ts @@ -14,7 +14,7 @@ import {ChromeApplicationEnvironment} from './chrome-application-environment'; import {ChromeApplicationOperations} from './chrome-application-operations'; import {ZoneAwareChromeMessageBus} from './zone-aware-chrome-message-bus'; import {Events, MessageBus, PriorityAwareMessageBus} from 'protocol'; -import {FrameManager} from '../../../ng-devtools/src/lib/frame_manager'; +import {FrameManager} from '../../../ng-devtools/src/lib/application-services/frame_manager'; export const appConfig: ApplicationConfig = { providers: [ diff --git a/devtools/projects/shell-browser/src/styles.scss b/devtools/projects/shell-browser/src/styles.scss index 7bc3816635c..3c0d3a12759 100644 --- a/devtools/projects/shell-browser/src/styles.scss +++ b/devtools/projects/shell-browser/src/styles.scss @@ -1,4 +1,4 @@ -@use '../../../styles.scss' as devtools; +@use '../../../projects/ng-devtools/src/styles/global'; body, html { diff --git a/devtools/projects/shell-browser/src/styles/chrome_styles.scss b/devtools/projects/shell-browser/src/styles/chrome_styles.scss deleted file mode 100644 index 9959545b62f..00000000000 --- a/devtools/projects/shell-browser/src/styles/chrome_styles.scss +++ /dev/null @@ -1 +0,0 @@ -/** Specific style for Chrome browser */ diff --git a/devtools/projects/shell-browser/src/styles/firefox_styles.scss b/devtools/projects/shell-browser/src/styles/firefox_styles.scss deleted file mode 100644 index af7608ad155..00000000000 --- a/devtools/projects/shell-browser/src/styles/firefox_styles.scss +++ /dev/null @@ -1 +0,0 @@ -/** Specific style for Firefox browser */ diff --git a/devtools/src/BUILD.bazel b/devtools/src/BUILD.bazel index ccca6207b5b..f5ba71ab6b1 100644 --- a/devtools/src/BUILD.bazel +++ b/devtools/src/BUILD.bazel @@ -1,8 +1,9 @@ -load("//devtools/tools:ng_module.bzl", "ng_module") -load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") load("@build_bazel_rules_nodejs//:index.bzl", "pkg_web") -load("//tools:defaults.bzl", "esbuild", "http_server") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") +load("//devtools/tools:ng_module.bzl", "ng_module") load("//devtools/tools/esbuild:index.bzl", "LINKER_PROCESSED_FW_PACKAGES") +load("//tools:defaults.bzl", "esbuild", "http_server") package(default_visibility = ["//visibility:public"]) @@ -11,17 +12,19 @@ sass_binary( src = "styles.scss", include_paths = ["external/npm/node_modules"], sourcemap = False, - deps = ["//devtools:global_styles"], + deps = ["//devtools/projects/ng-devtools/src/styles:global"], ) -sass_binary( - name = "firefox_styles", - src = "styles/firefox_styles.scss", -) - -sass_binary( - name = "chrome_styles", - src = "styles/chrome_styles.scss", +copy_to_directory( + name = "browser_specific_styles", + srcs = [ + "//devtools/projects/ng-devtools/src/styles:chrome", + "//devtools/projects/ng-devtools/src/styles:firefox", + ], + out = "styles", + replace_prefixes = { + "devtools/projects/ng-devtools/src/styles": "", + }, ) ng_module( @@ -59,9 +62,8 @@ exports_files(["index.html"]) filegroup( name = "dev_app_static_files", srcs = [ - ":chrome_styles", + ":browser_specific_styles", ":demo_styles", - ":firefox_styles", ":index.html", "//packages/zone.js/bundles:zone.umd.js", ], diff --git a/devtools/src/app/devtools-app/BUILD.bazel b/devtools/src/app/devtools-app/BUILD.bazel index 9f5cb5689d7..67fecc3ec09 100644 --- a/devtools/src/app/devtools-app/BUILD.bazel +++ b/devtools/src/app/devtools-app/BUILD.bazel @@ -20,7 +20,7 @@ ng_module( ], deps = [ "//devtools/projects/ng-devtools", - "//devtools/projects/ng-devtools/src/lib:frame_manager", + "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", "//devtools/projects/protocol", "//devtools/src:iframe_message_bus", "//packages/common", diff --git a/devtools/src/app/devtools-app/devtools-app.routes.ts b/devtools/src/app/devtools-app/devtools-app.routes.ts index 9f4e03d7887..8ef8b847e3b 100644 --- a/devtools/src/app/devtools-app/devtools-app.routes.ts +++ b/devtools/src/app/devtools-app/devtools-app.routes.ts @@ -9,7 +9,7 @@ import {Routes} from '@angular/router'; import {AppDevToolsComponent} from './devtools-app.component'; -import {FrameManager} from '../../../projects/ng-devtools/src/lib/frame_manager'; +import {FrameManager} from '../../../projects/ng-devtools/src/lib/application-services/frame_manager'; import {Events, MessageBus, PriorityAwareMessageBus} from 'protocol'; import {IFrameMessageBus} from '../../iframe-message-bus'; diff --git a/devtools/src/styles.scss b/devtools/src/styles.scss index a8c02e791f0..7ce8097f55e 100644 --- a/devtools/src/styles.scss +++ b/devtools/src/styles.scss @@ -1 +1 @@ -@use '../styles.scss' as devtools; +@use '../projects/ng-devtools/src/styles/global'; diff --git a/devtools/src/styles/chrome_styles.scss b/devtools/src/styles/chrome_styles.scss deleted file mode 100644 index 9959545b62f..00000000000 --- a/devtools/src/styles/chrome_styles.scss +++ /dev/null @@ -1 +0,0 @@ -/** Specific style for Chrome browser */ diff --git a/devtools/src/styles/firefox_styles.scss b/devtools/src/styles/firefox_styles.scss deleted file mode 100644 index af7608ad155..00000000000 --- a/devtools/src/styles/firefox_styles.scss +++ /dev/null @@ -1 +0,0 @@ -/** Specific style for Firefox browser */