| // Copyright 2022 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| /** |
| * @fileoverview The avatar-list component displays the list of avatar images |
| * that the user can select from. |
| */ |
| import { assert } from 'chrome://resources/js/assert_ts.js'; |
| import { isNonEmptyArray, isSelectionEvent } from '../../common/utils.js'; |
| import { WithPersonalizationStore } from '../personalization_store.js'; |
| import { decodeString16 } from '../utils.js'; |
| import { getTemplate } from './avatar_list_element.html.js'; |
| import { fetchDefaultUserImages } from './user_controller.js'; |
| import { getUserProvider } from './user_interface_provider.js'; |
| import { selectLastExternalUserImageUrl } from './user_selectors.js'; |
| var OptionId; |
| (function (OptionId) { |
| OptionId["LAST_EXTERNAL_IMAGE"] = "lastExternalImage"; |
| OptionId["OPEN_CAMERA"] = "openCamera"; |
| OptionId["OPEN_VIDEO"] = "openVideo"; |
| OptionId["PROFILE_IMAGE"] = "profileImage"; |
| OptionId["OPEN_FOLDER"] = "openFolder"; |
| })(OptionId || (OptionId = {})); |
| function isDefaultOption(option) { |
| return option && |
| typeof option.defaultImageIndex === 'number'; |
| } |
| export class AvatarList extends WithPersonalizationStore { |
| static get is() { |
| return 'avatar-list'; |
| } |
| static get template() { |
| return getTemplate(); |
| } |
| static get properties() { |
| return { |
| defaultUserImages_: Array, |
| profileImage_: Object, |
| image_: Object, |
| lastExternalUserImageUrl_: { |
| type: Object, |
| observer: 'onLastExternalUserImageUrlChanged_', |
| }, |
| /** The presence of a device camera. */ |
| isCameraPresent_: { |
| type: Boolean, |
| value: false, |
| observer: 'onIsCameraPresentChanged_', |
| }, |
| /** Whether the camera is off, photo mode, or video mode. */ |
| cameraMode_: { |
| type: String, |
| value: null, |
| }, |
| /** |
| * List of options to be displayed to the user. |
| */ |
| options_: { |
| type: Array, |
| value: [], |
| }, |
| }; |
| } |
| static get observers() { |
| return [ |
| 'updateOptions_(isCameraPresent_, profileImage_, lastExternalUserImageUrl_, defaultUserImages_)', |
| ]; |
| } |
| connectedCallback() { |
| super.connectedCallback(); |
| this.watch('defaultUserImages_', state => state.user.defaultUserImages); |
| this.watch('profileImage_', state => state.user.profileImage); |
| this.watch('isCameraPresent_', state => state.user.isCameraPresent); |
| this.watch('image_', state => state.user.image); |
| this.watch('lastExternalUserImageUrl_', selectLastExternalUserImageUrl); |
| this.updateFromStore(); |
| fetchDefaultUserImages(getUserProvider(), this.getStore()); |
| } |
| /** Invoked to update |options_|. */ |
| updateOptions_(isCameraPresent, profileImage, lastExternalUserImageUrl, defaultUserImages) { |
| const options = []; |
| if (isCameraPresent) { |
| // Add camera and video options. |
| options.push({ |
| id: OptionId.OPEN_CAMERA, |
| class: 'avatar-button-container', |
| imgSrc: '', |
| icon: 'personalization:camera', |
| title: this.i18n('takeWebcamPhoto'), |
| }); |
| options.push({ |
| id: OptionId.OPEN_VIDEO, |
| class: 'avatar-button-container', |
| icon: 'personalization:loop', |
| title: this.i18n('takeWebcamVideo'), |
| }); |
| } |
| // Add open folder option. |
| options.push({ |
| id: OptionId.OPEN_FOLDER, |
| class: 'avatar-button-container', |
| icon: 'personalization:folder', |
| title: this.i18n('chooseAFile'), |
| }); |
| if (profileImage) { |
| options.push({ |
| id: OptionId.PROFILE_IMAGE, |
| class: 'image-container', |
| imgSrc: profileImage.url, |
| icon: 'personalization:checkmark', |
| title: this.i18n('googleProfilePhoto'), |
| }); |
| } |
| if (lastExternalUserImageUrl) { |
| options.push({ |
| id: OptionId.LAST_EXTERNAL_IMAGE, |
| class: 'image-container', |
| imgSrc: lastExternalUserImageUrl.url, |
| icon: 'personalization:checkmark', |
| title: this.i18n('lastExternalImageTitle'), |
| }); |
| } |
| if (isNonEmptyArray(defaultUserImages)) { |
| defaultUserImages.forEach(defaultImage => { |
| options.push({ |
| id: `defaultUserImage-${defaultImage.index}`, |
| class: 'image-container', |
| imgSrc: defaultImage.url.url, |
| icon: 'personalization:checkmark', |
| title: decodeString16(defaultImage.title), |
| defaultImageIndex: defaultImage.index, |
| }); |
| }); |
| } |
| this.updateList( |
| /*propertyPath=*/ 'options_', |
| /*identityGetter=*/ |
| (option) => { |
| switch (option.id) { |
| // LAST_EXTERNAL_IMAGE needs to use imgSrc instead of id. Otherwise |
| // iron-list will not update properly when LAST_EXTERNAL_IMAGE |
| // changes, i.e. when user selects a new file from disk. |
| case OptionId.LAST_EXTERNAL_IMAGE: |
| return option.imgSrc; |
| default: |
| return option.id; |
| } |
| }, |
| /*newList=*/ options, |
| /*identityBasedUpdate=*/ true); |
| } |
| onLastExternalUserImageUrlChanged_(_, old) { |
| if (old && old.url && old.url.startsWith('blob:')) { |
| URL.revokeObjectURL(old.url); |
| } |
| } |
| onOptionSelected_(e) { |
| if (!isSelectionEvent(e)) { |
| return; |
| } |
| const divElement = e.currentTarget; |
| const id = divElement.id; |
| switch (id) { |
| case OptionId.OPEN_CAMERA: |
| this.openCamera_(e); |
| break; |
| case OptionId.OPEN_VIDEO: |
| this.openVideo_(e); |
| break; |
| case OptionId.OPEN_FOLDER: |
| this.onSelectImageFromDisk_(e); |
| break; |
| case OptionId.PROFILE_IMAGE: |
| this.onSelectProfileImage_(e); |
| break; |
| case OptionId.LAST_EXTERNAL_IMAGE: |
| this.onSelectLastExternalUserImage_(e); |
| break; |
| default: |
| this.onSelectDefaultImage_(e); |
| break; |
| } |
| } |
| getImageClassForOption_(option) { |
| if (option.imgSrc) { |
| return ''; |
| } |
| return 'hidden'; |
| } |
| onSelectDefaultImage_(event) { |
| if (!isSelectionEvent(event)) { |
| return; |
| } |
| const id = event.currentTarget.dataset['id']; |
| if (!id) { |
| return; |
| } |
| const index = parseInt(id, 10); |
| getUserProvider().selectDefaultImage(index); |
| } |
| onSelectProfileImage_(event) { |
| if (!isSelectionEvent(event)) { |
| return; |
| } |
| getUserProvider().selectProfileImage(); |
| } |
| onSelectLastExternalUserImage_(event) { |
| if (!isSelectionEvent(event)) { |
| return; |
| } |
| getUserProvider().selectLastExternalUserImage(); |
| } |
| openCamera_(event) { |
| if (!isSelectionEvent(event)) { |
| return; |
| } |
| assert(this.isCameraPresent_, 'Camera needed to record an image'); |
| this.cameraMode_ = "camera" /* CAMERA */; |
| } |
| openVideo_(event) { |
| if (!isSelectionEvent(event)) { |
| return; |
| } |
| assert(this.isCameraPresent_, 'Camera needed to record a video'); |
| this.cameraMode_ = "video" /* VIDEO */; |
| } |
| onIsCameraPresentChanged_(value) { |
| // Potentially hide camera UI if the camera has become unavailable. |
| if (!value) { |
| this.cameraMode_ = null; |
| } |
| } |
| onSelectImageFromDisk_(event) { |
| if (!isSelectionEvent(event)) { |
| return; |
| } |
| getUserProvider().selectImageFromDisk(); |
| } |
| onCameraClosed_() { |
| this.cameraMode_ = null; |
| } |
| getAriaIndex_(i) { |
| return i + 1; |
| } |
| getAriaSelected_(option, image) { |
| if (!option) { |
| return 'false'; |
| } |
| switch (option.id) { |
| case OptionId.OPEN_CAMERA: |
| case OptionId.OPEN_VIDEO: |
| case OptionId.OPEN_FOLDER: |
| return 'false'; |
| case OptionId.PROFILE_IMAGE: |
| return (!!image && !!image.profileImage).toString(); |
| case OptionId.LAST_EXTERNAL_IMAGE: |
| return (!!image && !!image.externalImage).toString(); |
| default: |
| // Handle default user image. |
| assert(isDefaultOption(option)); |
| return (!!image && !!image.defaultImage && |
| image.defaultImage.index === option.defaultImageIndex) |
| .toString(); |
| } |
| } |
| camelToKebab_(className) { |
| return className.replace(/[A-Z]/g, m => '-' + m.toLowerCase()); |
| } |
| getOptionInnerContainerClass_(option, image) { |
| const defaultClass = option ? option.class : 'image-container'; |
| return this.getAriaSelected_(option, image) === 'true' ? |
| `${defaultClass} tast-selected-${this.camelToKebab_(option.id)}` : |
| defaultClass; |
| } |
| } |
| customElements.define(AvatarList.is, AvatarList); |