chromium/chrome/browser/resources/chromeos/cloud_upload/move_confirmation_page.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 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_checkbox/cr_checkbox.js';
import 'chrome://resources/cros_components/lottie_renderer/lottie-renderer.js';

import type {CrCheckboxElement} from 'chrome://resources/ash/common/cr_elements/cr_checkbox/cr_checkbox.js';
import type {LottieRenderer} from 'chrome://resources/cros_components/lottie_renderer/lottie-renderer.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';

import {MetricsRecordedSetupPage, OperationType, UserAction} from './cloud_upload.mojom-webui.js';
import {CloudUploadBrowserProxy} from './cloud_upload_browser_proxy.js';
import {getTemplate} from './move_confirmation_page.html.js';

export enum CloudProvider {
  GOOGLE_DRIVE,
  ONE_DRIVE,
}

/**
 * The MoveConfirmationPageElement represents the dialog page shown when the
 * user opens a file that needs to be moved first, and they haven't yet decided
 * to always move files.
 */
export class MoveConfirmationPageElement extends HTMLElement {
  private proxy: CloudUploadBrowserProxy =
      CloudUploadBrowserProxy.getInstance();
  private cloudProvider: CloudProvider|undefined;
  private animationPlayer: LottieRenderer|undefined;
  private playPauseButton: HTMLElement|undefined;

  // Save reference to listener so it can be removed from the document in
  // disconnectedCallback().
  private boundKeyDownListener_: (e: KeyboardEvent) => void;

  constructor() {
    super();

    const shadowRoot = this.attachShadow({mode: 'open'});

    shadowRoot.innerHTML = getTemplate();
    const actionButton = this.$('.action-button')!;
    const cancelButton = this.$('.cancel-button')!;
    this.playPauseButton = this.$('#playPauseIcon')!;

    actionButton.addEventListener('click', () => this.onActionButtonClick());
    cancelButton.addEventListener('click', () => this.onCancelButtonClick());
    this.playPauseButton.addEventListener(
        'click', () => this.onPlayPauseButtonClick());
    this.boundKeyDownListener_ = this.onKeyDown.bind(this);
  }

  connectedCallback(): void {
    document.addEventListener('keydown', this.boundKeyDownListener_);
  }

  disconnectedCallback(): void {
    document.removeEventListener('keydown', this.boundKeyDownListener_);
  }

  $<T extends HTMLElement>(query: string): T {
    return this.shadowRoot!.querySelector(query)!;
  }

  async setDialogAttributes(
      numFiles: number, operationType: OperationType,
      cloudProvider: CloudProvider) {
    const [
      {moveConfirmationShown: officeMoveConfirmationShownForDrive},
      {alwaysMove: alwaysMoveToDrive},
      {moveConfirmationShown: officeMoveConfirmationShownForOneDrive},
      {alwaysMove: alwaysMoveToOneDrive},
    ] = await Promise.all([
      this.proxy.handler.getOfficeMoveConfirmationShownForDrive(),
      this.proxy.handler.getAlwaysMoveOfficeFilesToDrive(),
      this.proxy.handler.getOfficeMoveConfirmationShownForOneDrive(),
      this.proxy.handler.getAlwaysMoveOfficeFilesToOneDrive(),
    ]);

    this.cloudProvider = cloudProvider;

    const isCopyOperation = operationType === OperationType.kCopy;
    const isPlural = numFiles > 1;
    const providerName = this.getProviderName(this.cloudProvider);

    // Animation.
    this.updateAnimation();

    // Title.
    const titleElement = this.$<HTMLElement>('#title')!;
    if (isCopyOperation) {
      titleElement.innerText = loadTimeData.getStringF(
          isPlural ? 'moveConfirmationCopyTitlePlural' :
                     'moveConfirmationCopyTitle',
          providerName,
          numFiles.toString(),
      );
    } else {
      titleElement.innerText = loadTimeData.getStringF(
          isPlural ? 'moveConfirmationMoveTitlePlural' :
                     'moveConfirmationMoveTitle',
          providerName, numFiles.toString());
    }

    // Checkbox and Body.
    const bodyText = this.$('#body-text');
    const checkbox = this.$<CrCheckboxElement>('#always-copy-or-move-checkbox');
    checkbox.innerText = loadTimeData.getString('moveConfirmationAlwaysMove');
    if (this.cloudProvider === CloudProvider.ONE_DRIVE) {
      bodyText.innerText =
          loadTimeData.getString('moveConfirmationOneDriveBodyText');

      // Only show checkbox if the confirmation has been shown before for
      // OneDrive.
      if (officeMoveConfirmationShownForOneDrive) {
        checkbox.checked = alwaysMoveToOneDrive;
      } else {
        checkbox!.remove();
      }
    } else {
      bodyText.innerText =
          loadTimeData.getStringF('moveConfirmationGoogleDriveBodyText');

      // Only show checkbox if the confirmation has been shown before for
      // Drive.
      if (officeMoveConfirmationShownForDrive) {
        checkbox.checked = alwaysMoveToDrive;
      } else {
        checkbox!.remove();
      }
    }

    // Action button.
    const actionButton = this.$<HTMLElement>('.action-button')!;
    actionButton.innerText =
        loadTimeData.getString(isCopyOperation ? 'copyAndOpen' : 'moveAndOpen');
  }

  private getProviderName(cloudProvider: CloudProvider) {
    if (cloudProvider === CloudProvider.ONE_DRIVE) {
      return loadTimeData.getString('oneDrive');
    }
    return loadTimeData.getString('googleDrive');
  }

  private createAnimation(animationUrl: string) {
    this.animationPlayer = document.createElement('cros-lottie-renderer');
    this.animationPlayer.id = 'animation';
    this.animationPlayer.setAttribute('asset-url', animationUrl);
    this.animationPlayer.setAttribute('dynamic', 'true');
    this.animationPlayer.setAttribute('aria-hidden', 'true');
    this.animationPlayer.autoplay = true;
    const animationWrapper = this.$<HTMLElement>('.animation-wrapper')!;
    const playPauseIcon = this.$<HTMLElement>('#playPauseIcon')!;
    animationWrapper.insertBefore(this.animationPlayer, playPauseIcon);
  }

  private updateAnimation() {
    const provider =
        this.cloudProvider === CloudProvider.ONE_DRIVE ? 'onedrive' : 'drive';
    const animationUrl = `animations/move_confirmation_${provider}.json`;
    if (!this.animationPlayer) {
      this.createAnimation(animationUrl);
    } else {
      this.animationPlayer.setAttribute('asset-url', animationUrl);
    }
  }

  private onActionButtonClick(): void {
    const checkbox = this.$<CrCheckboxElement>('#always-copy-or-move-checkbox');
    const setAlwaysMove = !!(checkbox && checkbox.checked);
    if (this.cloudProvider === CloudProvider.ONE_DRIVE) {
      this.proxy.handler.setAlwaysMoveOfficeFilesToOneDrive(setAlwaysMove);
      this.proxy.handler.respondWithUserActionAndClose(
          UserAction.kUploadToOneDrive);
    } else {
      this.proxy.handler.setAlwaysMoveOfficeFilesToDrive(setAlwaysMove);
      this.proxy.handler.respondWithUserActionAndClose(
          UserAction.kUploadToGoogleDrive);
    }
  }

  private onCancelButtonClick(): void {
    if (this.cloudProvider === CloudProvider.ONE_DRIVE) {
      this.proxy.handler.recordCancel(
          MetricsRecordedSetupPage.kMoveConfirmationOneDrive);
      this.proxy.handler.respondWithUserActionAndClose(
          UserAction.kCancelOneDrive);
    } else {
      this.proxy.handler.recordCancel(
          MetricsRecordedSetupPage.kMoveConfirmationGoogleDrive);
      this.proxy.handler.respondWithUserActionAndClose(
          UserAction.kCancelGoogleDrive);
    }
  }

  private onPlayPauseButtonClick(): void {
    const animation = this.$<LottieRenderer>('#animation')!;
    const shouldPlay = this.playPauseButton!.className === 'play';
    if (shouldPlay) {
      animation.play();
      // Update button to Pause.
      this.playPauseButton!.className = 'pause';
      this.playPauseButton!.ariaLabel =
          loadTimeData.getString('animationPauseText');
    } else {
      animation.pause();
      // Update button to Play.
      this.playPauseButton!.className = 'play';
      this.playPauseButton!.ariaLabel =
          loadTimeData.getString('animationPlayText');
    }
  }

  private onKeyDown(e: KeyboardEvent) {
    if (e.key === 'Escape') {
      // Handle Escape as a "cancel".
      e.stopImmediatePropagation();
      e.preventDefault();
      this.onCancelButtonClick();
      return;
    }
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'move-confirmation-page': MoveConfirmationPageElement;
  }
}

customElements.define('move-confirmation-page', MoveConfirmationPageElement);