chromium/chrome/browser/resources/nearby_share/shared/nearby_progress.ts

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

/**
 * @fileoverview The 'nearby-progress' component shows a progress indicator for
 * a Nearby Share transfer to a remote device. It shows device icon and name,
 * and a circular progress bar that can show either progress as a percentage or
 * an animation if the percentage is indeterminate.
 */

import 'chrome://resources/ash/common/cr_elements/cr_auto_img/cr_auto_img.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_style.css.js';
import 'chrome://resources/ash/common/cr_elements/cr_icons.css.js';
import './nearby_shared_icons.html.js';
import './nearby_device_icon.js';

import {assert} from 'chrome://resources/js/assert.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import type {NearbyDeviceIconElement} from './nearby_device_icon.js';
import {getTemplate} from './nearby_progress.html.js';
import type {ShareTarget} from './nearby_share.mojom-webui.js';

export class NearbyProgressElement extends PolymerElement {
  static get is() {
    return 'nearby-progress' as const;
  }

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

  static get properties() {
    return {
      /**
       * The share target to show the progress for. Expected to start as null,
       * then change to a valid object before this component is shown.
       */
      shareTarget: {
        type: Object,
        value: null,
      },

      /**
       * If true, displays an animation representing an unknown amount of
       * progress; otherwise, the progress bar is hidden.
       */
      showIndeterminateProgress: {
        type: Boolean,
        value: false,
      },

      /**
       * If true, then set progress stroke to red, stop any animation, show
       * 100% instead, and set icons to grey. If |showProgress| is |NONE|, then
       * the progress bar is still hidden.
       */
      hasError: {
        type: Boolean,
        value: false,
      },

      /** Size of the target image/icon in pixels. */
      targetImageSize: {
        type: Number,
        readOnly: true,
        value: 68,
      },
    };
  }

  hasError: boolean;
  shareTarget: ShareTarget|null;
  showIndeterminateProgress: boolean;
  targetImageSize: number;

  override ready(): void {
    super.ready();

    this.updateStyles({'--target-image-size': this.targetImageSize + 'px'});
    this.listenToTargetImageLoad_();
  }

  private getProgressWheelClass_(): string {
    const classes: string[] = [];
    if (this.hasError) {
      classes.push('has-error');
    }
    if (this.showIndeterminateProgress) {
      classes.push('indeterminate-progress');
    } else {
      classes.push('hidden');
    }
    return classes.join(' ');
  }

  /**
   * Allow focusing on the progress bar. Ignored by Chromevox otherwise.
   * The tabindex to be applied to the progress wheel.
   */
  private getProgressBarTabIndex_(): number {
    if (this.showIndeterminateProgress && !this.hasError) {
      return 0;
    }
    return -1;
  }

  private getTargetImageUrl_(): string {
    if (!(this.shareTarget && this.shareTarget.imageUrl &&
          this.shareTarget.imageUrl.url &&
          this.shareTarget.imageUrl.url.length)) {
      return '';
    }

    // Adds the parameter to resize to the desired size.
    return this.shareTarget.imageUrl.url + '=s' + this.targetImageSize;
  }

  private listenToTargetImageLoad_(): void {
    const autoImg =
        this.shadowRoot!.querySelector<HTMLImageElement>('#share-target-image');
    assert(autoImg);
    if (autoImg.complete && autoImg.naturalHeight !== 0) {
      this.onTargetImageLoad_();
    } else {
      autoImg.onload = () => {
        this.onTargetImageLoad_();
      };
    }
  }

  private onTargetImageLoad_(): void {
    this.shadowRoot!.querySelector<HTMLImageElement>(
                        '#share-target-image')!.style.display = 'inline';
    this.shadowRoot!.querySelector<NearbyDeviceIconElement>(
                        '#icon')!.style.display = 'none';
  }
}

declare global {
  interface HTMLElementTagNameMap {
    [NearbyProgressElement.is]: NearbyProgressElement;
  }
}

customElements.define(NearbyProgressElement.is, NearbyProgressElement);