chromium/chrome/browser/resources/ash/settings/crostini_page/crostini_extra_containers_create_dialog.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.

/**
 * @fileoverview 'settings-crostini-create-container-dialog' is a component
 * enabling a user to create a new container.
 */

import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
import 'chrome://resources/ash/common/cr_elements/md_select.css.js';
import '../settings_shared.css.js';

import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {CrInputElement} from 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {ContainerInfo} from '../guest_os/guest_os_browser_proxy.js';

import {CrostiniBrowserProxy, CrostiniBrowserProxyImpl, DEFAULT_CROSTINI_CONTAINER, DEFAULT_CROSTINI_VM} from './crostini_browser_proxy.js';
import {getTemplate} from './crostini_extra_containers_create_dialog.html.js';

export interface ExtraContainersCreateDialog {
  $: {
    dialog: CrDialogElement,
    containerFileInput: CrInputElement,
    containerNameInput: CrInputElement,
    imageAliasInput: CrInputElement,
    imageServerInput: CrInputElement,
    vmNameInput: CrInputElement,
  };
}

export class ExtraContainersCreateDialog extends PolymerElement {
  static get is() {
    return 'settings-crostini-create-container-dialog';
  }

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

  static get properties() {
    return {
      /**
       * List of container properties that are already stored in settings.
       */
      allContainers: {
        type: Array,
        value: [],
      },

      containerFile_: {
        type: String,
        value: '',
      },

      inputVmName_: {
        type: String,
        value: DEFAULT_CROSTINI_VM,
      },

      inputContainerName_: {
        type: String,
        value: '',
      },

      inputImageServer_: {
        type: String,
        value: '',
      },

      inputImageAlias_: {
        type: String,
        value: '',
      },

      advancedToggleExpanded_: {
        type: Boolean,
        value: false,
      },

      disableCreateButton_: {
        type: Boolean,
        value: true,
      },

      validContainerName_: {
        type: Boolean,
        value: true,
      },

      containerNameError_: {
        type: String,
        value: '',
      },
    };
  }

  allContainers: ContainerInfo[];
  private advancedToggleExpanded_: boolean;
  private browserProxy_: CrostiniBrowserProxy;
  private containerFile_: string;
  private containerNameError_: string;
  private disableCreateButton_: boolean;
  private inputContainerName_: string;
  private inputImageAlias_: string;
  private inputImageServer_: string;
  private inputVmName_: string;
  private validContainerName_: boolean;

  constructor() {
    super();

    this.browserProxy_ = CrostiniBrowserProxyImpl.getInstance();
  }

  override connectedCallback(): void {
    super.connectedCallback();
    this.$.dialog.showModal();
    this.$.vmNameInput.value = this.inputVmName_;
    this.$.containerNameInput.value = this.inputContainerName_;
    this.$.imageServerInput.value = this.inputImageServer_;
    this.$.imageAliasInput.value = this.inputImageAlias_;
    this.$.containerNameInput.focus();
  }

  /**
   * @param input The vm name to verify.
   * @return if the input string is a valid vm name.
   */
  isValidVmName(_input: string): boolean {
    // TODO(crbug:1261319) Allowing non a non-default VM based on policy (TBD).
    return true;
  }

  private validateNames_(): void {
    this.inputVmName_ = this.$.vmNameInput.value.length === 0 ?
        DEFAULT_CROSTINI_VM :
        this.$.vmNameInput.value;
    this.inputContainerName_ = this.$.containerNameInput.value;

    this.containerNameError_ = '';
    if (this.inputContainerName_.length === 0) {
      this.containerNameError_ = loadTimeData.getString(
          'crostiniExtraContainersCreateDialogEmptyContainerNameError');
    } else if (
        this.inputContainerName_ === DEFAULT_CROSTINI_CONTAINER ||
        this.allContainers.find(
            e => e.id.vm_name === this.inputVmName_ &&
                e.id.container_name === this.inputContainerName_)) {
      this.containerNameError_ = loadTimeData.getString(
          'crostiniExtraContainersCreateDialogContainerExistsError');
    }

    this.validContainerName_ = this.containerNameError_.length === 0;
    this.disableCreateButton_ =
        !this.validContainerName_ || !this.isValidVmName(this.inputVmName_);
  }

  private onCancelClick_(): void {
    this.$.dialog.close();
  }

  private onCreateClick_(): void {
    if (this.advancedToggleExpanded_) {
      // These elements are part of a dom-if on |advancedToggleExpanded_|
      this.inputImageServer_ = this.$.imageServerInput.value;
      this.inputImageAlias_ = this.$.imageAliasInput.value;
      this.containerFile_ = this.$.containerFileInput.value;
    }

    this.browserProxy_.createContainer(
        {vm_name: this.inputVmName_, container_name: this.inputContainerName_},
        this.inputImageServer_, this.inputImageAlias_, this.containerFile_);

    this.$.dialog.close();
  }

  private async onSelectContainerFileClick_(): Promise<void> {
    this.$.containerFileInput.value =
        await this.browserProxy_.openContainerFileSelector();
  }

  private advancedToggleClicked_(): void {
    this.advancedToggleExpanded_ = !this.advancedToggleExpanded_;
    // Force repaint.
    this.$.dialog.getBoundingClientRect();
  }

  /**
   * @param opened Whether the menu is expanded.
   * @return Icon name.
   */
  private getArrowIcon_(opened: boolean): string {
    return opened ? 'cr:arrow-drop-up' : 'cr:arrow-drop-down';
  }

  private boolToString_(bool: boolean): string {
    return bool.toString();
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'settings-crostini-create-container-dialog': ExtraContainersCreateDialog;
  }
}

customElements.define(
    ExtraContainersCreateDialog.is, ExtraContainersCreateDialog);