chromium/chrome/browser/resources/ash/settings/os_people_page/os_sync_controls_subpage.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/ash/common/cr_elements/cr_toggle/cr_toggle.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_vars.css.js';
import 'chrome://resources/ash/common/cr_elements/localized_link/localized_link.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import '../settings_shared.css.js';

import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {WebUiListenerMixin} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {assertExists} from '../assert_extras.js';
import {DeepLinkingMixin} from '../common/deep_linking_mixin.js';
import {RouteObserverMixin} from '../common/route_observer_mixin.js';
import {Setting} from '../mojom-webui/setting.mojom-webui.js';
import {Route, routes} from '../router.js';

import {OsSyncBrowserProxy, OsSyncBrowserProxyImpl, OsSyncPrefs} from './os_sync_browser_proxy.js';
import {getTemplate} from './os_sync_controls_subpage.html.js';

/**
 * Names of the radio buttons which allow the user to choose their data sync
 * mechanism.
 */
export enum RadioButtonNames {
  SYNC_EVERYTHING = 'sync-everything',
  CUSTOMIZE_SYNC = 'customize-sync',
}

/**
 * Names of the individual data type properties to be cached from
 * OsSyncPrefs when the user checks 'Sync All'.
 */
const SyncPrefsIndividualDataTypes: Array<keyof OsSyncPrefs> = [
  'osAppsSynced',
  'osPreferencesSynced',
  'osWifiConfigurationsSynced',

  // Note: Wallpaper uses a different naming scheme because it's stored as its
  // own separate pref instead of through the sync service.
  'wallpaperEnabled',
];

/**
 * TODO(https://crbug.com/1294178): Consider merging this with sync_controls.
 * @fileoverview
 * 'os-sync-controls-subpage' contains all OS sync data type controls.
 */
const OsSyncControlsSubpageElementBase = DeepLinkingMixin(
    WebUiListenerMixin(RouteObserverMixin(I18nMixin(PolymerElement))));

export class OsSyncControlsSubpageElement extends
    OsSyncControlsSubpageElementBase {
  static get is() {
    return 'os-sync-controls-subpage' as const;
  }

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

  static get properties() {
    return {
      hidden: {
        type: Boolean,
        value: true,
        computed: 'syncControlsHidden_(osSyncPrefs)',
        reflectToAttribute: true,
      },

      /**
       * The current OS sync preferences. Cached so we can restore individual
       * toggle state when turning "sync everything" on and off, without
       * affecting the underlying chrome prefs.
       */
      osSyncPrefs: Object,

      areDataTypeTogglesDisabled_: {
        type: Boolean,
        value: true,
        computed: `computeDataTypeTogglesDisabled_(osSyncPrefs.syncAllOsTypes)`,
      },

      /**
       * Whether to show the new UI for OS Sync Settings and
       * Browser Sync Settings  which include sublabel and
       * Apps toggle shared between Ash and Lacros.
       */
      showSyncSettingsRevamp_: {
        type: Boolean,
        value: loadTimeData.getBoolean('appsToggleSharingEnabled'),
        readOnly: true,
      },

      /**
       * Used by DeepLinkingMixin to focus this page's deep links.
       */
      supportedSettingIds: {
        type: Object,
        value: () => new Set<Setting>([Setting.kSplitSyncOnOff]),
      },
    };
  }

  private areDataTypeTogglesDisabled_: boolean;
  private showSyncSettingsRevamp_: boolean;
  private supportedSettingsIds: Set<Setting>;
  private browserProxy_: OsSyncBrowserProxy;
  private osSyncPrefs: OsSyncPrefs|undefined;
  private cachedOsSyncPrefs_: Partial<Record<keyof OsSyncPrefs, any>>|null;

  constructor() {
    super();
    this.browserProxy_ = OsSyncBrowserProxyImpl.getInstance();

    /**
     * Caches the individually selected synced data types. This is used to
     * be able to restore the selections after checking and unchecking Sync All.
     */
    this.cachedOsSyncPrefs_ = null;
  }

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

    this.addWebUiListener(
        'os-sync-prefs-changed', this.handleOsSyncPrefsChanged_.bind(this));
  }

  /**
   * RouteObserverMixin override
   */
  override currentRouteChanged(newRoute: Route, oldRoute: Route): void {
    if (newRoute === routes.OS_SYNC) {
      this.browserProxy_.didNavigateToOsSyncPage();
      this.attemptDeepLink();
    }
    if (oldRoute === routes.OS_SYNC) {
      this.browserProxy_.didNavigateAwayFromOsSyncPage();
    }
  }

  /**
   * Handler for when the sync preferences are updated.
   */
  private handleOsSyncPrefsChanged_(osSyncPrefs: OsSyncPrefs): void {
    this.osSyncPrefs = osSyncPrefs;

    // If apps are not registered or synced, force wallpaper off.
    if (!this.osSyncPrefs.osPreferencesSynced) {
      this.set('osSyncPrefs.wallpaperEnabled', false);
    }
  }

  /**
   * Computed binding returning the selected sync data radio button.
   */
  private selectedSyncDataRadio_(): RadioButtonNames {
    assertExists(this.osSyncPrefs);
    return this.osSyncPrefs.syncAllOsTypes ? RadioButtonNames.SYNC_EVERYTHING :
                                             RadioButtonNames.CUSTOMIZE_SYNC;
  }

  /**
   * Called when the sync data radio button selection changes.
   */
  private onSyncDataRadioSelectionChanged_(event: CustomEvent<{value: string}>):
      void {
    assertExists(this.osSyncPrefs);
    const syncAllDataTypes =
        event.detail.value === RadioButtonNames.SYNC_EVERYTHING;
    this.set('osSyncPrefs.syncAllOsTypes', syncAllDataTypes);
    if (syncAllDataTypes) {
      // Cache the previously selected preference before checking every box.
      this.cachedOsSyncPrefs_ = {};
      for (const dataType of SyncPrefsIndividualDataTypes) {
        // These are all booleans, so this shallow copy is sufficient.
        this.cachedOsSyncPrefs_[dataType] = this.osSyncPrefs[dataType];
        this.set(['osSyncPrefs', dataType], true);
      }
    } else if (this.cachedOsSyncPrefs_) {
      // Restore the previously selected preference.
      for (const dataType of SyncPrefsIndividualDataTypes) {
        this.set(['osSyncPrefs', dataType], this.cachedOsSyncPrefs_[dataType]);
      }
    }

    this.sendOsSyncDatatypes_();
  }

  /**
   * Called when the link to the browser's sync settings is clicked.
   */
  private onBrowserSyncSettingsClicked_(event: CustomEvent<{event: Event}>):
      void {
    // Prevent the default link click behavior.
    event.detail.event.preventDefault();

    // Programmatically open browser's sync settings.
    chrome.send('OpenBrowserSyncSettings');
  }

  /**
   * Handler for when any sync data type checkbox is changed.
   */
  private onSingleSyncDataTypeChanged_(): void {
    this.sendOsSyncDatatypes_();
  }

  /**
   * Handler for changes to the settings sync state; settings have a special
   * handler instead of relying on onSingleSyncDataTypeChanged_() because
   * wallpaper has a dependency on it.
   */
  private onSettingsSyncedChanged_(): void {
    this.set(
        'osSyncPrefs.wallpaperEnabled', this.osSyncPrefs!.osPreferencesSynced);

    this.onSingleSyncDataTypeChanged_();
  }

  /**
   * Sends the osSyncPrefs dictionary back to the C++ handler.
   */
  private sendOsSyncDatatypes_(): void {
    assertExists(this.osSyncPrefs);
    this.browserProxy_.setOsSyncDatatypes(this.osSyncPrefs);
  }

  /**
   * Whether the sync data type toggles should be disabled.
   */
  private computeDataTypeTogglesDisabled_(): boolean {
    return this.osSyncPrefs !== undefined && this.osSyncPrefs!.syncAllOsTypes;
  }

  /**
   * Whether the sync controls are hidden.
   */
  private syncControlsHidden_(): boolean {
    // Hide everything until the initial prefs are received from C++,
    // otherwise there is a visible layout reshuffle on first load.
    return !this.osSyncPrefs;
  }

  /**
   * Whether the wallpaper checkbox and label should be
   *     disabled.
   */
  private shouldWallpaperSyncSectionBeDisabled_(): boolean {
    return this.areDataTypeTogglesDisabled_ || !this.osSyncPrefs ||
        !this.osSyncPrefs.osPreferencesSynced;
  }
}

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

customElements.define(
    OsSyncControlsSubpageElement.is, OsSyncControlsSubpageElement);