chromium/chrome/browser/resources/settings/a11y_page/live_caption_section.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.

/**
 * @fileoverview 'settings-live-caption' is a component for showing Live Caption
 * settings. It appears on the accessibility subpage
 * (chrome://settings/accessibility) on Mac and some versions of Windows and on
 * the captions subpage (chrome://settings/captions) on Linux and other versions
 * of Windows.
 */

import '//resources/cr_elements/cr_shared_style.css.js';
import '//resources/cr_elements/cr_collapse/cr_collapse.js';
import '../controls/settings_toggle_button.js';
import '../settings_shared.css.js';

import {WebUiListenerMixin} from '//resources/cr_elements/web_ui_listener_mixin.js';
import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import type {CaptionsBrowserProxy, LiveCaptionLanguage, LiveCaptionLanguageList} from '/shared/settings/a11y_page/captions_browser_proxy.js';
import {CaptionsBrowserProxyImpl} from '/shared/settings/a11y_page/captions_browser_proxy.js';
import {PrefsMixin} from '/shared/settings/prefs/prefs_mixin.js';

import type {SettingsToggleButtonElement} from '../controls/settings_toggle_button.js';
import {loadTimeData} from '../i18n_setup.js';

import {getTemplate} from './live_caption_section.html.js';

// clang-format off
// <if expr="not is_chromeos">
import '../controls/settings_dropdown_menu.js';
import './live_translate_section.js';
import '../languages_page/add_languages_dialog.js';

import type {LanguageHelper, LanguagesModel} from '../languages_page/languages_types.js';

import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
import {ListPropertyUpdateMixin} from 'chrome://resources/cr_elements/list_property_update_mixin.js';
import {assert} from 'chrome://resources/js/assert.js';
import {focusWithoutInk} from 'chrome://resources/js/focus_without_ink.js';

import type {DropdownMenuOptionList} from '../controls/settings_dropdown_menu.js';

import type {DomRepeatEvent} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
// </if>
// clang-format on


// <if expr="is_chromeos">
const SettingsLiveCaptionElementBase =
    WebUiListenerMixin(PrefsMixin(PolymerElement));
// </if>
// <if expr="not is_chromeos">
const SettingsLiveCaptionElementBase = WebUiListenerMixin(
    ListPropertyUpdateMixin(PrefsMixin(I18nMixin(PolymerElement))));
// </if>

export class SettingsLiveCaptionElement extends SettingsLiveCaptionElementBase {
  static get is() {
    return 'settings-live-caption';
  }

  static get template() {
    return getTemplate();
  }

  static get properties() {
    return {
      prefs: {
        type: Object,
        notify: true,
      },

      /**
       * The subtitle to display under the Live Caption heading. Generally, this
       * is a generic subtitle describing the feature. While the SODA model is
       * being downloading, this displays the download progress.
       */
      enableLiveCaptionSubtitle_: {
        type: String,
        value: loadTimeData.getString('captionsEnableLiveCaptionSubtitle'),
      },

      enableLiveCaptionMultiLanguage_: {
        type: Boolean,
        value: function() {
          return loadTimeData.getBoolean('enableLiveCaptionMultiLanguage');
        },
      },

      // <if expr="not is_chromeos">
      /**
       * Read-only reference to the languages model provided by the
       * 'settings-languages' instance.
       */
      languages: {
        type: Object,
        notify: true,
      },

      languageHelper: Object,

      enableLiveTranslate_: {
        type: Boolean,
        value: function() {
          return loadTimeData.getBoolean('enableLiveTranslate');
        },
      },

      installedLanguagePacks_: {
        type: Array,
        value: () => [],
        observer: 'updateLanguageOptions_',
      },

      availableLanguagePacks_: {
        type: Array,
        value: () => [],
      },

      languageOptions_: {
        type: Array,
        value: () => [],
      },

      showAddLanguagesDialog_: Boolean,
      // </if>
    };
  }

  // <if expr="not is_chromeos">
  languages: LanguagesModel;
  languageHelper: LanguageHelper;
  private enableLiveTranslate_: boolean;
  private installedLanguagePacks_: LiveCaptionLanguageList;
  private availableLanguagePacks_: LiveCaptionLanguageList;
  private languageOptions_: DropdownMenuOptionList;
  private showAddLanguagesDialog_: boolean;
  // </if>
  private browserProxy_: CaptionsBrowserProxy =
      CaptionsBrowserProxyImpl.getInstance();
  private enableLiveCaptionSubtitle_: string;
  private enableLiveCaptionMultiLanguage_: boolean;

  override ready() {
    super.ready();
    // <if expr="not is_chromeos">
    this.browserProxy_.getInstalledLanguagePacks().then(
        (installedLanguagePacks: LiveCaptionLanguageList) => {
          this.installedLanguagePacks_ = installedLanguagePacks;
        });

    this.browserProxy_.getAvailableLanguagePacks().then(
        (availableLanguagePacks: LiveCaptionLanguageList) => {
          this.availableLanguagePacks_ = availableLanguagePacks;
        });
    // </if>

    // <if expr="is_chromeos">
    this.addWebUiListener(
        'soda-download-progress-changed',
        (sodaDownloadProgress: string) =>
            this.onSodaDownloadProgressChanged_(sodaDownloadProgress));
    // </if>
    // <if expr="not is_chromeos">
    this.addWebUiListener(
        'soda-download-progress-changed',
        (sodaDownloadProgress: string, languageCode: string) =>
            this.onSodaDownloadProgressChangedForLanguage_(
                sodaDownloadProgress, languageCode));
    // </if>

    this.browserProxy_.liveCaptionSectionReady();
  }

  /**
   * @return the Live Caption toggle element.
   */
  getLiveCaptionToggle(): SettingsToggleButtonElement {
    return this.shadowRoot!.querySelector<SettingsToggleButtonElement>(
        '#liveCaptionToggleButton')!;
  }

  private onLiveCaptionEnabledChanged_(event: Event) {
    const liveCaptionEnabled =
        (event.target as SettingsToggleButtonElement).checked;
    chrome.metricsPrivate.recordBoolean(
        'Accessibility.LiveCaption.EnableFromSettings', liveCaptionEnabled);

    // <if expr="not is_chromeos">
    if (this.installedLanguagePacks_.length === 0) {
      this.installLanguagePacks_(
          [this.getPref('accessibility.captions.live_caption_language').value]);
    }
    // </if>
  }

  private onLiveCaptionMaskOffensiveWordsChanged_(event: Event) {
    const liveCaptionMaskOffensiveWords =
        (event.target as SettingsToggleButtonElement).checked;
    chrome.metricsPrivate.recordBoolean(
        'Accessibility.LiveCaption.MaskOffensiveWords',
        liveCaptionMaskOffensiveWords);
  }

  // <if expr="not is_chromeos">
  private onAddLanguagesClick_(e: Event) {
    e.preventDefault();
    this.showAddLanguagesDialog_ = true;
  }

  private onAddLanguagesDialogClose_() {
    this.showAddLanguagesDialog_ = false;
    const toFocus = this.shadowRoot!.querySelector<HTMLElement>('#addLanguage');
    assert(toFocus);
    focusWithoutInk(toFocus);
  }

  private onRemoveLanguageClick_(e: DomRepeatEvent<LiveCaptionLanguage>) {
    this.installedLanguagePacks_ = this.installedLanguagePacks_.filter(
        languagePack => languagePack.code !== e.model.item.code);
    this.browserProxy_.removeLanguagePack(e.model.item.code);

    if (this.installedLanguagePacks_.length === 0) {
      this.setPrefValue('accessibility.captions.live_caption_enabled', false);
      return;
    }

    if (!this.installedLanguagePacks_.some(
            languagePack => languagePack.code ===
                this.getPref('accessibility.captions.live_caption_language')
                    .value)) {
      this.setPrefValue(
          'accessibility.captions.live_caption_language',
          this.installedLanguagePacks_[0].code);
    }
  }

  private onLanguagesAdded_(e: CustomEvent<string[]>) {
    this.installLanguagePacks_(e.detail);
  }

  private installLanguagePacks_(languageCodes: string[]) {
    const newLanguagePacks: LiveCaptionLanguageList = [];
    languageCodes.forEach(languageCode => {
      const languagePackToAdd = this.availableLanguagePacks_.find(
          languagePack => languagePack.code === languageCode);
      if (languagePackToAdd) {
        newLanguagePacks.push(languagePackToAdd);
      }
    });

    this.updateList(
        `installedLanguagePacks_`, item => item.code,
        this.installedLanguagePacks_.concat(newLanguagePacks));
    this.browserProxy_.installLanguagePacks(languageCodes);

    // Explicitly call the function to update the language options because
    // updating the list does not trigger the observer function.
    this.updateLanguageOptions_();
  }

  private updateLanguageOptions_() {
    this.languageOptions_ = this.installedLanguagePacks_.map(languagePack => {
      return {value: languagePack.code, name: languagePack.displayName};
    });
  }

  private filterAvailableLanguagePacks_(
      availableLanguagePacks: LiveCaptionLanguageList,
      installedLanguagePacks: LiveCaptionLanguageList):
      chrome.languageSettingsPrivate.Language[] {
    const filteredLanguagePacks =
        availableLanguagePacks.filter(availableLanguagePack => {
          return !installedLanguagePacks.some(
              installedLanguagePack =>
                  installedLanguagePack.code === availableLanguagePack.code);
        });

    return filteredLanguagePacks.map(
        languagePack => ({
          code: languagePack.code,
          displayName: languagePack.displayName,

          // The native display name for language packs is not shown.
          nativeDisplayName: languagePack.nativeDisplayName,
        }));
  }
  // </if>

  // <if expr="is_chromeos">
  /**
   * Displays SODA download progress in the UI.
   * @param sodaDownloadProgress The message sent from the webui to be displayed
   *     as download progress for Live Caption.
   */
  private onSodaDownloadProgressChanged_(sodaDownloadProgress: string) {
    this.enableLiveCaptionSubtitle_ = sodaDownloadProgress;
  }
  // </if>

  // <if expr="not is_chromeos">
  /**
   * Displays SODA download progress in the UI. When the language UI is visible,
   * which occurs when the kLiveCaptionMultiLanguage feature is enabled and when
   * the kLiveCaptionEnabled pref is true, download progress should appear next
   * to the selected language. Otherwise, the download progress appears as a
   * subtitle below the Live Caption toggle.
   * @param sodaDownloadProgress The message sent from the webui to be displayed
   *     as download progress for Live Caption.
   * @param languageCode The language code indicating which language pack the
   *     message applies to.
   */
  private onSodaDownloadProgressChangedForLanguage_(
      sodaDownloadProgress: string, languageCode: string) {
    if (!this.enableLiveCaptionMultiLanguage_) {
      this.enableLiveCaptionSubtitle_ = sodaDownloadProgress;
      return;
    }

    for (let i = 0; i < this.installedLanguagePacks_.length; i++) {
      const language = this.installedLanguagePacks_[i];
      if (language.code === languageCode) {
        language.downloadProgress = sodaDownloadProgress;
        this.notifyPath('installedLanguagePacks_.' + i + '.downloadProgress');
        break;
      }
    }
  }
  // </if>
}

declare global {
  interface HTMLElementTagNameMap {
    'settings-live-caption': SettingsLiveCaptionElement;
  }
}

customElements.define(
    SettingsLiveCaptionElement.is, SettingsLiveCaptionElement);