signal-desktop/ts/components/conversation/Image.tsx

280 lines
7.5 KiB
TypeScript
Raw Normal View History

2020-10-30 20:34:04 +00:00
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import classNames from 'classnames';
2020-05-27 21:37:06 +00:00
import { Blurhash } from 'react-blurhash';
import { Spinner } from '../Spinner';
import { LocalizerType, ThemeType } from '../../types/Util';
2021-01-29 22:58:28 +00:00
import { AttachmentType, hasNotDownloaded } from '../../types/Attachment';
export type Props = {
alt: string;
attachment: AttachmentType;
url: string;
height?: number;
width?: number;
2019-11-07 21:36:16 +00:00
tabIndex?: number;
overlayText?: string;
noBorder?: boolean;
noBackground?: boolean;
bottomOverlay?: boolean;
closeButton?: boolean;
curveBottomLeft?: boolean;
curveBottomRight?: boolean;
curveTopLeft?: boolean;
curveTopRight?: boolean;
2019-01-16 03:03:56 +00:00
smallCurveTopLeft?: boolean;
darkOverlay?: boolean;
playIconOverlay?: boolean;
softCorners?: boolean;
2020-05-27 21:37:06 +00:00
blurHash?: string;
2019-01-14 21:49:58 +00:00
i18n: LocalizerType;
theme?: ThemeType;
onClick?: (attachment: AttachmentType) => void;
onClickClose?: (attachment: AttachmentType) => void;
onError?: () => void;
};
export class Image extends React.Component<Props> {
2020-05-27 21:37:06 +00:00
private canClick() {
const { onClick, attachment } = this.props;
2020-05-27 21:37:06 +00:00
const { pending } = attachment || { pending: true };
return Boolean(onClick && !pending);
2020-05-27 21:37:06 +00:00
}
2020-09-14 19:51:27 +00:00
public handleClick = (event: React.MouseEvent): void => {
2020-05-27 21:37:06 +00:00
if (!this.canClick()) {
event.preventDefault();
event.stopPropagation();
return;
}
2019-11-07 21:36:16 +00:00
const { onClick, attachment } = this.props;
if (onClick) {
event.preventDefault();
event.stopPropagation();
onClick(attachment);
}
};
2020-09-14 19:51:27 +00:00
public handleKeyDown = (
event: React.KeyboardEvent<HTMLButtonElement>
): void => {
2020-05-27 21:37:06 +00:00
if (!this.canClick()) {
event.preventDefault();
event.stopPropagation();
return;
}
2019-11-07 21:36:16 +00:00
const { onClick, attachment } = this.props;
if (onClick && (event.key === 'Enter' || event.key === 'Space')) {
event.preventDefault();
event.stopPropagation();
onClick(attachment);
}
};
2021-01-29 22:58:28 +00:00
public renderPending = (): JSX.Element => {
const { blurHash, height, i18n, width } = this.props;
if (blurHash) {
return (
<div className="module-image__download-pending">
<Blurhash
hash={blurHash}
width={width}
height={height}
style={{ display: 'block' }}
/>
<div className="module-image__download-pending--spinner-container">
<div
className="module-image__download-pending--spinner"
title={i18n('loading')}
>
<Spinner moduleClassName="module-image-spinner" svgSize="small" />
</div>
</div>
</div>
);
}
return (
<div
className="module-image__loading-placeholder"
style={{
height: `${height}px`,
width: `${width}px`,
lineHeight: `${height}px`,
textAlign: 'center',
}}
title={i18n('loading')}
>
<Spinner svgSize="normal" />
</div>
);
};
2020-09-14 19:51:27 +00:00
public render(): JSX.Element {
const {
alt,
attachment,
2020-05-27 21:37:06 +00:00
blurHash,
bottomOverlay,
closeButton,
curveBottomLeft,
curveBottomRight,
curveTopLeft,
curveTopRight,
darkOverlay,
2020-05-27 21:37:06 +00:00
height = 0,
i18n,
noBackground,
noBorder,
onClickClose,
onError,
overlayText,
playIconOverlay,
2019-01-16 03:03:56 +00:00
smallCurveTopLeft,
softCorners,
2019-11-07 21:36:16 +00:00
tabIndex,
theme,
url,
2020-05-27 21:37:06 +00:00
width = 0,
} = this.props;
const { caption, pending } = attachment || { caption: null, pending: true };
2020-05-27 21:37:06 +00:00
const canClick = this.canClick();
2021-01-29 22:58:28 +00:00
const imgNotDownloaded = hasNotDownloaded(attachment);
const defaulBlurHash =
theme === ThemeType.dark
? 'L05OQnoffQofoffQfQfQfQfQfQfQ'
: 'L1Q]+w-;fQ-;~qfQfQfQfQfQfQfQ';
const resolvedBlurHash = blurHash || defaulBlurHash;
2021-01-29 22:58:28 +00:00
const overlayClassName = classNames('module-image__border-overlay', {
'module-image__border-overlay--with-border': !noBorder,
'module-image__border-overlay--with-click-handler': canClick,
'module-image--curved-top-left': curveTopLeft,
'module-image--curved-top-right': curveTopRight,
'module-image--curved-bottom-left': curveBottomLeft,
'module-image--curved-bottom-right': curveBottomRight,
'module-image--small-curved-top-left': smallCurveTopLeft,
'module-image--soft-corners': softCorners,
'module-image__border-overlay--dark': darkOverlay,
'module-image--not-downloaded': imgNotDownloaded,
});
2019-11-07 21:36:16 +00:00
2020-05-27 21:37:06 +00:00
const overlay = canClick ? (
2020-09-14 19:51:27 +00:00
// Not sure what this button does.
// eslint-disable-next-line jsx-a11y/control-has-associated-label
2020-05-27 21:37:06 +00:00
<button
2020-09-14 19:51:27 +00:00
type="button"
2020-05-27 21:37:06 +00:00
className={overlayClassName}
onClick={this.handleClick}
onKeyDown={this.handleKeyDown}
tabIndex={tabIndex}
2021-01-29 22:58:28 +00:00
>
{imgNotDownloaded ? <i /> : null}
</button>
2020-05-27 21:37:06 +00:00
) : null;
2020-09-14 19:51:27 +00:00
/* eslint-disable no-nested-ternary */
return (
<div
className={classNames(
'module-image',
!noBackground ? 'module-image--with-background' : null,
curveBottomLeft ? 'module-image--curved-bottom-left' : null,
curveBottomRight ? 'module-image--curved-bottom-right' : null,
curveTopLeft ? 'module-image--curved-top-left' : null,
curveTopRight ? 'module-image--curved-top-right' : null,
2019-01-16 03:03:56 +00:00
smallCurveTopLeft ? 'module-image--small-curved-top-left' : null,
softCorners ? 'module-image--soft-corners' : null
)}
>
{pending ? (
2021-01-29 22:58:28 +00:00
this.renderPending()
2020-05-27 21:37:06 +00:00
) : url ? (
<img
onError={onError}
className="module-image__image"
alt={alt}
height={height}
width={width}
src={url}
/>
) : resolvedBlurHash ? (
2020-05-27 21:37:06 +00:00
<Blurhash
hash={resolvedBlurHash}
2020-05-27 21:37:06 +00:00
width={width}
height={height}
style={{ display: 'block' }}
/>
) : null}
{caption ? (
<img
className="module-image__caption-icon"
src="images/caption-shadow.svg"
alt={i18n('imageCaptionIconAlt')}
/>
) : null}
{bottomOverlay ? (
<div
className={classNames(
'module-image__bottom-overlay',
curveBottomLeft ? 'module-image--curved-bottom-left' : null,
curveBottomRight ? 'module-image--curved-bottom-right' : null
)}
/>
) : null}
{!pending && playIconOverlay ? (
<div className="module-image__play-overlay__circle">
<div className="module-image__play-overlay__icon" />
</div>
) : null}
{overlayText ? (
<div
className="module-image__text-container"
style={{ lineHeight: `${height}px` }}
>
{overlayText}
</div>
) : null}
2019-11-19 23:03:00 +00:00
{overlay}
{closeButton ? (
<button
2020-09-14 19:51:27 +00:00
type="button"
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
if (onClickClose) {
onClickClose(attachment);
}
}}
className="module-image__close-button"
title={i18n('remove-attachment')}
2020-09-14 19:51:27 +00:00
aria-label={i18n('remove-attachment')}
/>
) : null}
</div>
);
2020-09-14 19:51:27 +00:00
/* eslint-enable no-nested-ternary */
}
}