chromium/chrome/browser/resources/side_panel/customize_chrome/appearance.ts

// 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 './theme_snapshot.js';
import './hover_button.js';
import './strings.m.js'; // Required by <managed-dialog>.
import 'chrome://resources/cr_components/customize_color_scheme_mode/customize_color_scheme_mode.js';
import 'chrome://resources/cr_components/theme_color_picker/theme_color_picker.js';
import 'chrome://resources/cr_components/managed_dialog/managed_dialog.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';

import type {CrA11yAnnouncerElement} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
import type {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
import {I18nMixinLit} from 'chrome://resources/cr_elements/i18n_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 type {PropertyValues} from 'chrome://resources/lit/v3_0/lit.rollup.js';

import {getCss} from './appearance.css.js';
import {getHtml} from './appearance.html.js';
import {CustomizeChromeAction, recordCustomizeChromeAction} from './common.js';
import type {CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerInterface, Theme} from './customize_chrome.mojom-webui.js';
import {CustomizeChromeApiProxy} from './customize_chrome_api_proxy.js';

export interface AppearanceElement {
  $: {
    chromeColors: HTMLElement,
    editThemeButton: HTMLButtonElement,
    themeSnapshot: HTMLElement,
    setClassicChromeButton: HTMLButtonElement,
    thirdPartyThemeLinkButton: HTMLButtonElement,
    followThemeToggle: HTMLElement,
    followThemeToggleControl: CrToggleElement,
    uploadedImageButton: HTMLButtonElement,
    searchedImageButton: HTMLButtonElement,
  };
}

const AppearanceElementBase = I18nMixinLit(CrLitElement);

export class AppearanceElement extends AppearanceElementBase {
  static get is() {
    return 'customize-chrome-appearance';
  }

  static override get styles() {
    return getCss();
  }

  override render() {
    return getHtml.bind(this)();
  }

  static override get properties() {
    return {
      theme_: {type: Object},
      editThemeButtonText_: {type: String},

      thirdPartyThemeId_: {
        type: String,
        reflect: true,
      },

      thirdPartyThemeName_: {
        type: String,
        reflect: true,
      },

      showBottomDivider_: {type: Boolean},
      showClassicChromeButton_: {type: Boolean},
      showColorPicker_: {type: Boolean},
      showDeviceThemeToggle_: {type: Boolean},
      showThemeSnapshot_: {type: Boolean},
      showUploadedImageButton_: {type: Boolean},
      showSearchedImageButton_: {type: Boolean},
      showManagedDialog_: {type: Boolean},
      isSourceTabFirstPartyNtp_: {type: Boolean},

      wallpaperSearchButtonEnabled_: {
        type: Boolean,
        reflect: true,
      },

      wallpaperSearchEnabled_: {type: Boolean},
    };
  }

  protected theme_?: Theme;
  protected editThemeButtonText_: string = '';
  protected thirdPartyThemeId_: string|null = null;
  protected thirdPartyThemeName_: string|null = null;
  protected showBottomDivider_: boolean = false;
  protected showClassicChromeButton_: boolean = false;
  protected showColorPicker_: boolean = false;
  protected showDeviceThemeToggle_: boolean = false;
  protected showThemeSnapshot_: boolean = false;
  protected showUploadedImageButton_: boolean = false;
  protected showSearchedImageButton_: boolean = false;
  protected showManagedDialog_: boolean = false;
  protected wallpaperSearchButtonEnabled_: boolean =
      loadTimeData.getBoolean('wallpaperSearchButtonEnabled');
  private wallpaperSearchEnabled_: boolean =
      loadTimeData.getBoolean('wallpaperSearchEnabled');
  protected isSourceTabFirstPartyNtp_: boolean = true;
  protected ntpManagedByName_: string = '';
  private setThemeListenerId_: number|null = null;
  private attachedTabStateUpdatedId_: number|null = null;
  private ntpManagedByNameUpdatedId_: number|null = null;

  private callbackRouter_: CustomizeChromePageCallbackRouter;
  private pageHandler_: CustomizeChromePageHandlerInterface;


  constructor() {
    super();
    this.pageHandler_ = CustomizeChromeApiProxy.getInstance().handler;
    this.callbackRouter_ = CustomizeChromeApiProxy.getInstance().callbackRouter;
  }

  override connectedCallback() {
    super.connectedCallback();
    this.setThemeListenerId_ =
        this.callbackRouter_.setTheme.addListener((theme: Theme) => {
          this.theme_ = theme;
        });
    this.pageHandler_.updateTheme();

    this.attachedTabStateUpdatedId_ =
        CustomizeChromeApiProxy.getInstance()
            .callbackRouter.attachedTabStateUpdated.addListener(
                (isSourceTabFirstPartyNtp: boolean) => {
                  this.isSourceTabFirstPartyNtp_ = isSourceTabFirstPartyNtp;
                });
    this.pageHandler_.updateAttachedTabState();

    this.ntpManagedByNameUpdatedId_ =
        CustomizeChromeApiProxy.getInstance()
            .callbackRouter.ntpManagedByNameUpdated.addListener(
                (ntpManagedByName: string) => {
                  this.ntpManagedByName_ = ntpManagedByName;
                });
    this.pageHandler_.updateNtpManagedByName();
  }

  override disconnectedCallback() {
    super.disconnectedCallback();
    assert(this.setThemeListenerId_);
    this.callbackRouter_.removeListener(this.setThemeListenerId_);

    assert(this.attachedTabStateUpdatedId_);
    CustomizeChromeApiProxy.getInstance().callbackRouter.removeListener(
        this.attachedTabStateUpdatedId_);

    assert(this.ntpManagedByNameUpdatedId_);
    CustomizeChromeApiProxy.getInstance().callbackRouter.removeListener(
        this.ntpManagedByNameUpdatedId_);
  }

  override willUpdate(changedProperties: PropertyValues<this>) {
    super.willUpdate(changedProperties);

    const changedPrivateProperties =
        changedProperties as Map<PropertyKey, unknown>;

    this.editThemeButtonText_ = this.computeEditThemeButtonText_();

    if (changedPrivateProperties.has('theme_') ||
        changedPrivateProperties.has('isSourceTabFirstPartyNtp_')) {
      this.thirdPartyThemeId_ = this.computeThirdPartyThemeId_();
      this.thirdPartyThemeName_ = this.computeThirdPartyThemeName_();
      this.showClassicChromeButton_ = this.computeShowClassicChromeButton_();
      this.showColorPicker_ = this.computeShowColorPicker_();
      this.showDeviceThemeToggle_ = this.computeShowDeviceThemeToggle_();
      this.showThemeSnapshot_ = this.computeShowThemeSnapshot_();
      this.showUploadedImageButton_ = this.computeShowUploadedImageButton_();
      this.showSearchedImageButton_ = this.computeShowSearchedImageButton_();
    }

    this.showBottomDivider_ = this.computeShowBottomDivider_();

    // Announce when theme is set to Classic Chrome.
    // This should only be triggered if the classic chrome's button is hidden
    // after the initial theme value has already been set.
    if (changedPrivateProperties.has('theme_') &&
        changedPrivateProperties.has('showClassicChromeButton_') &&
        !!changedPrivateProperties.get('theme_') &&
        !this.showClassicChromeButton_) {
      const announcer = getAnnouncerInstance() as CrA11yAnnouncerElement;
      announcer.announce(this.i18n('updatedToClassicChrome'));
      // If the classicChrome button has focus, change focus to editTheme
      // button, since the button is disappearing.
      if (this.shadowRoot!.activeElement === this.$.setClassicChromeButton) {
        this.focusOnThemeButton();
      }
    }
  }

  focusOnThemeButton() {
    this.$.editThemeButton.focus();
  }

  private computeEditThemeButtonText_(): string {
    return this.i18n(
        this.wallpaperSearchButtonEnabled_ ? 'categoriesHeader' :
                                             'changeTheme');
  }

  private computeThirdPartyThemeId_(): string|null {
    if (this.theme_ && this.theme_.thirdPartyThemeInfo) {
      return this.theme_.thirdPartyThemeInfo.id;
    } else {
      return null;
    }
  }

  private computeThirdPartyThemeName_(): string|null {
    if (this.theme_ && this.theme_.thirdPartyThemeInfo) {
      return this.theme_.thirdPartyThemeInfo.name;
    } else {
      return null;
    }
  }

  private computeShowBottomDivider_(): boolean {
    return !!(this.showClassicChromeButton_ || this.showDeviceThemeToggle_);
  }

  private computeShowClassicChromeButton_(): boolean {
    return !!(
        this.theme_ &&
        (this.theme_.backgroundImage || this.theme_.thirdPartyThemeInfo));
  }

  private computeShowColorPicker_(): boolean {
    return !!this.theme_ && !this.theme_.thirdPartyThemeInfo;
  }

  private computeShowDeviceThemeToggle_(): boolean {
    return loadTimeData.getBoolean('showDeviceThemeToggle') &&
        !(!!this.theme_ && !!this.theme_.thirdPartyThemeInfo);
  }

  private computeShowThemeSnapshot_(): boolean {
    return !!this.theme_ && !this.theme_.thirdPartyThemeInfo &&
        (!(this.theme_.backgroundImage &&
           this.theme_.backgroundImage.isUploadedImage)) &&
        this.isSourceTabFirstPartyNtp_;
  }

  private computeShowUploadedImageButton_(): boolean {
    return !!(
        this.theme_ && this.theme_.backgroundImage &&
        this.theme_.backgroundImage.isUploadedImage &&
        !this.theme_.backgroundImage.localBackgroundId);
  }

  private computeShowSearchedImageButton_(): boolean {
    return !!(
        this.theme_ && this.theme_.backgroundImage &&
        this.theme_.backgroundImage.localBackgroundId);
  }

  protected onEditThemeClicked_() {
    recordCustomizeChromeAction(CustomizeChromeAction.EDIT_THEME_CLICKED);
    if (this.handleClickForManagedThemes_()) {
      return;
    }
    this.dispatchEvent(new Event('edit-theme-click'));
  }

  protected onWallpaperSearchClicked_() {
    recordCustomizeChromeAction(
        CustomizeChromeAction.WALLPAPER_SEARCH_APPEARANCE_BUTTON_CLICKED);
    if (this.handleClickForManagedThemes_()) {
      return;
    }
    this.dispatchEvent(new Event('wallpaper-search-click'));
  }

  protected onThirdPartyThemeLinkButtonClick_() {
    if (this.thirdPartyThemeId_) {
      this.pageHandler_.openThirdPartyThemePage(this.thirdPartyThemeId_);
    }
  }

  protected onUploadedImageButtonClick_() {
    this.pageHandler_.chooseLocalCustomBackground();
  }

  protected onSearchedImageButtonClick_() {
    if (this.wallpaperSearchEnabled_) {
      this.dispatchEvent(new CustomEvent('wallpaper-search-click'));
    } else {
      this.dispatchEvent(new Event('edit-theme-click'));
    }
  }

  protected onSetClassicChromeClicked_() {
    if (this.handleClickForManagedThemes_()) {
      return;
    }
    this.pageHandler_.removeBackgroundImage();
    this.pageHandler_.setDefaultColor();
    recordCustomizeChromeAction(
        CustomizeChromeAction.SET_CLASSIC_CHROME_THEME_CLICKED);
  }

  protected onFollowThemeToggleChange_(e: CustomEvent<boolean>) {
    this.pageHandler_.setFollowDeviceTheme(e.detail);
  }

  protected onManagedDialogClosed_() {
    this.showManagedDialog_ = false;
  }

  protected onNewTabPageManageByButtonClicked_() {
    this.pageHandler_.openNtpManagedByPage();
  }

  private handleClickForManagedThemes_(): boolean {
    if (!this.theme_ || !this.theme_.backgroundManagedByPolicy) {
      return false;
    }
    this.showManagedDialog_ = true;
    return true;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'customize-chrome-appearance': AppearanceElement;
  }
}

customElements.define(AppearanceElement.is, AppearanceElement);