chromium/chrome/browser/resources/print_preview/ui/destination_dialog.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/cr_button/cr_button.js';
import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
import 'chrome://resources/cr_elements/cr_hidden_style.css.js';
import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
import 'chrome://resources/cr_elements/icons.html.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import '../print_preview_utils.js';
import './destination_dialog_style.css.js';
import './destination_list.js';
import './print_preview_search_box.js';
import './print_preview_shared.css.js';
import './print_preview_vars.css.js';
import '../strings.m.js';
import './throbber.css.js';
import './destination_list_item.js';

import type {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
import {ListPropertyUpdateMixin} from 'chrome://resources/cr_elements/list_property_update_mixin.js';
import {assert} from 'chrome://resources/js/assert.js';
import {EventTracker} from 'chrome://resources/js/event_tracker.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import type {Destination} from '../data/destination.js';
import type {DestinationStore} from '../data/destination_store.js';
import {DestinationStoreEventType} from '../data/destination_store.js';
import {NativeLayerImpl} from '../native_layer.js';

import {getTemplate} from './destination_dialog.html.js';
import type {PrintPreviewDestinationListItemElement} from './destination_list_item.js';
import type {PrintPreviewSearchBoxElement} from './print_preview_search_box.js';

export interface PrintPreviewDestinationDialogElement {
  $: {
    dialog: CrDialogElement,
    searchBox: PrintPreviewSearchBoxElement,
  };
}

const PrintPreviewDestinationDialogElementBase =
    ListPropertyUpdateMixin(PolymerElement);

export class PrintPreviewDestinationDialogElement extends
    PrintPreviewDestinationDialogElementBase {
  static get is() {
    return 'print-preview-destination-dialog';
  }

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

  static get properties() {
    return {
      destinationStore: {
        type: Object,
        observer: 'onDestinationStoreSet_',
      },

      destinations_: {
        type: Array,
        value: [],
      },

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

      searchQuery_: {
        type: Object,
        value: null,
      },
    };
  }

  destinationStore: DestinationStore;
  private destinations_: Destination[];
  private loadingDestinations_: boolean;
  private searchQuery_: RegExp|null;

  private tracker_: EventTracker = new EventTracker();
  private initialized_: boolean = false;

  override ready() {
    super.ready();
    this.addEventListener('keydown', (e: KeyboardEvent) => this.onKeydown_(e));
  }

  override disconnectedCallback() {
    super.disconnectedCallback();

    this.tracker_.removeAll();
  }

  private onKeydown_(e: KeyboardEvent) {
    e.stopPropagation();
    const searchInput = this.$.searchBox.getSearchInput();
    if (e.key === 'Escape' &&
        (e.composedPath()[0] !== searchInput || !searchInput.value.trim())) {
      this.$.dialog.cancel();
      e.preventDefault();
    }
  }

  private onDestinationStoreSet_() {
    assert(this.destinations_.length === 0);
    this.tracker_.add(
        this.destinationStore, DestinationStoreEventType.DESTINATIONS_INSERTED,
        this.updateDestinations_.bind(this));
    this.tracker_.add(
        this.destinationStore,
        DestinationStoreEventType.DESTINATION_SEARCH_DONE,
        this.updateDestinations_.bind(this));
    this.initialized_ = true;
  }

  private updateDestinations_() {
    if (this.destinationStore === undefined || !this.initialized_) {
      return;
    }

    this.updateList(
        'destinations_', destination => destination.key,
        this.getDestinationList_());

    this.loadingDestinations_ =
        this.destinationStore.isPrintDestinationSearchInProgress;
  }

  private getDestinationList_(): Destination[] {
    const destinations = this.destinationStore.destinations();

    return destinations;
  }

  private onCloseOrCancel_() {
    if (this.searchQuery_) {
      this.$.searchBox.setValue('');
    }
  }

  private onCancelButtonClick_() {
    this.$.dialog.cancel();
  }

  /**
   * @param e Event containing the selected destination list item element.
   */
  private onDestinationSelected_(
      e: CustomEvent<PrintPreviewDestinationListItemElement>) {
    const listItem = e.detail;
    const destination = listItem.destination;
    this.selectDestination_(destination);
  }

  private selectDestination_(destination: Destination) {
    this.destinationStore.selectDestination(destination);
    this.$.dialog.close();
  }

  show() {
    this.$.dialog.showModal();
    const loading = this.destinationStore === undefined ||
        this.destinationStore.isPrintDestinationSearchInProgress;
    if (!loading) {
      // All destinations have already loaded.
      this.updateDestinations_();
    }
    this.loadingDestinations_ = loading;
  }

  /** @return Whether the dialog is open. */
  isOpen(): boolean {
    return this.$.dialog.hasAttribute('open');
  }

  private onManageButtonClick_() {
    NativeLayerImpl.getInstance().managePrinters();
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'print-preview-destination-dialog': PrintPreviewDestinationDialogElement;
  }
}

customElements.define(
    PrintPreviewDestinationDialogElement.is,
    PrintPreviewDestinationDialogElement);