Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix attachment previews #3170

Merged
1 change: 1 addition & 0 deletions stylesheets/_modules.scss
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
line-height: 16px;
letter-spacing: 0.3px;
margin-top: 3px;
text-align: start;
white-space: nowrap;
}

Expand Down
90 changes: 72 additions & 18 deletions ts/components/conversation/Image.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import classNames from 'classnames';
import { useCallback } from 'react';
import { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';

import { isNumber } from 'lodash';
import { useDisableDrag } from '../../hooks/useDisableDrag';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import { AttachmentType, AttachmentTypeWithPath } from '../../types/Attachment';
import { Spinner } from '../loading';
import { MessageModelType } from '../../models/messageType';
import { MessageGenericAttachment } from './message/message-content/MessageGenericAttachment';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';

type Props = {
alt: string;
attachment: AttachmentTypeWithPath | AttachmentType;
url: string | undefined; // url is undefined if the message is not visible yet
imageBroken?: boolean;

height?: number | string;
width?: number | string;
Expand All @@ -26,10 +29,14 @@ type Props = {
forceSquare?: boolean;
dropShadow?: boolean;
attachmentIndex?: number;
direction?: MessageModelType;
highlight?: boolean;

onClick?: (attachment: AttachmentTypeWithPath | AttachmentType) => void;
onClickClose?: (attachment: AttachmentTypeWithPath | AttachmentType) => void;
onError?: () => void;

timestamp?: number;
};

const StyledOverlay = styled.div<Pick<Props, 'darkOverlay' | 'softCorners'>>`
Expand All @@ -46,6 +53,7 @@ export const Image = (props: Props) => {
const {
alt,
attachment,
imageBroken,
closeButton,
darkOverlay,
height: _height,
Expand All @@ -58,32 +66,78 @@ export const Image = (props: Props) => {
forceSquare,
dropShadow,
attachmentIndex,
direction,
highlight,
url,
width: _width,
timestamp,
} = props;

const onErrorUrlFilterering = useCallback(() => {
if (url && onError) {
onError();
}
}, [url, onError]);
const disableDrag = useDisableDrag();
const { loading, urlToLoad } = useEncryptedFileFetch(
url,
attachment.contentType,
false,
timestamp
);

const { caption } = attachment || { caption: null };
let { pending } = attachment || { pending: true };
if (!url) {
// force pending to true if the url is undefined, so we show a loader while decrypting the attachemtn
pending = true;
}
const [pending, setPending] = useState<boolean>(attachment.pending || !url || true);
const [mounted, setMounted] = useState<boolean>(
(!loading || !pending) && urlToLoad === undefined
);

const canClick = onClick && !pending;
const role = canClick ? 'button' : undefined;
const { loading, urlToLoad } = useEncryptedFileFetch(url || '', attachment.contentType, false);
// data will be url if loading is finished and '' if not
const srcData = !loading ? urlToLoad : '';

const onErrorUrlFilterering = useCallback(() => {
if (mounted && url && urlToLoad === '' && onError) {
onError();
setPending(false);
}
}, [mounted, onError, url, urlToLoad]);

const width = isNumber(_width) ? `${_width}px` : _width;
const height = isNumber(_height) ? `${_height}px` : _height;

useEffect(() => {
if (mounted && url === '') {
setPending(false);
onErrorUrlFilterering();
window.log.debug(
`WIP: [Image] timestamp ${timestamp} fail url ${url !== '' ? url : 'empty'} urlToLoad ${urlToLoad !== '' ? urlToLoad : 'empty'} loading ${loading} pending ${pending} imageBroken ${imageBroken}`
);
}

if (mounted && imageBroken && urlToLoad === '') {
setPending(false);
onErrorUrlFilterering();
window.log.debug(
`WIP: [Image] timestamp ${timestamp} fail url ${url !== '' ? url : 'empty'} urlToLoad ${urlToLoad !== '' ? urlToLoad : 'empty'} loading ${loading} pending ${pending} imageBroken ${imageBroken}`
);
}

if (url) {
setPending(false);
setMounted(!loading && !pending);
window.log.debug(
`WIP: [Image] timestamp ${timestamp} success url ${url !== '' ? url : 'empty'} urlToLoad ${urlToLoad !== '' ? urlToLoad : 'empty'} loading ${loading} pending ${pending} imageBroken ${imageBroken}`
);
}
}, [imageBroken, loading, mounted, onErrorUrlFilterering, pending, timestamp, url, urlToLoad]);

if (mounted && imageBroken) {
return (
<MessageGenericAttachment
attachment={attachment as AttachmentTypeWithPath}
pending={false}
highlight={!!highlight}
selected={!!dropShadow} // dropshadow is selected
direction={direction}
/>
);
}

return (
<div
role={role}
Expand All @@ -107,7 +161,7 @@ export const Image = (props: Props) => {
}}
data-attachmentindex={attachmentIndex}
>
{pending || loading ? (
{!mounted || loading || pending ? (
<div
className="module-image__loading-placeholder"
style={{
Expand Down Expand Up @@ -137,7 +191,7 @@ export const Image = (props: Props) => {
width: forceSquare ? width : '',
height: forceSquare ? height : '',
}}
src={srcData}
src={urlToLoad}
onDragStart={disableDrag}
/>
)}
Expand Down Expand Up @@ -166,7 +220,7 @@ export const Image = (props: Props) => {
className="module-image__close-button"
/>
) : null}
{!(pending || loading) && playIconOverlay ? (
{!pending && playIconOverlay ? (
<div className="module-image__play-overlay__circle">
<div className="module-image__play-overlay__icon" />
</div>
Expand Down
41 changes: 31 additions & 10 deletions ts/components/conversation/ImageGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ import {
} from '../../types/Attachment';

import { useIsMessageVisible } from '../../contexts/isMessageVisibleContext';
import { useMessageSelected } from '../../state/selectors';
import {
useMessageDirection,
useMessageSelected,
useMessageTimestamp,
} from '../../state/selectors';
import { THUMBNAIL_SIDE } from '../../types/attachments/VisualAttachment';
import { Image } from './Image';

type Props = {
attachments: Array<AttachmentTypeWithPath>;
onError: () => void;
imageBroken: boolean;
highlight: boolean;
onClickAttachment?: (attachment: AttachmentTypeWithPath | AttachmentType) => void;
messageId?: string;
};
Expand All @@ -33,22 +39,27 @@ const Row = (
renderedSize: number;
startIndex: number;
totalAttachmentsCount: number;
selected: boolean;
}
) => {
const {
attachments,
imageBroken,
highlight,
onError,
renderedSize,
startIndex,
onClickAttachment,
totalAttachmentsCount,
selected,
messageId,
} = props;
const isMessageVisible = useIsMessageVisible();
const moreMessagesOverlay = totalAttachmentsCount > 3;
const moreMessagesOverlayText = moreMessagesOverlay ? `+${totalAttachmentsCount - 3}` : undefined;

const selected = useMessageSelected(messageId);
const direction = useMessageDirection(messageId);
const timestamp = useMessageTimestamp(messageId);

return (
<>
{attachments.map((attachment, index) => {
Expand All @@ -64,11 +75,15 @@ const Row = (
url={isMessageVisible ? getThumbnailUrl(attachment) : undefined}
attachmentIndex={startIndex + index}
onClick={onClickAttachment}
imageBroken={imageBroken}
highlight={highlight}
onError={onError}
softCorners={true}
darkOverlay={showOverlay}
overlayText={showOverlay ? moreMessagesOverlayText : undefined}
dropShadow={selected}
direction={direction}
timestamp={timestamp}
/>
);
})}
Expand All @@ -77,9 +92,7 @@ const Row = (
};

export const ImageGrid = (props: Props) => {
const { attachments, onError, onClickAttachment, messageId } = props;

const selected = useMessageSelected(messageId);
const { attachments, imageBroken, highlight, onError, onClickAttachment, messageId } = props;

if (!attachments || !attachments.length) {
return null;
Expand All @@ -90,12 +103,14 @@ export const ImageGrid = (props: Props) => {
<StyledImageGrid flexDirection={'row'}>
<Row
attachments={attachments.slice(0, 1)}
imageBroken={imageBroken}
highlight={highlight}
onError={onError}
onClickAttachment={onClickAttachment}
renderedSize={THUMBNAIL_SIDE}
startIndex={0}
totalAttachmentsCount={attachments.length}
selected={selected}
messageId={messageId}
yougotwill marked this conversation as resolved.
Show resolved Hide resolved
/>
</StyledImageGrid>
);
Expand All @@ -107,12 +122,14 @@ export const ImageGrid = (props: Props) => {
<StyledImageGrid flexDirection={'row'}>
<Row
attachments={attachments.slice(0, 2)}
imageBroken={imageBroken}
highlight={highlight}
onError={onError}
onClickAttachment={onClickAttachment}
renderedSize={THUMBNAIL_SIDE}
startIndex={0}
totalAttachmentsCount={attachments.length}
selected={selected}
messageId={messageId}
/>
</StyledImageGrid>
);
Expand All @@ -125,23 +142,27 @@ export const ImageGrid = (props: Props) => {
<StyledImageGrid flexDirection={'row'}>
<Row
attachments={attachments.slice(0, 1)}
imageBroken={imageBroken}
highlight={highlight}
onError={onError}
onClickAttachment={onClickAttachment}
renderedSize={THUMBNAIL_SIDE}
startIndex={0}
totalAttachmentsCount={attachments.length}
selected={selected}
messageId={messageId}
/>

<StyledImageGrid flexDirection={'column'}>
<Row
attachments={attachments.slice(1, 3)}
imageBroken={imageBroken}
highlight={highlight}
onError={onError}
onClickAttachment={onClickAttachment}
renderedSize={columnImageSide}
startIndex={1}
totalAttachmentsCount={attachments.length}
selected={selected}
messageId={messageId}
/>
</StyledImageGrid>
</StyledImageGrid>
Expand Down
Loading
Loading