chromium/ash/webui/personalization_app/resources/js/ambient/ambient_subpage_element.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.

/**
 * @fileoverview The ambient-subpage component displays the main content of
 * the ambient mode settings.
 */

import 'chrome://resources/ash/common/personalization/common.css.js';
import './albums_subpage_element.js';
import './ambient_preview_small_element.js';
import './ambient_theme_list_element.js';
import './ambient_weather_element.js';
import './toggle_row_element.js';
import './topic_source_list_element.js';

import {assert} from 'chrome://resources/js/assert.js';
import {afterNextRender} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {AmbientModeAlbum, AmbientTheme, TemperatureUnit, TopicSource} from '../../personalization_app.mojom-webui.js';
import {isAmbientModeAllowed} from '../load_time_booleans.js';
import {Paths, ScrollableTarget} from '../personalization_router_element.js';
import {WithPersonalizationStore} from '../personalization_store.js';

import {dismissTimeOfDayBanner, setAmbientModeEnabled} from './ambient_controller.js';
import {getAmbientProvider} from './ambient_interface_provider.js';
import {AmbientObserver} from './ambient_observer.js';
import {getTemplate} from './ambient_subpage_element.html.js';

export class AmbientSubpageElement extends WithPersonalizationStore {
  static get is() {
    return 'ambient-subpage';
  }

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

  static get properties() {
    return {
      path: Paths,
      queryParams: Object,
      albums_: {
        type: Array,
        value: null,
      },
      ambientTheme_: {
        type: Object,
        value: null,
      },
      ambientModeEnabled_: {
        type: Boolean,
        value: null,
        observer: 'onAmbientModeEnabledChanged_',
      },
      duration_: {
        type: Number,
        value: null,
      },
      temperatureUnit_: {
        type: Number,
        value: null,
      },
      topicSource_: {
        type: Number,
        value: null,
      },
      loading_: {
        type: Boolean,
        computed:
            'computeLoading_(ambientModeEnabled_, albums_, temperatureUnit_, topicSource_, isOnline_)',
        observer: 'onLoadingChanged_',
      },
      isOnline_: {
        type: Boolean,
        value() {
          return window.navigator.onLine;
        },
      },
    };
  }

  path: Paths;
  queryParams: Record<string, string>;
  private albums_: AmbientModeAlbum[]|null;
  private ambientModeEnabled_: boolean|null;
  private ambientTheme_: AmbientTheme|null;
  private duration_: number|null;
  private temperatureUnit_: TemperatureUnit|null;
  private topicSource_: TopicSource|null;
  private isOnline_: boolean;

  // Refetch albums if the user is currently viewing ambient subpage, focuses
  // another window, and then re-focuses personalization app.
  private onFocus_ = () => getAmbientProvider().fetchSettingsAndAlbums();

  override ready() {
    // Pre-scroll to prevent visual jank when focusing the toggle row.
    window.scrollTo(0, 0);
    super.ready();
    afterNextRender(this, () => {
      const elem = this.shadowRoot!.getElementById('ambientToggleRow');
      if (elem) {
        // Focus the toggle row to inform screen reader users of the current
        // state.
        elem.focus();
      }
    });

    window.addEventListener('online', () => {
      this.isOnline_ = true;
    });
    window.addEventListener('offline', () => {
      this.isOnline_ = false;
    });
  }

  override connectedCallback() {
    assert(
        isAmbientModeAllowed(),
        'ambient subpage should not load if ambient not allowed');

    super.connectedCallback();
    AmbientObserver.initAmbientObserverIfNeeded();
    this.watch<AmbientSubpageElement['albums_']>(
        'albums_', state => state.ambient.albums);
    this.watch<AmbientSubpageElement['ambientModeEnabled_']>(
        'ambientModeEnabled_', state => state.ambient.ambientModeEnabled);
    this.watch<AmbientSubpageElement['ambientTheme_']>(
        'ambientTheme_', state => state.ambient.ambientTheme);
    this.watch<AmbientSubpageElement['temperatureUnit_']>(
        'temperatureUnit_', state => state.ambient.temperatureUnit);
    this.watch<AmbientSubpageElement['topicSource_']>(
        'topicSource_', state => state.ambient.topicSource);
    this.watch<AmbientSubpageElement['duration_']>(
        'duration_', state => state.ambient.duration);
    this.updateFromStore();

    getAmbientProvider().setPageViewed();

    window.addEventListener('focus', this.onFocus_);
  }

  override disconnectedCallback() {
    super.disconnectedCallback();
    window.removeEventListener('focus', this.onFocus_);
  }

  // Scroll down to the topic source list.
  private scrollToTopicSourceList_() {
    const elem = this.shadowRoot!.querySelector('topic-source-list');
    if (elem) {
      elem.scrollIntoView();
      elem.focus();
    }
  }

  private onAmbientModeEnabledChanged_(value: boolean) {
    if (value) {
      // Dismisses the banner after the user visits this subpage and ambient
      // mode is enabled.
      dismissTimeOfDayBanner(this.getStore());
    }
  }

  private onLoadingChanged_(value: boolean) {
    if (!value && !!this.queryParams &&
        this.queryParams['scrollTo'] === ScrollableTarget.TOPIC_SOURCE_LIST) {
      afterNextRender(this, () => this.scrollToTopicSourceList_());
    }
  }

  private setAmbientModeEnabled_(ambientModeEnabled: boolean) {
    setAmbientModeEnabled(
        ambientModeEnabled, getAmbientProvider(), this.getStore());
  }

  private temperatureUnitToString_(temperatureUnit: TemperatureUnit): string {
    return temperatureUnit != null ? temperatureUnit.toString() : '';
  }

  private hasGooglePhotosAlbums_(): boolean {
    return (this.albums_ || [])
        .some(album => album.topicSource === TopicSource.kGooglePhotos);
  }

  private getTopicSource_(): TopicSource|null {
    if (!this.queryParams) {
      return null;
    }

    const topicSource = parseInt(this.queryParams['topicSource'], 10);
    if (isNaN(topicSource)) {
      return null;
    }

    return topicSource;
  }

  // Null result indicates albums are loading.
  private getAlbums_(): AmbientModeAlbum[]|null {
    if (!this.queryParams || this.albums_ === null) {
      return null;
    }

    const topicSource = this.getTopicSource_();
    return (this.albums_ || []).filter(album => {
      return album.topicSource === topicSource;
    });
  }

  private shouldShowMainSettings_(path: Paths): boolean {
    return path === Paths.AMBIENT;
  }

  private shouldShowAlbums_(path: Paths): boolean {
    return path === Paths.AMBIENT_ALBUMS;
  }

  private computeLoading_(): boolean {
    return this.ambientModeEnabled_ === null || this.albums_ === null ||
        this.topicSource_ === null || this.temperatureUnit_ === null ||
        this.duration_ === null || !this.isOnline_;
  }

  private getPlaceholders_(x: number): number[] {
    return new Array(x).fill(0);
  }
}

customElements.define(AmbientSubpageElement.is, AmbientSubpageElement);