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

// Copyright 2019 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_collapse/cr_collapse.js';
import 'chrome://resources/cr_elements/cr_hidden_style.css.js';
import 'chrome://resources/cr_elements/cr_shared_style.css.js';
import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
import './advanced_options_settings.js';
import './button_strip.js';
import './color_settings.js';
import './copies_settings.js';
import './dpi_settings.js';
import './duplex_settings.js';
import './header.js';
import './layout_settings.js';
import './media_size_settings.js';
import './media_type_settings.js';
import './margins_settings.js';
import './more_settings.js';
import './other_options_settings.js';
import './pages_per_sheet_settings.js';
import './pages_settings.js';
// <if expr="is_chromeos">
import './pin_settings.js';
// </if>
import './print_preview_vars.css.js';
import './scaling_settings.js';
import '../strings.m.js';
// <if expr="not is_chromeos">
import './link_container.js';

// </if>

import {CrContainerShadowMixin} from 'chrome://resources/cr_elements/cr_container_shadow_mixin.js';
import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {DarkModeMixin} from '../dark_mode_mixin.js';
import type {Destination} from '../data/destination.js';
import type {Settings} from '../data/model.js';
import type {Error} from '../data/state.js';
import {State} from '../data/state.js';
import {MetricsContext, PrintSettingsUiBucket} from '../metrics.js';

import type {DestinationState, PrintPreviewDestinationSettingsElement} from './destination_settings.js';
import {SettingsMixin} from './settings_mixin.js';
import {getTemplate} from './sidebar.html.js';

/**
 * Number of settings sections to show when "More settings" is collapsed.
 */
const MAX_SECTIONS_TO_SHOW: number = 6;

export interface PrintPreviewSidebarElement {
  $: {
    destinationSettings: PrintPreviewDestinationSettingsElement,
  };
}

const PrintPreviewSidebarElementBase = CrContainerShadowMixin(
    WebUiListenerMixin(SettingsMixin(DarkModeMixin(PolymerElement))));

export class PrintPreviewSidebarElement extends PrintPreviewSidebarElementBase {
  static get is() {
    return 'print-preview-sidebar';
  }

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

  static get properties() {
    return {
      controlsManaged: Boolean,

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

      destinationState: {
        type: Number,
        notify: true,
      },

      error: {
        type: Number,
        notify: true,
      },

      isPdf: Boolean,

      pageCount: Number,

      state: {
        type: Number,
        observer: 'onStateChanged_',
      },

      controlsDisabled_: {
        type: Boolean,
        computed: 'computeControlsDisabled_(state)',
      },

      maxSheets: Number,

      sheetCount_: {
        type: Number,
        computed: 'computeSheetCount_(' +
            'settings.pages.*, settings.duplex.*, settings.copies.*)',
      },

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

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

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

      shouldShowMoreSettings_: {
        type: Boolean,
        computed: 'computeShouldShowMoreSettings_(settings.pages.available, ' +
            'settings.copies.available, settings.layout.available, ' +
            'settings.color.available, settings.mediaSize.available, ' +
            'settings.dpi.available, settings.margins.available, ' +
            'settings.pagesPerSheet.available, settings.scaling.available, ' +
            'settings.duplex.available, settings.otherOptions.available, ' +
            'settings.vendorItems.available)',
      },

      // <if expr="is_chromeos">
      isPinValid_: {
        type: Boolean,
        value: true,
      },
      // </if>
    };
  }

  controlsManaged: boolean;
  destination: Destination|null;
  destinationState: DestinationState;
  error: Error;
  isPdf: boolean;
  pageCount: number;
  state: State;
  private controlsDisabled_: boolean;
  private firstLoad_: boolean;
  private isInAppKioskMode_: boolean;
  private settingsExpandedByUser_: boolean;
  private sheetCount_: number;
  private shouldShowMoreSettings_: boolean;
  // <if expr="is_chromeos">
  private isPinValid_: boolean;
  // </if>

  /**
   * @param defaultPrinter The system default printer ID.
   * @param serializedDestinationSelectionRulesStr String with rules
   *     for selecting the default destination.
   * @param pdfPrinterDisabled Whether the PDF printer is disabled.
   * @param isDriveMounted Whether Google Drive is mounted. Only used
        on Chrome OS.
   */
  init(
      appKioskMode: boolean, defaultPrinter: string,
      serializedDestinationSelectionRulesStr: string|null,
      pdfPrinterDisabled: boolean, isDriveMounted: boolean) {
    this.isInAppKioskMode_ = appKioskMode;
    pdfPrinterDisabled = this.isInAppKioskMode_ || pdfPrinterDisabled;

    // 'Save to Google Drive' is almost the same as PDF printing. The only
    // difference is the default location shown in the file picker when user
    // clicks 'Save'. Therefore, we should disable the 'Save to Google Drive'
    // destination if the user should be blocked from using PDF printing.
    const saveToDriveDisabled = pdfPrinterDisabled || !isDriveMounted;
    this.$.destinationSettings.init(
        defaultPrinter, pdfPrinterDisabled, saveToDriveDisabled,
        serializedDestinationSelectionRulesStr);
  }

  /**
   * @return Whether the controls should be disabled.
   */
  private computeControlsDisabled_(): boolean {
    return this.state !== State.READY;
  }

  /**
   * @return The number of sheets that will be printed.
   */
  private computeSheetCount_(): number {
    let sheets = (this.getSettingValue('pages') as number[]).length;
    if (this.getSettingValue('duplex')) {
      sheets = Math.ceil(sheets / 2);
    }
    return sheets * (this.getSettingValue('copies') as number);
  }

  /**
   * @return Whether to show the "More settings" link.
   */
  private computeShouldShowMoreSettings_(): boolean {
    // Destination settings is always available. See if the total number of
    // available sections exceeds the maximum number to show.
    const keys: Array<keyof Settings> = [
      'pages',
      'copies',
      'layout',
      'color',
      'mediaSize',
      'margins',
      'color',
      'pagesPerSheet',
      'scaling',
      'dpi',
      'duplex',
      'otherOptions',
      'vendorItems',
    ];
    return keys.reduce((count, setting) => {
      return this.getSetting(setting).available ? count + 1 : count;
    }, 1) > MAX_SECTIONS_TO_SHOW;
  }

  /**
   * @return Whether the "more settings" collapse should be expanded.
   */
  private shouldExpandSettings_(): boolean {
    if (this.settingsExpandedByUser_ === undefined ||
        this.shouldShowMoreSettings_ === undefined) {
      return false;
    }

    // Expand the settings if the user has requested them expanded or if more
    // settings is not displayed (i.e. less than 6 total settings available).
    return this.settingsExpandedByUser_ || !this.shouldShowMoreSettings_;
  }

  private onPrintButtonFocused_() {
    this.firstLoad_ = false;
  }

  private onStateChanged_() {
    if (this.state !== State.PRINTING) {
      return;
    }

    if (this.shouldShowMoreSettings_) {
      MetricsContext.printSettingsUi().record(
          this.settingsExpandedByUser_ ?
              PrintSettingsUiBucket.PRINT_WITH_SETTINGS_EXPANDED :
              PrintSettingsUiBucket.PRINT_WITH_SETTINGS_COLLAPSED);
    }
  }

  // <if expr="not is_chromeos">
  /** @return Whether the system dialog link is available. */
  systemDialogLinkAvailable(): boolean {
    const linkContainer =
        this.shadowRoot!.querySelector('print-preview-link-container');
    return !!linkContainer && linkContainer.systemDialogLinkAvailable();
  }
  // </if>

  // <if expr="is_chromeos">
  /**
   * Returns true if at least one non-PDF printer destination is shown in the
   * destination dropdown.
   */
  printerExistsInDisplayedDestinations(): boolean {
    return this.$.destinationSettings.printerExistsInDisplayedDestinations();
  }
  // </if>
}

declare global {
  interface HTMLElementTagNameMap {
    'print-preview-sidebar': PrintPreviewSidebarElement;
  }
}

customElements.define(
    PrintPreviewSidebarElement.is, PrintPreviewSidebarElement);