chromium/ash/webui/personalization_app/resources/js/user/user_preview_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 user-preview component displays information about the
 * current user.
 */

import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import 'chrome://resources/polymer/v3_0/paper-ripple/paper-ripple.js';

import {isNonEmptyArray} from 'chrome://resources/ash/common/sea_pen/sea_pen_utils.js';
import {assert} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {mojoString16ToString} from 'chrome://resources/js/mojo_type_util.js';
import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';

import {UserImage, UserInfo} from '../../personalization_app.mojom-webui.js';
import {Paths, PersonalizationRouterElement} from '../personalization_router_element.js';
import {WithPersonalizationStore} from '../personalization_store.js';
import {isNonEmptyString} from '../utils.js';

import {initializeUserData} from './user_controller.js';
import {UserImageObserver} from './user_image_observer.js';
import {getUserProvider} from './user_interface_provider.js';
import {getTemplate} from './user_preview_element.html.js';
import {selectUserImageUrl} from './user_selectors.js';
import {getAvatarUrl} from './utils.js';

class AvatarChangedEvent extends CustomEvent<{text: string}> {
  constructor() {
    super(
        'iron-announce',
        {
          bubbles: true,
          composed: true,
          detail: {text: loadTimeData.getString('ariaAnnounceAvatarChanged')},
        },
    );
  }
}

export class UserPreviewElement extends WithPersonalizationStore {
  static get is() {
    return 'user-preview';
  }

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

  static get properties() {
    return {
      path: String,
      info_: Object,
      image_: Object,
      imageUrl_: {
        type: String,
        observer: 'onImageUrlChanged_',
        value: null,
      },
      imageIsEnterpriseManaged_: {
        type: Boolean,
        value: null,
      },
    };
  }

  private path: string;
  private info_: UserInfo|null;
  private image_: UserImage|null;
  private imageUrl_: Url|null;
  private imageIsEnterpriseManaged_: boolean|null;

  override ready() {
    super.ready();
    IronA11yAnnouncer.requestAvailability();
  }

  override connectedCallback() {
    super.connectedCallback();
    UserImageObserver.initUserImageObserverIfNeeded();
    this.watch<UserPreviewElement['info_']>('info_', state => state.user.info);
    this.watch<UserPreviewElement['image_']>(
        'image_', state => state.user.image);
    this.watch<UserPreviewElement['imageUrl_']>(
        'imageUrl_', selectUserImageUrl);
    this.watch<UserPreviewElement['imageIsEnterpriseManaged_']>(
        'imageIsEnterpriseManaged_',
        state => state.user.imageIsEnterpriseManaged);
    this.updateFromStore();
    initializeUserData(getUserProvider(), this.getStore());
  }

  private onClickUserSubpageLink_() {
    PersonalizationRouterElement.instance().goToRoute(Paths.USER);
  }

  private onImageUrlChanged_(value: Url|null, old: Url|null): void {
    if (value && old) {
      this.dispatchEvent(new AvatarChangedEvent());
    }
  }

  private onImgError_(e: Event) {
    const divElement = e.currentTarget as HTMLDivElement;
    divElement.setAttribute('hidden', 'true');
  }

  private shouldShowMainPageView_(path: string, isEnterpriseManaged: boolean):
      boolean {
    return path === Paths.ROOT && !isEnterpriseManaged;
  }

  private shouldShowSubpageView_(path: string, isEnterpriseManaged: boolean):
      boolean {
    return path === Paths.USER && !isEnterpriseManaged;
  }

  private getImageContainerAriaLabel_(
      path: string, isEnterpriseManaged: boolean): string|boolean {
    if (this.shouldShowMainPageView_(path, isEnterpriseManaged) ||
        isEnterpriseManaged) {
      return this.i18n('ariaLabelChangeAvatar');
    }
    if (this.shouldShowSubpageView_(path, isEnterpriseManaged)) {
      return this.i18n('ariaLabelCurrentAvatar');
    }
    // No aria-label attribute will be set.
    return false;
  }

  private getImageAlt_(image: UserImage|null): string {
    if (!image || image.invalidImage) {
      return '';
    }
    if (image.defaultImage) {
      return mojoString16ToString(image.defaultImage.title);
    }
    if (image.externalImage) {
      return this.i18n('lastExternalImageTitle');
    }
    if (image.profileImage) {
      return this.i18n('googleProfilePhoto');
    }

    console.error('Unknown image type received');
    return '';
  }

  /**
   * Creates style string with static background image url for default user
   * images . Static image loads faster and will provide a smooth experience
   * when the animated image complete loading.
   */
  private getImgBackgroundStyle_(url: string|null): string {
    // Only add background image for default user images.
    if (!this.image_ || this.image_.invalidImage || !this.image_.defaultImage ||
        !url) {
      return '';
    }
    assert(
        !url.startsWith('chrome://image/'), 'The url should not be sanitized');
    return `background-image: url('${
        getAvatarUrl(url, /*staticEncode=*/ true)}')`;
  }

  private getAvatarUrl_(url: string): string {
    return getAvatarUrl(url);
  }

  private shouldShowDeprecatedImageSourceInfo_(image: UserImage|null): boolean {
    return !!image && !!image.defaultImage && !!image.defaultImage.sourceInfo &&
        isNonEmptyArray(image.defaultImage.sourceInfo.author.data) &&
        isNonEmptyString(image.defaultImage.sourceInfo.website.url);
  }

  private getDeprecatedAuthor_(image: UserImage): string {
    assert(
        image && image.defaultImage && image.defaultImage.sourceInfo,
        'only called for deprecated default images with sourceInfo');
    return mojoString16ToString(image.defaultImage.sourceInfo.author);
  }

  private getDeprecatedWebsite_(image: UserImage): string {
    assert(
        image && image.defaultImage && image.defaultImage.sourceInfo,
        'only called for deprecated default images with sourceInfo');
    return image.defaultImage.sourceInfo.website.url;
  }
}

customElements.define(UserPreviewElement.is, UserPreviewElement);