chromium/chrome/browser/resources/internals/user_education/user_education_internals.ts

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../strings.m.js';
import 'chrome://resources/cr_components/help_bubble/help_bubble.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/cr_elements/cr_expand_button/cr_expand_button.js';
import 'chrome://resources/cr_elements/cr_menu_selector/cr_menu_selector.js';
import 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.js';
import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
import '//resources/cr_elements/icons_lit.html.js';
import './user_education_internals_card.js';
import './user_education_whats_new_internals_card.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 {CrContainerShadowMixinLit} from 'chrome://resources/cr_elements/cr_container_shadow_mixin_lit.js';
import type {CrMenuSelector} from 'chrome://resources/cr_elements/cr_menu_selector/cr_menu_selector.js';
import type {CrToastElement} from 'chrome://resources/cr_elements/cr_toast/cr_toast.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 './user_education_internals.css.js';
import {getHtml} from './user_education_internals.html.js';
import type {FeaturePromoDemoPageData, FeaturePromoDemoPageInfo, UserEducationInternalsPageHandlerInterface, WhatsNewEditionDemoPageInfo, WhatsNewModuleDemoPageInfo} from './user_education_internals.mojom-webui.js';
import {UserEducationInternalsPageHandler} from './user_education_internals.mojom-webui.js';

export interface UserEducationInternalsElement {
  $: {
    content: HTMLElement,
    errorMessageToast: CrToastElement,
    menu: CrMenuSelector,
  };
}

const UserEducationInternalsElementBase =
    CrContainerShadowMixinLit(HelpBubbleMixinLit(CrLitElement));

export class UserEducationInternalsElement extends
    UserEducationInternalsElementBase {
  static get is() {
    return 'user-education-internals';
  }

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

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

  static override get properties() {
    return {
      /**
       * Substring filter that (when set) shows only entries containing
       * `filter`.
       */
      filter: {type: String},
      /**
       * List of tutorials and feature_promos that can be started.
       * Each tutorial has a string identifier.
       */
      tutorials_: {type: Array},
      featurePromos_: {type: Array},
      featurePromoErrorMessage_: {type: String},
      narrow_: {type: Boolean},

      /**
       * Indicates if the information about session data is expanded or
       * collapsed.
       */
      sessionExpanded_: {
        type: Boolean,
        value: false,
      },
    };
  }

  filter: string = '';
  protected isWhatsNewV2_: boolean = loadTimeData.getBoolean('isWhatsNewV2');
  protected tutorials_: FeaturePromoDemoPageInfo[] = [];
  protected featurePromos_: FeaturePromoDemoPageInfo[] = [];
  protected newBadges_: FeaturePromoDemoPageInfo[] = [];
  protected whatsNewModules_: WhatsNewModuleDemoPageInfo[] = [];
  protected whatsNewEditions_: WhatsNewEditionDemoPageInfo[] = [];
  protected featurePromoErrorMessage_: string = '';
  protected narrow_: boolean = false;
  protected sessionExpanded_: boolean = false;
  protected sessionData_: FeaturePromoDemoPageData[] = [];

  private handler_: UserEducationInternalsPageHandlerInterface;

  constructor() {
    super();
    this.handler_ = UserEducationInternalsPageHandler.getRemote();
  }

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

    // There is a self-referential demo IPH for showing a help bubble in a
    // WebUI (specifically, this WebUI). Because of that, the target anchor for
    // the help bubble needs to be registered.
    //
    // However, because we want to attach the help bubble to an element
    // dynamically created, we have to wait until after the element is
    // populated to register the anchor element.
    if (changedProperties.has('featurePromos_')) {
      if (this.shadowRoot!.querySelector('#IPH_WebUiHelpBubbleTest')) {
        this.registerHelpBubble(
            'kWebUIIPHDemoElementIdentifier',
            ['#IPH_WebUiHelpBubbleTest', '#launch']);
      }
    }
  }

  override firstUpdated() {
    ColorChangeUpdater.forDocument().start();

    this.handler_.getTutorials().then(({tutorialInfos}) => {
      this.tutorials_ = tutorialInfos;
    });

    this.handler_.getSessionData().then(({sessionData}) => {
      this.sessionData_ = sessionData;
    });

    this.handler_.getFeaturePromos().then(({featurePromos}) => {
      this.featurePromos_ = featurePromos;
    });

    this.handler_.getNewBadges().then(({newBadges}) => {
      this.newBadges_ = newBadges;
    });

    if (this.isWhatsNewV2_) {
      this.handler_.getWhatsNewModules().then(({whatsNewModules}) => {
        this.whatsNewModules_ = whatsNewModules;
      });
      this.handler_.getWhatsNewEditions().then(({whatsNewEditions}) => {
        this.whatsNewEditions_ = whatsNewEditions;
      });
    }
  }

  protected onSearchChanged_(e: CustomEvent<string>) {
    this.filter = e.detail.toLowerCase();
  }

  protected startTutorial_(e: CustomEvent) {
    const id = e.detail;
    this.featurePromoErrorMessage_ = '';

    this.handler_.startTutorial(id).then(({errorMessage}) => {
      this.featurePromoErrorMessage_ = errorMessage;
      if (errorMessage !== '') {
        this.$.errorMessageToast.show();
      }
    });
  }

  protected showFeaturePromo_(e: CustomEvent) {
    const id = e.detail;
    this.featurePromoErrorMessage_ = '';

    this.handler_.showFeaturePromo(id).then(({errorMessage}) => {
      this.featurePromoErrorMessage_ = errorMessage;
      if (errorMessage !== '') {
        this.$.errorMessageToast.show();
      }
    });
  }

  protected clearPromoData_(e: CustomEvent) {
    const id = e.detail;
    this.featurePromoErrorMessage_ = '';

    this.handler_.clearFeaturePromoData(id).then(({errorMessage}) => {
      this.featurePromoErrorMessage_ = errorMessage;
      if (errorMessage !== '') {
        this.$.errorMessageToast.show();
      } else {
        this.handler_.getFeaturePromos().then(({featurePromos}) => {
          this.featurePromos_ = featurePromos;
        });
      }
    });
  }

  protected clearSessionData_() {
    this.handler_.clearSessionData().then(({errorMessage}) => {
      this.featurePromoErrorMessage_ = errorMessage;
      if (errorMessage !== '') {
        this.$.errorMessageToast.show();
      } else {
        this.handler_.getSessionData().then(({sessionData}) => {
          this.sessionData_ = sessionData;
        });
      }
    });
  }

  protected clearNewBadgeData_(e: CustomEvent) {
    const id = e.detail;
    this.featurePromoErrorMessage_ = '';

    this.handler_.clearNewBadgeData(id).then(({errorMessage}) => {
      this.featurePromoErrorMessage_ = errorMessage;
      if (errorMessage !== '') {
        this.$.errorMessageToast.show();
      } else {
        this.handler_.getNewBadges().then(({newBadges}) => {
          this.newBadges_ = newBadges;
        });
      }
    });
  }

  protected clearWhatsNewData_() {
    if (!this.isWhatsNewV2_) {
      return;
    }
    this.featurePromoErrorMessage_ = '';

    this.handler_.clearWhatsNewData().then(({errorMessage}) => {
      this.featurePromoErrorMessage_ = errorMessage;
      if (errorMessage !== '') {
        this.$.errorMessageToast.show();
      } else {
        this.handler_.getWhatsNewModules().then(({whatsNewModules}) => {
          this.whatsNewModules_ = whatsNewModules;
        });
        this.handler_.getWhatsNewEditions().then(({whatsNewEditions}) => {
          this.whatsNewEditions_ = whatsNewEditions;
        });
      }
    });
  }

  protected promoFilter_(promo: FeaturePromoDemoPageInfo) {
    return this.filter === '' ||
        promo.displayTitle.toLowerCase().includes(this.filter) ||
        promo.displayDescription.toLowerCase().includes(this.filter) ||
        promo.instructions.find(
            (instruction: string) =>
                instruction.toLowerCase().includes(this.filter)) ||
        promo.supportedPlatforms.find(
            (platform: string) => platform.toLowerCase().includes(this.filter));
  }

  protected whatsNewFilter_(item: (WhatsNewModuleDemoPageInfo|
                                   WhatsNewEditionDemoPageInfo)) {
    return this.filter === '' ||
        item.displayTitle.toLowerCase().includes(this.filter);
  }

  /**
   * Prevent clicks on sidebar items from navigating.
   */
  protected onLinkClick_(event: Event) {
    event.preventDefault();
  }

  protected onSelectorActivate_(event: CustomEvent<{selected: string}>) {
    const url = event.detail.selected;
    this.$.menu.selected = url;
    const idx = url.lastIndexOf('#');
    const el = this.$.content.querySelector(url.substring(idx));
    el?.scrollIntoView(true);
  }

  protected onNarrowChanged_(e: CustomEvent<{value: boolean}>) {
    this.narrow_ = e.detail.value;
  }

  protected onSessionExpandedChanged_(e: CustomEvent<{value: boolean}>) {
    this.sessionExpanded_ = e.detail.value;
  }

  protected launchWhatsNewStaging_() {
    this.handler_.launchWhatsNewStaging();
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'user-education-internals': UserEducationInternalsElement;
  }
}

customElements.define(
    UserEducationInternalsElement.is, UserEducationInternalsElement);