From bca664b5d91db3ddd9482eedc1ae67af002cfb98 Mon Sep 17 00:00:00 2001 From: Evan Hahn <69474926+EvanHahn-Signal@users.noreply.github.com> Date: Tue, 27 Apr 2021 08:20:17 -0500 Subject: [PATCH] Make blurrable --- images/icons/v2/click-outline-24.svg | 1 + stylesheets/components/Avatar.scss | 38 +++++++++++++++++- ts/components/Avatar.stories.tsx | 23 ++++++++++- ts/components/Avatar.tsx | 58 ++++++++++++++++++++++++---- 4 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 images/icons/v2/click-outline-24.svg diff --git a/images/icons/v2/click-outline-24.svg b/images/icons/v2/click-outline-24.svg new file mode 100644 index 000000000000..7b25b1832629 --- /dev/null +++ b/images/icons/v2/click-outline-24.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/stylesheets/components/Avatar.scss b/stylesheets/components/Avatar.scss index c80e566388c0..ed68e2f358a2 100644 --- a/stylesheets/components/Avatar.scss +++ b/stylesheets/components/Avatar.scss @@ -8,6 +8,7 @@ justify-content: center; line-height: 0; overflow: hidden; + position: relative; user-select: none; vertical-align: middle; @@ -27,12 +28,45 @@ } } - img { - object-fit: cover; + &__image { + background-size: cover; + display: flex; height: 100%; + transition: filter 100ms ease-out; width: 100%; } + &__click-to-view { + @include font-body-2; + align-items: center; + background: $color-black-alpha-20; + color: $color-white; + display: flex; + flex-direction: column; + height: 100%; + justify-content: center; + left: 0; + position: absolute; + top: 0; + width: 100%; + + &::before { + @include color-svg( + '../images/icons/v2/click-outline-24.svg', + $color-white + ); + content: ''; + display: block; + height: 24px; + margin-bottom: 8px; + width: 24px; + } + + &:hover { + background: $color-black-alpha-40; + } + } + &__label { align-items: center; display: flex; diff --git a/ts/components/Avatar.stories.tsx b/ts/components/Avatar.stories.tsx index ca323d3a70f5..1ed57a3b8e73 100644 --- a/ts/components/Avatar.stories.tsx +++ b/ts/components/Avatar.stories.tsx @@ -1,4 +1,4 @@ -// Copyright 2020 Signal Messenger, LLC +// Copyright 2020-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; @@ -7,7 +7,7 @@ import { storiesOf } from '@storybook/react'; import { boolean, select, text } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; -import { Avatar, Props } from './Avatar'; +import { Avatar, AvatarBlur, Props } from './Avatar'; import { setup as setupI18n } from '../../js/modules/i18n'; import enMessages from '../../_locales/en/messages.json'; import { Colors, ColorType } from '../types/Colors'; @@ -31,6 +31,7 @@ const conversationTypeMap: Record = { const createProps = (overrideProps: Partial = {}): Props => ({ avatarPath: text('avatarPath', overrideProps.avatarPath || ''), + blur: overrideProps.blur, color: select('color', colorMap, overrideProps.color || 'blue'), conversationType: select( 'conversationType', @@ -149,3 +150,21 @@ story.add('Loading', () => { return sizes.map(size => ); }); + +story.add('Blurred', () => { + const props = createProps({ + avatarPath: '/fixtures/kitten-3-64-64.jpg', + blur: AvatarBlur.BlurPicture, + }); + + return sizes.map(size => ); +}); + +story.add('Blurred with "click to view"', () => { + const props = createProps({ + avatarPath: '/fixtures/kitten-3-64-64.jpg', + blur: AvatarBlur.BlurPictureWithClickToView, + }); + + return ; +}); diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx index 93e74930bef6..ad82ef0be64b 100644 --- a/ts/components/Avatar.tsx +++ b/ts/components/Avatar.tsx @@ -8,6 +8,7 @@ import React, { useState, } from 'react'; import classNames from 'classnames'; +import { noop } from 'lodash'; import { Spinner } from './Spinner'; @@ -15,6 +16,13 @@ import { getInitials } from '../util/getInitials'; import { LocalizerType } from '../types/Util'; import { ColorType } from '../types/Colors'; import * as log from '../logging/log'; +import { assert } from '../util/assert'; + +export enum AvatarBlur { + NoBlur, + BlurPicture, + BlurPictureWithClickToView, +} export enum AvatarSize { TWENTY_EIGHT = 28, @@ -27,6 +35,7 @@ export enum AvatarSize { export type Props = { avatarPath?: string; + blur?: AvatarBlur; color?: ColorType; loading?: boolean; @@ -58,6 +67,7 @@ export const Avatar: FunctionComponent = ({ onClick, size, title, + blur = AvatarBlur.NoBlur, }) => { const [imageBroken, setImageBroken] = useState(false); @@ -65,6 +75,23 @@ export const Avatar: FunctionComponent = ({ setImageBroken(false); }, [avatarPath]); + useEffect(() => { + if (!avatarPath) { + return noop; + } + + const image = new Image(); + image.src = avatarPath; + image.onerror = () => { + log.warn('Avatar: Image failed to load; failing over to placeholder'); + setImageBroken(true); + }; + + return () => { + image.onerror = noop; + }; + }, [avatarPath]); + const initials = getInitials(title); const hasImage = !noteToSelf && avatarPath && !imageBroken; const shouldUseInitials = @@ -83,15 +110,28 @@ export const Avatar: FunctionComponent = ({ ); } else if (hasImage) { + assert(avatarPath, 'avatarPath should be defined here'); + assert( + blur !== AvatarBlur.BlurPictureWithClickToView || size >= 100, + 'Rendering "click to view" for a small avatar. This may not render correctly' + ); + + const isBlurred = + blur === AvatarBlur.BlurPicture || + blur === AvatarBlur.BlurPictureWithClickToView; contents = ( - { - log.warn('Avatar: Image failed to load; failing over to placeholder'); - setImageBroken(true); - }} - alt={i18n('contactAvatarAlt', [title])} - src={avatarPath} - /> + <> +
+ {blur === AvatarBlur.BlurPictureWithClickToView && ( +
{i18n('view')}
+ )} + ); } else if (noteToSelf) { contents = ( @@ -105,6 +145,7 @@ export const Avatar: FunctionComponent = ({ } else if (shouldUseInitials) { contents = (