chromium/chrome/browser/resources/ash/settings/os_printing_page/cups_enterprise_printers.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-cups-enterprise-printers' is a list container for
 * Enterprise Printers.
 */

import 'chrome://resources/ash/common/cr_elements/cr_action_menu/cr_action_menu.js';
import 'chrome://resources/ash/common/cr_elements/icons.html.js';
import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
import '../settings_shared.css.js';
import './cups_printer_types.js';
import './cups_printers_browser_proxy.js';
import './cups_printers_entry.js';

import {CrActionMenuElement} from 'chrome://resources/ash/common/cr_elements/cr_action_menu/cr_action_menu.js';
import {WebUiListenerMixin} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

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

import {getTemplate} from './cups_enterprise_printers.html.js';
import {matchesSearchTerm, sortPrinters} from './cups_printer_dialog_util.js';
import {PrinterListEntry} from './cups_printer_types.js';
import {CupsPrinterInfo, CupsPrintersBrowserProxy, CupsPrintersBrowserProxyImpl} from './cups_printers_browser_proxy.js';
import {CupsPrintersEntryListMixin} from './cups_printers_entry_list_mixin.js';

/**
 * If the Show more button is visible, the minimum number of printers we show
 * is 3.
 */
const MIN_VISIBLE_PRINTERS = 3;

/**
 * Move a printer's position in |printerArr| from |fromIndex| to |toIndex|.
 */
export function moveEntryInPrinters(
    printerArr: PrinterListEntry[], fromIndex: number, toIndex: number): void {
  const element = printerArr[fromIndex];
  printerArr.splice(fromIndex, 1);
  printerArr.splice(toIndex, 0, element);
}

const SettingsCupsEnterprisePrintersElementBase =
    CupsPrintersEntryListMixin(WebUiListenerMixin(PolymerElement));

export class SettingsCupsEnterprisePrintersElement extends
    SettingsCupsEnterprisePrintersElementBase {
  static get is() {
    return 'settings-cups-enterprise-printers';
  }

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

  static get properties() {
    return {

      /**
       * Search term for filtering |enterprisePrinters|.
       */
      searchTerm: {
        type: String,
        value: '',
      },

      activePrinter: {
        type: Object,
        notify: true,
      },

      activePrinterListEntryIndex_: {
        type: Number,
        value: -1,
      },

      printersCount: {
        type: Number,
        computed: 'getFilteredPrintersLength_(filteredPrinters_.*)',
        notify: true,
      },

      /**
       * List of printers filtered through a search term.
       */
      filteredPrinters_: {
        type: Array,
        value: () => [],
      },

      /**
       * Keeps track of whether the user has tapped the Show more button. A
       * search term will expand the collapsed list, so we need to keep track of
       * whether the list expanded because of a search term or because the user
       * tapped on the Show more button.
       */
      hasShowMoreBeenTapped_: {
        type: Boolean,
        value: false,
      },

      /**
       * Used by FocusRowBehavior to track the last focused element on a row.
       */
      lastFocused_: Object,

      /**
       * Used by FocusRowBehavior to track if the list has been blurred.
       */
      listBlurred_: Boolean,
    };
  }

  static get observers() {
    return [
      'onSearchOrPrintersChanged_(enterprisePrinters.*, searchTerm, ' +
          'hasShowMoreBeenTapped_)',
    ];
  }

  activePrinter: CupsPrinterInfo;
  printersCount: number;
  searchTerm: string;

  private activePrinterListEntryIndex_: number;
  private browserProxy_: CupsPrintersBrowserProxy;
  private filteredPrinters_: PrinterListEntry[];
  private hasShowMoreBeenTapped_: boolean;
  private lastFocused_: Object;
  private listBlurred_: boolean;
  private visiblePrinterCounter_: number;

  constructor() {
    super();

    this.browserProxy_ = CupsPrintersBrowserProxyImpl.getInstance();


    /**
     * The number of printers we display if hidden printers are allowed.
     * MIN_VISIBLE_PRINTERS is the default value and we never show fewer
     * printers if the Show more button is visible.
     */
    this.visiblePrinterCounter_ = MIN_VISIBLE_PRINTERS;
  }

  override ready(): void {
    super.ready();
    this.addEventListener('open-action-menu', event => {
      this.onOpenActionMenu_(event);
    });
  }

  /**
   * Redoes the search whenever |searchTerm| or |enterprisePrinters| changes.
   */
  private onSearchOrPrintersChanged_(): void {
    if (!this.enterprisePrinters) {
      return;
    }
    /**
     * Filter printers through |searchTerm|. If |searchTerm| is empty,
     * |filteredPrinters_| is just |enterprisePrinters|.
     */
    let updatedPrinters = this.searchTerm ?
        this.enterprisePrinters.filter(
            (item: PrinterListEntry) =>
                matchesSearchTerm(item.printerInfo, this.searchTerm)) :
        this.enterprisePrinters.slice();
    updatedPrinters.sort(sortPrinters);

    if (this.shouldPrinterListBeCollapsed_()) {
      // If the Show more button is visible, we only display the first
      // N < |visiblePrinterCounter_| printers and the rest are hidden.
      updatedPrinters = updatedPrinters.filter(
          (_: PrinterListEntry, idx: number) =>
              idx < this.visiblePrinterCounter_);
    }

    this.updateList(
        'filteredPrinters_',
        (printer: PrinterListEntry) => printer.printerInfo.printerId,
        updatedPrinters);
  }

  private onShowMoreClick_(): void {
    this.hasShowMoreBeenTapped_ = true;
  }


  /**
   * Keeps track of whether the Show more button should be visible which means
   * that the printer list is collapsed. There are two ways a collapsed list
   * may be expanded: the Show more button is tapped or if there is a search
   * term.
   */
  private shouldPrinterListBeCollapsed_(): boolean {
    // If |searchTerm| is set, never collapse the list.
    if (this.searchTerm) {
      return false;
    }

    // If |hasShowMoreBeenTapped_| is set to true, never collapse the list.
    if (this.hasShowMoreBeenTapped_) {
      return false;
    }

    // If the total number of enterprise printers does not exceed the number of
    // visible printers, there is no need for the list to be collapsed.
    if (this.enterprisePrinters.length - this.visiblePrinterCounter_ < 1) {
      return false;
    }

    return true;
  }

  private showNoSearchResultsMessage_(): boolean {
    return !!this.searchTerm && !this.filteredPrinters_.length;
  }

  private getFilteredPrintersLength_(): number {
    return this.filteredPrinters_.length;
  }

  private getCrActionMenu(): CrActionMenuElement {
    return castExists(this.shadowRoot!.querySelector('cr-action-menu'));
  }

  private onOpenActionMenu_(
      e: CustomEvent<{target: HTMLElement, item: PrinterListEntry}>): void {
    const item: PrinterListEntry = e.detail.item;
    this.activePrinterListEntryIndex_ = this.enterprisePrinters.findIndex(
        (printer: PrinterListEntry) =>
            printer.printerInfo.printerId === item.printerInfo.printerId);
    this.activePrinter =
        this.get(['enterprisePrinters', this.activePrinterListEntryIndex_])
            .printerInfo;

    const target: HTMLElement = e.detail.target;
    this.getCrActionMenu().showAt(target);
  }

  private onViewClick_(): void {
    // Event is caught by 'settings-cups-printers'.
    const editCupsPrinterDetailsEvent = new CustomEvent(
        'edit-cups-printer-details', {bubbles: true, composed: true});
    this.dispatchEvent(editCupsPrinterDetailsEvent);
    this.closeActionMenu_();
  }

  private closeActionMenu_(): void {
    this.getCrActionMenu().close();
  }
}

declare global {
  interface HTMLElementEventMap {
    'open-action-menu':
        CustomEvent<{target: HTMLElement, item: PrinterListEntry}>;
  }
  interface HTMLElementTagNameMap {
    'settings-cups-enterprise-printers': SettingsCupsEnterprisePrintersElement;
  }
}

customElements.define(
    SettingsCupsEnterprisePrintersElement.is,
    SettingsCupsEnterprisePrintersElement);