chromium/chrome/browser/resources/print_preview/ui/destination_list_item_cros.ts

// Copyright 2018 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/cr_elements/icons.html.js';
import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
import './destination_list_item_style.css.js';
import './icons.html.js';
import '../strings.m.js';

import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
import {assert} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {removeHighlights} from 'chrome://resources/js/search_highlight_utils.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import type {Destination} from '../data/destination.js';
import {DestinationOrigin} from '../data/destination.js';
import {ERROR_STRING_KEY_MAP, getPrinterStatusIcon, getStatusTextColorClass, PrinterStatusReason} from '../data/printer_status_cros.js';

import {getTemplate} from './destination_list_item_cros.html.js';
import {updateHighlights} from './highlight_utils.js';

enum DestinationConfigStatus {
  IDLE = 0,
  IN_PROGRESS = 1,
  FAILED = 2,
}

const PrintPreviewDestinationListItemElementBase = I18nMixin(PolymerElement);

export class PrintPreviewDestinationListItemElement extends
    PrintPreviewDestinationListItemElementBase {
  static get is() {
    return 'print-preview-destination-list-item';
  }

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

  static get properties() {
    return {
      destination: Object,
      searchQuery: Object,
      searchHint_: String,

      destinationIcon_: {
        type: String,
        computed: 'computeDestinationIcon_(destination, ' +
            'destination.printerStatusReason)',
      },

      statusText_: {
        type: String,
        computed:
            'computeStatusText_(destination, destination.printerStatusReason,' +
            'configurationStatus_)',
      },

      // Holds status of iron-media-query (prefers-color-scheme: dark).
      isDarkModeActive_: Boolean,

      isDestinationCrosLocal_: {
        type: Boolean,
        computed: 'computeIsDestinationCrosLocal_(destination)',
        reflectToAttribute: true,
      },

      configurationStatus_: {
        type: Number,
        value: DestinationConfigStatus.IDLE,
      },

      /**
       * Mirroring the enum so that it can be used from HTML bindings.
       */
      statusEnum_: {
        type: Object,
        value: DestinationConfigStatus,
      },
    };
  }

  static get observers() {
    return [
      'onDestinationPropertiesChange_(' +
          'destination.displayName, destination.isExtension)',
      'updateHighlightsAndHint_(destination, searchQuery)',
      'requestPrinterStatus_(destination.key)',
    ];
  }

  destination: Destination;
  searchQuery: RegExp|null;
  private destinationIcon_: string;
  private searchHint_: string;
  private statusText_: string;
  private isDarkModeActive_: boolean;
  private isDestinationCrosLocal_: boolean;
  private configurationStatus_: DestinationConfigStatus;

  private highlights_: HTMLElement[] = [];

  private onDestinationPropertiesChange_() {
    this.title = this.destination.displayName;
    if (this.destination.isExtension) {
      const icon =
          this.shadowRoot!.querySelector<HTMLElement>('.extension-icon');
      assert(icon);
      icon.style.backgroundImage = 'image-set(' +
          'url(chrome://extension-icon/' + this.destination.extensionId +
          '/24/1) 1x,' +
          'url(chrome://extension-icon/' + this.destination.extensionId +
          '/48/1) 2x)';
    }
  }

  private updateHighlightsAndHint_() {
    this.updateSearchHint_();
    removeHighlights(this.highlights_);
    this.highlights_ = updateHighlights(this, this.searchQuery, new Map());
  }

  private updateSearchHint_() {
    const matches = !this.searchQuery ?
        [] :
        this.destination.extraPropertiesToMatch.filter(
            p => p.match(this.searchQuery!));
    this.searchHint_ = matches.length === 0 ?
        (this.destination.extraPropertiesToMatch.find(p => !!p) || '') :
        matches.join(' ');
  }

  private getExtensionPrinterTooltip_(): string {
    if (!this.destination.isExtension) {
      return '';
    }
    return loadTimeData.getStringF(
        'extensionDestinationIconTooltip', this.destination.extensionName);
  }

  /**
   * Called if the printer configuration request is accepted. Show the waiting
   * message to the user as the configuration might take longer than expected.
   */
  onConfigureRequestAccepted() {
    // It must be a Chrome OS CUPS printer which hasn't been set up before.
    assert(
        this.destination.origin === DestinationOrigin.CROS &&
        !this.destination.capabilities);
    this.configurationStatus_ = DestinationConfigStatus.IN_PROGRESS;
  }

  /**
   * Called when the printer configuration request completes.
   * @param success Whether configuration was successful.
   */
  onConfigureComplete(success: boolean) {
    this.configurationStatus_ =
        success ? DestinationConfigStatus.IDLE : DestinationConfigStatus.FAILED;
  }

  /**
   * @return Whether the current configuration status is |status|.
   */
  private checkConfigurationStatus_(status: DestinationConfigStatus): boolean {
    return this.configurationStatus_ === status;
  }

  /**
   * @return If the destination is a local CrOS printer, this returns
   *    the error text associated with the printer status.
   */
  private computeStatusText_(): string {
    if (!this.destination ||
        this.destination.origin !== DestinationOrigin.CROS) {
      return '';
    }

    // Don't show status text when destination is configuring.
    if (this.configurationStatus_ !== DestinationConfigStatus.IDLE) {
      return '';
    }

    const printerStatusReason = this.destination.printerStatusReason;
    if (printerStatusReason === null ||
        printerStatusReason === PrinterStatusReason.NO_ERROR ||
        printerStatusReason === PrinterStatusReason.UNKNOWN_REASON) {
      return '';
    }

    const errorStringKey = ERROR_STRING_KEY_MAP.get(printerStatusReason);
    return errorStringKey ? this.i18n(errorStringKey) : '';
  }

  private computeDestinationIcon_(): string {
    if (!this.destination) {
      return '';
    }

    if (this.destination.origin === DestinationOrigin.CROS) {
      return getPrinterStatusIcon(
          this.destination.printerStatusReason,
          this.destination.isEnterprisePrinter, this.isDarkModeActive_);
    }

    return this.destination.icon;
  }

  private computeStatusClass_(): string {
    const statusClass = 'connection-status';
    if (!this.destination || this.destination.printerStatusReason === null) {
      return statusClass;
    }

    return `${statusClass} ${
        getStatusTextColorClass(this.destination.printerStatusReason)}`;
  }

  /**
   * True when the destination is a CrOS local printer.
   */
  private computeIsDestinationCrosLocal_(): boolean {
    return this.destination &&
        this.destination.origin === DestinationOrigin.CROS;
  }

  private requestPrinterStatus_() {
    // Requesting printer status only allowed for local CrOS printers.
    if (this.destination.origin !== DestinationOrigin.CROS) {
      return;
    }

    this.destination.requestPrinterStatus().then(
        destinationKey => this.onPrinterStatusReceived_(destinationKey));
  }

  private onPrinterStatusReceived_(destinationKey: string) {
    if (this.destination.key === destinationKey) {
      // Notify printerStatusReason to trigger icon and status text update.
      this.notifyPath(`destination.printerStatusReason`);
    }
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'print-preview-destination-list-item':
        PrintPreviewDestinationListItemElement;
  }
}

customElements.define(
    PrintPreviewDestinationListItemElement.is,
    PrintPreviewDestinationListItemElement);