chromium/ash/webui/diagnostics_ui/resources/network_list.ts

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import './connectivity_card.js';
import './diagnostics_shared.css.js';
import './icons.html.js';
import './network_card.js';

import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {assert} from 'chrome://resources/js/assert.js';
import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
import {afterNextRender, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {ConnectivityCardElement} from './connectivity_card.js';
import {DiagnosticsBrowserProxy, DiagnosticsBrowserProxyImpl} from './diagnostics_browser_proxy.js';
import {getNetworkHealthProvider} from './mojo_interface_provider.js';
import {NetworkCardElement} from './network_card.js';
import {NetworkHealthProviderInterface, NetworkListObserverReceiver} from './network_health_provider.mojom-webui.js';
import {getTemplate} from './network_list.html.js';
import {TestSuiteStatus} from './routine_list_executor.js';

export interface NetworkListElement {
  $: {
    networkListContainer: HTMLDivElement,
  };
}

/**
 * @fileoverview
 * 'network-list' is responsible for displaying Ethernet, Cellular,
 *  and WiFi networks.
 */

const NetworkListElementBase = I18nMixin(PolymerElement);

export class NetworkListElement extends NetworkListElementBase {
  static get is(): 'network-list' {
    return 'network-list' as const;
  }

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

  static get properties(): PolymerElementProperties {
    return {
      testSuiteStatus: {
        type: Number,
        value: TestSuiteStatus.NOT_RUNNING,
      },

      otherNetworkGuids: {
        type: Array,
        value: () => [],
      },

      activeGuid: {
        type: String,
        value: '',
      },

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

      isLoggedIn: {
        type: Boolean,
        value: loadTimeData.getBoolean('isLoggedIn'),
      },

    };
  }

  testSuiteStatus: TestSuiteStatus;
  isActive: boolean;
  protected isLoggedIn: boolean;
  private otherNetworkGuids: string[];
  private activeGuid: string;
  private browserProxy: DiagnosticsBrowserProxy =
      DiagnosticsBrowserProxyImpl.getInstance();
  private networkHealthProvider: NetworkHealthProviderInterface =
      getNetworkHealthProvider();
  private networkListObserverReceiver: NetworkListObserverReceiver|null = null;

  constructor() {
    super();
    this.browserProxy.initialize();
    this.observeNetworkList();
  }

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

    if (this.networkListObserverReceiver) {
      this.networkListObserverReceiver.$.close();
    }
  }

  private observeNetworkList(): void {
    // Calling observeNetworkList will trigger onNetworkListChanged.
    this.networkListObserverReceiver = new NetworkListObserverReceiver(this);

    this.networkHealthProvider.observeNetworkList(
        this.networkListObserverReceiver.$.bindNewPipeAndPassRemote());
  }

  /**
   * Implements NetworkListObserver.onNetworkListChanged
   */
  onNetworkListChanged(networkGuids: string[], activeGuid: string): void {
    // The connectivity-card is responsible for displaying the active network
    // so we need to filter out the activeGuid to avoid displaying a
    // a network-card for it.
    this.otherNetworkGuids = networkGuids.filter(guid => guid !== activeGuid);
    this.activeGuid = activeGuid;
  }

  /**
   * 'navigation-view-panel' is responsible for calling this function when
   * the active page changes.
   */
  onNavigationPageChanged({isActive}: {isActive: boolean}): void {
    this.isActive = isActive;
    // TODO(ashleydp): Update when connectivity/network card's are merged.
    if (isActive) {
      // Focus the first visible card title. If no cards are present,
      // fallback to focusing the element's main container.
      afterNextRender(this, () => {
        if (this.activeGuid) {
          const connectivityCard: ConnectivityCardElement|null =
              this.shadowRoot!.querySelector('connectivity-card');
          assert(connectivityCard);
          const cardTitle: HTMLDivElement|null =
              connectivityCard.shadowRoot!.querySelector('#cardTitle');
          assert(cardTitle);
          cardTitle.focus();
          return;
        } else if (this.otherNetworkGuids.length > 0) {
          const networkCard: NetworkCardElement|null =
              this.shadowRoot!.querySelector('network-card');
          assert(networkCard);
          const cardTitle: HTMLDivElement|null =
              networkCard.shadowRoot!.querySelector('#cardTitle');
          assert(cardTitle);
          cardTitle.focus();
        }
        this.$.networkListContainer.focus();
      });
      // TODO(ashleydp): Remove when a call can be made at a higher component
      // to avoid duplicate code in all navigatable pages.
      this.browserProxy.recordNavigation('connectivity');
    }
  }

  protected getSettingsString(): TrustedHTML {
    return this.i18nAdvanced('settingsLinkText');
  }

  setActiveGuidForTesting(guid: string): void {
    this.activeGuid = guid;
  }

  setIsLoggedInForTesting(state: boolean): void {
    this.isLoggedIn = state;
  }

  getOtherNetworkGuidsForTesting(): string[] {
    return this.otherNetworkGuids;
  }
}

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

customElements.define(NetworkListElement.is, NetworkListElement);