// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'chrome://customize-chrome-side-panel.top-chrome/shared/sp_heading.js';
import 'chrome://resources/cr_components/help_bubble/new_badge.js';
import 'chrome://resources/cr_elements/cr_chip/cr_chip.js';
import 'chrome://resources/cr_elements/cr_icon/cr_icon.js';
import 'chrome://resources/cr_elements/cr_page_selector/cr_page_selector.js';
import 'chrome://resources/cr_elements/icons_lit.html.js';
import './appearance.js';
import './cards.js';
import './categories.js';
import './customize_toolbar/toolbar.js';
import './shortcuts.js';
import './themes.js';
import './wallpaper_search/wallpaper_search.js';
import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
import {HelpBubbleMixinLit} from 'chrome://resources/cr_components/help_bubble/help_bubble_mixin_lit.js';
import {assert} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
import {getCss} from './app.css.js';
import {getHtml} from './app.html.js';
import type {AppearanceElement} from './appearance.js';
import type {CategoriesElement} from './categories.js';
import {CustomizeChromeImpression, recordCustomizeChromeImpression} from './common.js';
import type {BackgroundCollection, CustomizeChromePageHandlerInterface} from './customize_chrome.mojom-webui.js';
import {ChromeWebStoreCategory, ChromeWebStoreCollection, CustomizeChromeSection} from './customize_chrome.mojom-webui.js';
import {CustomizeChromeApiProxy} from './customize_chrome_api_proxy.js';
import type {ThemesElement} from './themes.js';
const SECTION_TO_SELECTOR = {
[CustomizeChromeSection.kAppearance]: '#appearance',
[CustomizeChromeSection.kShortcuts]: '#shortcuts',
[CustomizeChromeSection.kModules]: '#modules',
};
const CHANGE_CHROME_THEME_BUTTON_ELEMENT_ID =
'CustomizeChromeUI::kChangeChromeThemeButtonElementId';
export enum CustomizeChromePage {
OVERVIEW = 'overview',
CATEGORIES = 'categories',
THEMES = 'themes',
TOOLBAR = 'toolbar',
WALLPAPER_SEARCH = 'wallpaper-search',
}
const AppElementBase = HelpBubbleMixinLit(CrLitElement);
export interface AppElement {
$: {
overviewPage: HTMLDivElement,
categoriesPage: CategoriesElement,
themesPage: ThemesElement,
appearanceElement: AppearanceElement,
};
}
export class AppElement extends AppElementBase {
static get is() {
return 'customize-chrome-app';
}
static override get styles() {
return getCss();
}
override render() {
return getHtml.bind(this)();
}
static override get properties() {
return {
page_: {type: String},
modulesEnabled_: {type: Boolean},
selectedCollection_: {type: Object},
extensionsCardEnabled_: {type: Boolean},
wallpaperSearchEnabled_: {type: Boolean},
toolbarCustomizationEnabled_: {type: Boolean},
isSourceTabFirstPartyNtp_: {type: Boolean},
};
}
override firstUpdated() {
ColorChangeUpdater.forDocument().start();
this.registerHelpBubble(
CHANGE_CHROME_THEME_BUTTON_ELEMENT_ID,
['#appearanceElement', '#editThemeButton']);
}
protected page_: CustomizeChromePage = CustomizeChromePage.OVERVIEW;
protected modulesEnabled_: boolean =
loadTimeData.getBoolean('modulesEnabled');
protected selectedCollection_: BackgroundCollection|null = null;
protected extensionsCardEnabled_: boolean =
loadTimeData.getBoolean('extensionsCardEnabled');
protected wallpaperSearchEnabled_: boolean =
loadTimeData.getBoolean('wallpaperSearchEnabled');
protected toolbarCustomizationEnabled_: boolean =
loadTimeData.getBoolean('toolbarCustomizationEnabled');
protected isSourceTabFirstPartyNtp_: boolean = true;
private scrollToSectionListenerId_: number|null = null;
private attachedTabStateUpdatedId_: number|null = null;
private pageHandler_: CustomizeChromePageHandlerInterface =
CustomizeChromeApiProxy.getInstance().handler;
override connectedCallback() {
super.connectedCallback();
this.scrollToSectionListenerId_ =
CustomizeChromeApiProxy.getInstance()
.callbackRouter.scrollToSection.addListener(
(section: CustomizeChromeSection) => {
if (section === CustomizeChromeSection.kWallpaperSearch) {
this.onWallpaperSearchSelect_();
return;
} else if (section === CustomizeChromeSection.kToolbar) {
this.openToolbarCustomizationPage();
chrome.metricsPrivate.recordUserAction(
'Actions.CustomizeToolbarSidePanel' +
'.OpenedFromOutsideCustomizeChrome');
return;
}
const selector = SECTION_TO_SELECTOR[section];
const element = this.shadowRoot!.querySelector(selector);
if (!element) {
return;
}
this.page_ = CustomizeChromePage.OVERVIEW;
element.scrollIntoView({behavior: 'auto'});
});
this.attachedTabStateUpdatedId_ =
CustomizeChromeApiProxy.getInstance()
.callbackRouter.attachedTabStateUpdated.addListener(
(isSourceTabFirstPartyNtp: boolean) => {
if (this.isSourceTabFirstPartyNtp_ ===
isSourceTabFirstPartyNtp) {
return;
}
this.isSourceTabFirstPartyNtp_ = isSourceTabFirstPartyNtp;
// Since some pages aren't supported in non first party mode,
// change the section back to the overview.
if (!this.isSourceTabFirstPartyNtp_ &&
!this.pageSupportedOnNonFirstPartyNtps()) {
this.page_ = CustomizeChromePage.OVERVIEW;
}
});
this.pageHandler_.updateAttachedTabState();
// We wait for load because `scrollIntoView` above requires the page to be
// laid out.
window.addEventListener('load', () => {
CustomizeChromeApiProxy.getInstance().handler.updateScrollToSection();
// Install observer to log extension cards impression.
const extensionsCardSectionObserver =
new IntersectionObserver(entries => {
assert(entries.length >= 1);
if (entries[0]!.intersectionRatio >= 0.8) {
extensionsCardSectionObserver.disconnect();
this.dispatchEvent(
new Event('detect-extensions-card-section-impression'));
recordCustomizeChromeImpression(
CustomizeChromeImpression.EXTENSIONS_CARD_SECTION_DISPLAYED);
}
}, {
threshold: 1.0,
});
// Start observing if extension cards are scroll into view.
if (this.shadowRoot && this.shadowRoot.querySelector('#extensions')) {
extensionsCardSectionObserver.observe(
this.shadowRoot!.querySelector('#extensions')!);
}
}, {once: true});
}
override disconnectedCallback() {
super.disconnectedCallback();
assert(this.scrollToSectionListenerId_);
CustomizeChromeApiProxy.getInstance().callbackRouter.removeListener(
this.scrollToSectionListenerId_);
assert(this.attachedTabStateUpdatedId_);
CustomizeChromeApiProxy.getInstance().callbackRouter.removeListener(
this.attachedTabStateUpdatedId_);
}
protected async onBackClick_() {
switch (this.page_) {
case CustomizeChromePage.CATEGORIES:
case CustomizeChromePage.TOOLBAR:
this.page_ = CustomizeChromePage.OVERVIEW;
await this.updateComplete;
this.$.appearanceElement.focusOnThemeButton();
break;
case CustomizeChromePage.THEMES:
case CustomizeChromePage.WALLPAPER_SEARCH:
this.page_ = CustomizeChromePage.CATEGORIES;
await this.updateComplete;
this.$.categoriesPage.focusOnBackButton();
break;
}
}
protected async onEditThemeClick_() {
this.page_ = CustomizeChromePage.CATEGORIES;
await this.updateComplete;
this.$.categoriesPage.focusOnBackButton();
}
protected async onCollectionSelect_(event:
CustomEvent<BackgroundCollection>) {
this.selectedCollection_ = event.detail;
this.page_ = CustomizeChromePage.THEMES;
await this.updateComplete;
this.$.themesPage.focusOnBackButton();
}
protected async onLocalImageUpload_() {
this.page_ = CustomizeChromePage.OVERVIEW;
await this.updateComplete;
this.$.appearanceElement.focusOnThemeButton();
}
protected onWallpaperSearchSelect_() {
this.page_ = CustomizeChromePage.WALLPAPER_SEARCH;
const page =
this.shadowRoot!.querySelector('customize-chrome-wallpaper-search');
assert(page);
page.focusOnBackButton();
}
protected onCouponsButtonClick_() {
this.pageHandler_.openChromeWebStoreCategoryPage(
ChromeWebStoreCategory.kShopping);
}
protected onWritingButtonClick_() {
this.pageHandler_.openChromeWebStoreCollectionPage(
ChromeWebStoreCollection.kWritingEssentials);
}
protected onProductivityButtonClick_() {
this.pageHandler_.openChromeWebStoreCategoryPage(
ChromeWebStoreCategory.kWorkflowPlanning);
}
protected onChromeWebStoreLinkClick_(e: Event) {
if ((e.target as HTMLElement).id !== 'chromeWebstoreLink') {
// Ignore any clicks that are not directly on the <a> element itself. Note
// that the <a> element is part of a localized string, which is why the
// listener is added on the parent DOM node.
return;
}
this.pageHandler_.openChromeWebStoreHomePage();
}
protected onToolbarCustomizationButtonClick_() {
this.openToolbarCustomizationPage();
chrome.metricsPrivate.recordUserAction(
'Actions.CustomizeToolbarSidePanel.OpenedFromCustomizeChrome');
}
private async openToolbarCustomizationPage() {
this.page_ = CustomizeChromePage.TOOLBAR;
const page = this.shadowRoot!.querySelector('customize-chrome-toolbar');
assert(page);
await this.updateComplete;
page.focusOnBackButton();
}
private pageSupportedOnNonFirstPartyNtps() {
return this.page_ === CustomizeChromePage.TOOLBAR;
}
}
declare global {
interface HTMLElementTagNameMap {
'customize-chrome-app': AppElement;
}
}
customElements.define(AppElement.is, AppElement);