chromium/chrome/browser/resources/ash/settings/os_printing_page/cups_add_printer_manually_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 'add-printer-manually-dialog' is a dialog in which user can
 * manually enter the information to set up a new printer.
 */

import 'chrome://resources/ash/common/cr_elements/localized_link/localized_link.js';
import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
import './cups_add_print_server_dialog.js';
import './cups_add_printer_dialog.js';
import './cups_printer_dialog_error.js';
import './cups_printer_shared.css.js';
import './cups_printers_browser_proxy.js';

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

import {cast, castExists} from '../assert_extras.js';

import {AddPrinterDialogElement} from './cups_add_printer_dialog.js';
import {getTemplate} from './cups_add_printer_manually_dialog.html.js';
import {getErrorText, isNameAndAddressValid} from './cups_printer_dialog_util.js';
import {CupsPrinterInfo, CupsPrintersBrowserProxy, CupsPrintersBrowserProxyImpl, PrinterMakeModel, PrinterSetupResult} from './cups_printers_browser_proxy.js';

function getEmptyPrinter(): object {
  return {
    ppdManufacturer: '',
    ppdModel: '',
    printerAddress: '',
    printerDescription: '',
    printerId: '',
    printerMakeAndModel: '',
    printerName: '',
    printerPPDPath: '',
    printerPpdReference: {
      userSuppliedPpdUrl: '',
      effectiveMakeAndModel: '',
      autoconf: false,
    },
    printerProtocol: 'ipp',
    printerQueue: 'ipp/print',
    printServerUri: '',
  };
}

export interface AddPrinterManuallyDialogElement {
  $: {printerAddressInput: CrInputElement};
}

export class AddPrinterManuallyDialogElement extends PolymerElement {
  static get is() {
    return 'add-printer-manually-dialog';
  }

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

  static get properties() {
    return {
      newPrinter: {type: Object, notify: true, value: getEmptyPrinter},

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

      /**
       * The error text to be displayed on the dialog.
       */
      errorText_: {
        type: String,
        value: '',
      },

      showPrinterQueue_: {
        type: Boolean,
        value: true,
      },
    };
  }

  static get observers() {
    return ['printerInfoChanged_(newPrinter.*)'];
  }

  newPrinter: CupsPrinterInfo;

  private addPrinterInProgress_: boolean;
  private browserProxy_: CupsPrintersBrowserProxy;
  private errorText_: string;
  private showPrinterQueue_: boolean;

  constructor() {
    super();

    this.browserProxy_ = CupsPrintersBrowserProxyImpl.getInstance();
  }

  private getAddPrinterDialog_(): AddPrinterDialogElement {
    return castExists(this.shadowRoot!.querySelector('add-printer-dialog'));
  }
  private onCancelClick_(): void {
    this.getAddPrinterDialog_().close();
  }

  private onAddPrinterSucceeded_(result: PrinterSetupResult): void {
    this.recordAddPrinterResult(/*success=*/ true);
    const showCupsPrinterToastEvent =
        new CustomEvent('show-cups-printer-toast', {
          bubbles: true,
          composed: true,
          detail: {
            resultCode: result,
            printerName: this.newPrinter.printerName,
          },
        });
    this.dispatchEvent(showCupsPrinterToastEvent);
    this.getAddPrinterDialog_().close();
  }

  private onAddPrinterFailed_(result: PrinterSetupResult): void {
    this.recordAddPrinterResult(/*success=*/ false);
    this.errorText_ = getErrorText(result);
  }

  private openManufacturerModelDialog_(): void {
    const event = new CustomEvent('open-manufacturer-model-dialog', {
      bubbles: true,
      composed: true,
    });
    this.dispatchEvent(event);
  }

  private onPrinterFound_(info: PrinterMakeModel): void {
    const newPrinter: CupsPrinterInfo = Object.assign({}, this.newPrinter);

    newPrinter.printerMakeAndModel = info.makeAndModel;
    newPrinter.printerPpdReference.userSuppliedPpdUrl =
        info.ppdRefUserSuppliedPpdUrl;
    newPrinter.printerPpdReference.effectiveMakeAndModel =
        info.ppdRefEffectiveMakeAndModel;
    newPrinter.printerPpdReference.autoconf = info.autoconf;

    this.newPrinter = newPrinter;

    // Add the printer if it's configurable. Otherwise, forward to the
    // manufacturer dialog.
    if (info.ppdReferenceResolved) {
      this.browserProxy_.addCupsPrinter(this.newPrinter)
          .then(
              this.onAddPrinterSucceeded_.bind(this),
              this.onAddPrinterFailed_.bind(this));
    } else {
      this.getAddPrinterDialog_()!.close();
      this.openManufacturerModelDialog_();
    }
  }

  private infoFailed_(result: PrinterSetupResult): void {
    this.recordAddPrinterResult(/*success=*/ false);
    this.addPrinterInProgress_ = false;
    if (result === PrinterSetupResult.PRINTER_UNREACHABLE) {
      this.$.printerAddressInput.invalid = true;
      return;
    }
    this.errorText_ = getErrorText(result);
  }

  private addPressed_(): void {
    this.addPrinterInProgress_ = true;

    if (this.newPrinter.printerProtocol === 'ipp' ||
        this.newPrinter.printerProtocol === 'ipps') {
      this.browserProxy_.getPrinterInfo(this.newPrinter)
          .then(this.onPrinterFound_.bind(this), this.infoFailed_.bind(this));
    } else {
      this.getAddPrinterDialog_()!.close();
      this.openManufacturerModelDialog_();
    }
  }

  private onPrintServerClick_(): void {
    this.getAddPrinterDialog_()!.close();

    const openAddPrintServerDialogEvent =
        new CustomEvent('open-add-print-server-dialog', {
          bubbles: true,
          composed: true,
        });
    this.dispatchEvent(openAddPrintServerDialogEvent);
  }

  private onProtocolChange_(event: Event): void {
    // Queue input should be hidden when protocol is set to "App Socket".
    const selectEl = cast(event.target, HTMLSelectElement);
    this.showPrinterQueue_ = selectEl.value !== 'socket';
    this.set('newPrinter.printerProtocol', selectEl!.value);
  }

  private canAddPrinter_(): boolean {
    return (
        !this.addPrinterInProgress_ && isNameAndAddressValid(this.newPrinter));
  }

  private printerInfoChanged_(): void {
    this.$.printerAddressInput.invalid = false;
    this.errorText_ = '';
  }

  private onKeypress_(event: KeyboardEvent): void {
    if (event.key !== 'Enter') {
      return;
    }
    event.stopPropagation();

    if (this.canAddPrinter_()) {
      this.addPressed_();
    }
  }

  private recordAddPrinterResult(success: boolean): void {
    chrome.metricsPrivate.recordBoolean(
        'Printing.CUPS.AddPrinterManuallyResult', success);
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'add-printer-manually-dialog': AddPrinterManuallyDialogElement;
  }
}

customElements.define(
    AddPrinterManuallyDialogElement.is, AddPrinterManuallyDialogElement);