chromium/chrome/browser/resources/chromeos/borealis_installer/app.ts

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import './error_dialog.js';
import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_vars.css.js';
import 'chrome://resources/polymer/v3_0/paper-progress/paper-progress.js';
import 'chrome://borealis-installer/strings.m.js';
import 'chrome://resources/ash/common/cr.m.js';
import 'chrome://resources/ash/common/event_target.js';

import {assertNotReached} from 'chrome://resources/ash/common/assert.js';
import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
import type {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';


import {getTemplate} from './app.html.js';
import {PageCallbackRouter} from './borealis_installer.mojom-webui.js';
import {InstallResult} from './borealis_types.mojom-webui.js';
import {BrowserProxy} from './browser_proxy.js';
import type {BorealisInstallerErrorDialogElement} from './error_dialog.js';

const State = {
  WELCOME: 'welcome',
  INSTALLING: 'installing',
  COMPLETED: 'completed',
};

export interface BorealisInstallerAppElement {
  $: {
    errorDialog: BorealisInstallerErrorDialogElement,
    installLaunch: CrButtonElement,
  };
}

/**
 * @fileoverview
 * Dialog to install borealis.
 */
export class BorealisInstallerAppElement extends PolymerElement {
  static get is(): string {
    return 'borealis-installer-app';
  }

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

  static get properties() {
    return {
      state: {
        type: String,
        value: State.WELCOME,
      },
    };
  }

  private listenerIds: number[];
  private router: PageCallbackRouter;
  private state: string;
  private installerProgress: number;
  private progressLabel: string;
  private canceling: boolean = false;

  constructor() {
    super();
    this.listenerIds = [];
    this.router = BrowserProxy.getInstance().callbackRouter;
  }

  override ready() {
    super.ready();
    this.addEventListener('retry', this.onErrorRetry);
    this.addEventListener('cancel', this.onErrorCancel);
    this.addEventListener('storage', this.onOpenStorage);
  }

  override connectedCallback() {
    super.connectedCallback();
    this.listenerIds.push(
        this.router.onProgressUpdate.addListener(
            (progressFraction: number, progressLabel: string) => {
              // Multiply by 100 to get percentage.
              this.installerProgress = Math.round(progressFraction * 100);
              this.progressLabel = progressLabel;
            }),
        this.router.onInstallFinished.addListener(
            (installResult: InstallResult) => {
              this.handleInstallResult(installResult);
            }),
        // Called when the user closes the installer (e.g. from the window bar)
        this.router.requestClose.addListener(() => {
          this.cancelAndClose();
        }));
  }

  override disconnectedCallback() {
    super.disconnectedCallback();
    this.listenerIds.forEach(id => this.router.removeListener(id));
  }

  private onErrorRetry() {
    this.startInstall();
  }

  private onErrorCancel() {
    this.cancelAndClose();
  }

  private onOpenStorage() {
    BrowserProxy.getInstance().handler.openStoragePage();
    this.cancelAndClose();
  }

  private handleInstallResult(installResult: InstallResult) {
    switch (installResult) {
      case InstallResult.kSuccess:
        this.state = State.COMPLETED;
        this.$.installLaunch.focus();
        break;
      case InstallResult.kCancelled:
        this.cancelAndClose();
        break;
      default:
        this.$.errorDialog.show(installResult);
    }
  }

  protected eq(value1: any, value2: any): boolean {
    return value1 === value2;
  }

  protected getTitle(): string {
    let titleId: string = '';
    switch (this.state) {
      case State.WELCOME:
        titleId = 'confirmationTitle';
        break;
      case State.INSTALLING:
        titleId = 'ongoingTitle';
        break;
      case State.COMPLETED:
        titleId = 'finishedTitle';
        break;
      default:
        assertNotReached();
    }
    return loadTimeData.getString(titleId);
  }

  protected getMessage(): string {
    let messageId: string = '';
    switch (this.state) {
      case State.WELCOME:
        messageId = 'confirmationMessage';
        break;
      case State.INSTALLING:
        messageId = 'ongingMessage';
        break;
      case State.COMPLETED:
        messageId = 'finishedMessage';
        break;
      default:
        assertNotReached();
    }
    return loadTimeData.getString(messageId);
  }

  protected getProgressMessage(): string {
    return loadTimeData.getStringF('percent', this.installerProgress);
  }

  protected getProgressLabel(): string {
    return this.progressLabel;
  }

  protected shouldShowInstallOrLaunchButton(state: string): boolean {
    return [State.WELCOME, State.COMPLETED].includes(state);
  }

  protected getInstallOrLaunchLabel(state: string): string {
    if (state === State.COMPLETED) {
        return loadTimeData.getString('launch');
    }
    return loadTimeData.getString('install');
  }

  protected getCancelOrCloseLabel(state: string): string {
    if (state === State.COMPLETED) {
      return loadTimeData.getString('close');
    }
    return loadTimeData.getString('cancel');
  }

  protected onCancelButtonClicked(): void {
    this.cancelAndClose();
  }

  cancelAndClose(): void {
    if (this.canceling) {
      return;
    }
    this.canceling = true;
    switch (this.state) {
      case State.INSTALLING:
        BrowserProxy.getInstance().handler.cancelInstall();
        break;
      case State.COMPLETED:
        BrowserProxy.getInstance().handler.shutDown();
        break;
      default:
        break;
    }
    this.closePage();
  }

  protected onInstallOrLaunchButtonClicked(): void {
    switch (this.state) {
      case State.WELCOME:
        // 'Install' button clicked.
        this.startInstall();
        break;
      case State.COMPLETED:
        // 'Open Steam' button clicked.
        BrowserProxy.getInstance().handler.launch();
        this.closePage();
        break;
    }
  }

  startInstall(): void {
    this.installerProgress = 0;
    this.progressLabel = '';
    this.state = State.INSTALLING;
    BrowserProxy.getInstance().handler.install();
  }

  closePage(): void {
    BrowserProxy.getInstance().handler.onPageClosed();
  }
}


declare global {
  interface HTMLElementTagNameMap {
    'borealis-installer-app': BorealisInstallerAppElement;
  }
}

customElements.define(
    BorealisInstallerAppElement.is, BorealisInstallerAppElement);