Source: ui/text_style.js

/*! @license
 * Shaka Player
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */


goog.provide('shaka.ui.TextStyleMenu');

goog.require('shaka.ui.SettingsMenu');
goog.require('shaka.ui.Utils');
goog.require('shaka.util.Dom');
goog.requireType('shaka.Player');
goog.requireType('shaka.ui.Controls');
goog.requireType('shaka.ui.TextStylePreview');


/**
 * Abstract base for text style configuration menus (position, size, etc.).
 * Subclasses implement the template methods getItems / getLabelForItem /
 * onItemSelected / getPreviewConfigForItem / getCurrentValueLabel.
 *
 * @extends {shaka.ui.SettingsMenu}
 * @template T
 * @abstract
 * @export
 */
shaka.ui.TextStyleMenu = class extends shaka.ui.SettingsMenu {
  /**
   * @param {!HTMLElement} parent
   * @param {!shaka.ui.Controls} controls
   * @param {string} icon
   */
  constructor(parent, controls, icon) {
    super(parent, controls, icon);

    this.eventManager.listenMulti(
        this.player,
        [
          'loading',
          'unloading',
          'configurationchanged',
          'trackschanged',
        ], () => {
          this.updateCurrentSelection_();
          this.checkAvailability();
        });
  }

  /** @override */
  checkAvailability() {
    const available = !this.isSubMenuOpened &&
        shaka.ui.TextStyleMenu.isStyleAvailable(this.player, this.controls);
    shaka.ui.Utils.setDisplay(this.button, available);
    this.button.ariaPressed = available ? 'true' : 'false';
  }

  /**
   * Whether the subtitle style can be changed.  The style can be set before
   * the text is enabled, so it is available as soon as there is any text
   * track.  The preview shows an example subtitle even while the text is
   * hidden.
   *
   * @param {shaka.Player} player
   * @param {shaka.ui.Controls} controls
   * @return {boolean}
   */
  static isStyleAvailable(player, controls) {
    const tracks = player.getTextTracks() || [];
    return tracks.length > 0 && controls.getConfig().captionsStyles;
  }

  /** @override */
  onMenuOpen() {
    this.controls.showTextStylePreview();
  }

  /** @override */
  onMenuClose() {
    this.controls.hideTextStylePreview();
  }

  /**
   * @return {!Array<T>}
   * @protected
   */
  getItems() { return []; }

  /**
   * @param {T} item
   * @return {string}
   * @protected
   */
  getLabelForItem(item) { return ''; }

  /**
   * @param {T} item
   * @protected
   */
  onItemSelected(item) {}

  /**
   * @param {T} item
   * @return {!shaka.ui.TextStylePreview.Configuration}
   * @protected
   */
  getPreviewConfigForItem(item) { return {}; }

  /**
   * @return {string}
   * @protected
   */
  getCurrentValueLabel() { return ''; }

  /**
   * Returns the CSS color of the swatch shown next to the item, or null to
   * not show a swatch.
   *
   * @param {T} item
   * @return {?string}
   * @protected
   */
  getSwatchColorForItem(item) { return null; }

  /**
   * Clears and rebuilds the menu items, then refreshes the selection indicator.
   * Call from the subclass constructor and from updateLocalizedStrings() when
   * item labels are localized strings.
   * @protected
   */
  rebuildMenu() {
    shaka.ui.Utils.clearMenuKeepingBackButton(this.menu);

    for (const item of this.getItems()) {
      const button = shaka.util.Dom.createButton();
      button.setAttribute('role', 'menuitemradio');
      button.setAttribute('aria-checked', 'false');
      const span = shaka.util.Dom.createHTMLElement('span');
      span.textContent = this.getLabelForItem(item);
      button.appendChild(span);

      const swatchColor = this.getSwatchColorForItem(item);
      if (swatchColor) {
        // Not a span, because the chosen item is looked up by its span.
        const swatch = shaka.util.Dom.createHTMLElement('div');
        swatch.classList.add('shaka-color-swatch');
        swatch.style.backgroundColor = swatchColor;
        button.appendChild(swatch);
      }

      this.eventManager.listen(button, 'click', () => {
        this.onItemSelected(item);
        this.updateCurrentSelection_();
      });

      shaka.ui.Utils.addHoverAndFocusListeners(
          this.eventManager, button,
          () => this.controls.updateTextStylePreview(
              this.getPreviewConfigForItem(item)),
          () => this.controls.resetTextStylePreview());

      this.menu.appendChild(button);
    }

    this.updateCurrentSelection_();
    shaka.ui.Utils.focusOnTheChosenItem(this.menu);
  }

  /** @private */
  updateCurrentSelection_() {
    const checkmarkIcon = shaka.ui.Utils.getDescendantIfExists(
        this.menu, 'shaka-ui-icon shaka-chosen-item');
    if (checkmarkIcon) {
      const prevButton = checkmarkIcon.parentElement;
      prevButton.setAttribute('aria-checked', 'false');
      const prevSpan = prevButton.getElementsByTagName('span')[0];
      if (prevSpan) {
        prevSpan.classList.remove('shaka-chosen-item');
      }
      prevButton.removeChild(checkmarkIcon);
    }

    const currentLabel = this.getCurrentValueLabel();
    const span = Array.from(this.menu.querySelectorAll('span'))
        .find((el) => el.textContent === currentLabel);
    if (span) {
      const button = span.parentElement;
      button.appendChild(shaka.ui.Utils.checkmarkIcon());
      shaka.ui.Utils.setChosenItem(button, span);
    }
    this.currentSelection.textContent = currentLabel;
  }
};