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

// Copyright 2023 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/polymer/v3_0/iron-icon/iron-icon.js';
import './print_preview_shared.css.js';

import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {MetricsContext, PrintPreviewLaunchSourceBucket} from '../metrics.js';
import type {NativeLayer} from '../native_layer.js';
import {NativeLayerImpl} from '../native_layer.js';
import {NativeLayerCrosImpl} from '../native_layer_cros.js';

import {getTemplate} from './printer_setup_info_cros.html.js';

/**
 * @fileoverview PrintPreviewPrinterSetupInfoCrosElement
 * This element provides contextual instructions to help users navigate
 * to printer settings based on the state of printers available in
 * print-preview. Element will use NativeLayer to open the correct printer
 * settings interface.
 */

const PrintPreviewPrinterSetupInfoCrosElementBase = I18nMixin(PolymerElement);

// Minimum values used to hide the illustration when the preview area is reduced
// to a small size.
const MIN_SHOW_ILLUSTRATION_HEIGHT = 400;
const MIN_SHOW_ILLUSTRATION_WIDTH = 250;

export enum PrinterSetupInfoInitiator {
  PREVIEW_AREA,
  DESTINATION_DIALOG_CROS,
}

export enum PrinterSetupInfoMessageType {
  NO_PRINTERS,
  PRINTER_OFFLINE,
}

interface PrinterSetupInfoMessageData {
  detailKey: string;
  headingKey: string;
}

const MESSAGE_TYPE_LOCALIZED_STRINGS_MAP =
    new Map<PrinterSetupInfoMessageType, PrinterSetupInfoMessageData>([
      [
        PrinterSetupInfoMessageType.NO_PRINTERS,
        {
          headingKey: 'printerSetupInfoMessageHeadingNoPrintersText',
          detailKey: 'printerSetupInfoMessageDetailNoPrintersText',
        },
      ],
      [
        PrinterSetupInfoMessageType.PRINTER_OFFLINE,
        {
          headingKey: 'printerSetupInfoMessageHeadingPrinterOfflineText',
          detailKey: 'printerSetupInfoMessageDetailPrinterOfflineText',
        },
      ],
    ]);

export class PrintPreviewPrinterSetupInfoCrosElement extends
    PrintPreviewPrinterSetupInfoCrosElementBase {
  static get is() {
    return 'print-preview-printer-setup-info-cros' as const;
  }

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

  static get properties() {
    return {
      messageType: {
        type: Number,
        value: PrinterSetupInfoMessageType.NO_PRINTERS,
      },

      initiator: Number,

      showManagePrintersButton: Boolean,

      showIllustration: Boolean,
    };
  }

  messageType: PrinterSetupInfoMessageType;
  private initiator: PrinterSetupInfoInitiator;
  private nativeLayer: NativeLayer;
  private metricsContext: MetricsContext;
  private showManagePrintersButton: boolean = false;
  private showIllustration: boolean = true;
  private resizeObserver: ResizeObserver;

  override connectedCallback() {
    super.connectedCallback();
    this.nativeLayer = NativeLayerImpl.getInstance();
    this.metricsContext =
        MetricsContext.getLaunchPrinterSettingsMetricsContextCros();
    NativeLayerCrosImpl.getInstance().getShowManagePrinters().then(
        (show: boolean) => {
          this.showManagePrintersButton = show;
        });

    // If this is Print Preview, observe the window resizing to know when to
    // hide the illustration.
    if (this.initiator === PrinterSetupInfoInitiator.PREVIEW_AREA) {
      this.startResizeObserver();
    }
  }

  override disconnectedCallback(): void {
    super.disconnectedCallback();

    if (this.initiator === PrinterSetupInfoInitiator.PREVIEW_AREA) {
      this.resizeObserver.disconnect();
    }
  }

  private getMessageDetail(): string {
    const messageData =
        MESSAGE_TYPE_LOCALIZED_STRINGS_MAP.get(this.messageType);
    assert(messageData);
    return this.i18n(messageData!.detailKey);
  }

  private getMessageHeading(): string {
    const messageData =
        MESSAGE_TYPE_LOCALIZED_STRINGS_MAP.get(this.messageType);
    assert(messageData);
    return this.i18n(messageData!.headingKey);
  }

  private onManagePrintersClicked(): void {
    this.nativeLayer.managePrinters();
    switch (this.initiator) {
      case PrinterSetupInfoInitiator.PREVIEW_AREA:
        this.metricsContext.record(
            PrintPreviewLaunchSourceBucket.PREVIEW_AREA_CONNECTION_ERROR);
        break;
      case PrinterSetupInfoInitiator.DESTINATION_DIALOG_CROS:
        // `<print-preview-printer-setup-info-cros>` is only displayed when
        // there are no printers.
        this.metricsContext.record(
            PrintPreviewLaunchSourceBucket.DESTINATION_DIALOG_CROS_NO_PRINTERS);
        break;
      default:
        assertNotReached();
    }
  }

  private setShowIllustration(): void {
    assert(this.initiator === PrinterSetupInfoInitiator.PREVIEW_AREA);

    // Only show the illustration if the parent element's width and height are
    // wide enough.
    const parentDiv = this.getPreviewAreaParentDiv();
    this.showIllustration =
        parentDiv.offsetHeight >= MIN_SHOW_ILLUSTRATION_HEIGHT &&
        parentDiv.offsetWidth >= MIN_SHOW_ILLUSTRATION_WIDTH;
  }

  private getPreviewAreaParentDiv(): HTMLElement {
    assert(this.initiator === PrinterSetupInfoInitiator.PREVIEW_AREA);

    const parentShadowRoot = this.shadowRoot!.host.getRootNode() as ShadowRoot;
    assert(parentShadowRoot);
    const previewContainer =
        parentShadowRoot!.querySelector<HTMLElement>('.preview-area-message');
    assert(previewContainer);
    return previewContainer;
  }

  private startResizeObserver(): void {
    // Set timeout to 0 to delay the callback action to the next event cycle.
    this.resizeObserver = new ResizeObserver(
        () => setTimeout(() => this.setShowIllustration(), 0));
    this.resizeObserver.observe(this.getPreviewAreaParentDiv());
  }

  setInitiatorForTesting(
      initiator: PrinterSetupInfoInitiator,
      startResizeObserver: boolean): void {
    this.initiator = initiator;
    if (this.initiator === PrinterSetupInfoInitiator.PREVIEW_AREA) {
      if (startResizeObserver) {
        this.startResizeObserver();
      } else {
        // Most tests don't need an resize observer with an active callback.
        this.resizeObserver = new ResizeObserver(() => {});
      }
    }
  }
}

declare global {
  interface HTMLElementTagNameMap {
    [PrintPreviewPrinterSetupInfoCrosElement.is]:
        PrintPreviewPrinterSetupInfoCrosElement;
  }
}

customElements.define(
    PrintPreviewPrinterSetupInfoCrosElement.is,
    PrintPreviewPrinterSetupInfoCrosElement);