Use streams to download attachments directly to disk
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
parent
2da49456c6
commit
99b2bc304e
48 changed files with 2297 additions and 356 deletions
|
@ -7,20 +7,20 @@ import classNames from 'classnames';
|
|||
import { getIncrement, getTimerBucket } from '../../util/timer';
|
||||
|
||||
export type Props = {
|
||||
deletedForEveryone?: boolean;
|
||||
direction?: 'incoming' | 'outgoing';
|
||||
expirationLength: number;
|
||||
expirationTimestamp?: number;
|
||||
isOutlineOnlyBubble?: boolean;
|
||||
withImageNoCaption?: boolean;
|
||||
withSticker?: boolean;
|
||||
withTapToViewExpired?: boolean;
|
||||
};
|
||||
|
||||
export function ExpireTimer({
|
||||
deletedForEveryone,
|
||||
direction,
|
||||
expirationLength,
|
||||
expirationTimestamp,
|
||||
isOutlineOnlyBubble,
|
||||
withImageNoCaption,
|
||||
withSticker,
|
||||
withTapToViewExpired,
|
||||
|
@ -44,7 +44,7 @@ export function ExpireTimer({
|
|||
'module-expire-timer',
|
||||
`module-expire-timer--${bucket}`,
|
||||
direction ? `module-expire-timer--${direction}` : null,
|
||||
deletedForEveryone ? 'module-expire-timer--deleted-for-everyone' : null,
|
||||
isOutlineOnlyBubble ? 'module-expire-timer--outline-only-bubble' : null,
|
||||
withTapToViewExpired
|
||||
? `module-expire-timer--${direction}-with-tap-to-view-expired`
|
||||
: null,
|
||||
|
|
|
@ -75,13 +75,16 @@ function getCurves({
|
|||
curveTopRight = CurveType.Normal;
|
||||
}
|
||||
|
||||
if (shouldCollapseBelow && direction === 'incoming') {
|
||||
if (withContentBelow) {
|
||||
curveBottomLeft = CurveType.None;
|
||||
curveBottomRight = CurveType.None;
|
||||
} else if (shouldCollapseBelow && direction === 'incoming') {
|
||||
curveBottomLeft = CurveType.Tiny;
|
||||
curveBottomRight = CurveType.None;
|
||||
} else if (shouldCollapseBelow && direction === 'outgoing') {
|
||||
curveBottomLeft = CurveType.None;
|
||||
curveBottomRight = CurveType.Tiny;
|
||||
} else if (!withContentBelow) {
|
||||
} else {
|
||||
curveBottomLeft = CurveType.Normal;
|
||||
curveBottomRight = CurveType.Normal;
|
||||
}
|
||||
|
|
|
@ -284,6 +284,7 @@ export type PropsData = {
|
|||
reactions?: ReactionViewerProps['reactions'];
|
||||
|
||||
deletedForEveryone?: boolean;
|
||||
attachmentDroppedDueToSize?: boolean;
|
||||
|
||||
canDeleteForEveryone: boolean;
|
||||
isBlocked: boolean;
|
||||
|
@ -565,6 +566,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
private getMetadataPlacement(
|
||||
{
|
||||
attachments,
|
||||
attachmentDroppedDueToSize,
|
||||
deletedForEveryone,
|
||||
direction,
|
||||
expirationLength,
|
||||
|
@ -599,12 +601,16 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
return MetadataPlacement.Bottom;
|
||||
}
|
||||
|
||||
if (!text && !deletedForEveryone) {
|
||||
if (!text && !deletedForEveryone && !attachmentDroppedDueToSize) {
|
||||
return isAudio(attachments)
|
||||
? MetadataPlacement.RenderedByMessageAudioComponent
|
||||
: MetadataPlacement.Bottom;
|
||||
}
|
||||
|
||||
if (!text && attachmentDroppedDueToSize) {
|
||||
return MetadataPlacement.InlineWithText;
|
||||
}
|
||||
|
||||
if (this.canRenderStickerLikeEmoji()) {
|
||||
return MetadataPlacement.Bottom;
|
||||
}
|
||||
|
@ -796,6 +802,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
const {
|
||||
attachmentDroppedDueToSize,
|
||||
deletedForEveryone,
|
||||
direction,
|
||||
expirationLength,
|
||||
|
@ -822,11 +829,14 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
direction={direction}
|
||||
expirationLength={expirationLength}
|
||||
expirationTimestamp={expirationTimestamp}
|
||||
hasText={Boolean(text)}
|
||||
hasText={Boolean(text || attachmentDroppedDueToSize)}
|
||||
i18n={i18n}
|
||||
id={id}
|
||||
isEditedMessage={isEditedMessage}
|
||||
isInline={isInline}
|
||||
isOutlineOnlyBubble={
|
||||
deletedForEveryone || (attachmentDroppedDueToSize && !text)
|
||||
}
|
||||
isShowingImage={this.isShowingImage()}
|
||||
isSticker={isStickerLike}
|
||||
isTapToViewExpired={isTapToViewExpired}
|
||||
|
@ -878,6 +888,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
public renderAttachment(): JSX.Element | null {
|
||||
const {
|
||||
attachments,
|
||||
attachmentDroppedDueToSize,
|
||||
conversationId,
|
||||
direction,
|
||||
expirationLength,
|
||||
|
@ -912,7 +923,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
const firstAttachment = attachments[0];
|
||||
|
||||
// For attachments which aren't full-frame
|
||||
const withContentBelow = Boolean(text);
|
||||
const withContentBelow = Boolean(text || attachmentDroppedDueToSize);
|
||||
const withContentAbove = Boolean(quote) || this.shouldRenderAuthor();
|
||||
const displayImage = canDisplayImage(attachments);
|
||||
|
||||
|
@ -1274,6 +1285,62 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
public renderAttachmentTooBig(): JSX.Element | null {
|
||||
const {
|
||||
attachments,
|
||||
attachmentDroppedDueToSize,
|
||||
direction,
|
||||
i18n,
|
||||
quote,
|
||||
shouldCollapseAbove,
|
||||
shouldCollapseBelow,
|
||||
text,
|
||||
} = this.props;
|
||||
const { metadataWidth } = this.state;
|
||||
|
||||
if (!attachmentDroppedDueToSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const labelText = attachments?.length
|
||||
? i18n('icu:message--attachmentTooBig--multiple')
|
||||
: i18n('icu:message--attachmentTooBig--one');
|
||||
|
||||
const isContentAbove = quote || attachments?.length;
|
||||
const isContentBelow = Boolean(text);
|
||||
const willCollapseAbove = shouldCollapseAbove && !isContentAbove;
|
||||
const willCollapseBelow = shouldCollapseBelow && !isContentBelow;
|
||||
|
||||
const maybeSpacer = text
|
||||
? undefined
|
||||
: this.getMetadataPlacement() === MetadataPlacement.InlineWithText && (
|
||||
<MessageTextMetadataSpacer metadataWidth={metadataWidth} />
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-message__attachment-too-big',
|
||||
isContentAbove
|
||||
? 'module-message__attachment-too-big--content-above'
|
||||
: null,
|
||||
isContentBelow
|
||||
? 'module-message__attachment-too-big--content-below'
|
||||
: null,
|
||||
willCollapseAbove
|
||||
? `module-message__attachment-too-big--collapse-above--${direction}`
|
||||
: null,
|
||||
willCollapseBelow
|
||||
? `module-message__attachment-too-big--collapse-below--${direction}`
|
||||
: null
|
||||
)}
|
||||
>
|
||||
{labelText}
|
||||
{maybeSpacer}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderGiftBadge(): JSX.Element | null {
|
||||
const { conversationTitle, direction, getPreferredBadge, giftBadge, i18n } =
|
||||
this.props;
|
||||
|
@ -1757,6 +1824,19 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private getContents(): string | undefined {
|
||||
const { deletedForEveryone, direction, i18n, status, text } = this.props;
|
||||
|
||||
if (deletedForEveryone) {
|
||||
return i18n('icu:message--deletedForEveryone');
|
||||
}
|
||||
if (direction === 'incoming' && status === 'error') {
|
||||
return i18n('icu:incomingError');
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
public renderText(): JSX.Element | null {
|
||||
const {
|
||||
bodyRanges,
|
||||
|
@ -1772,17 +1852,12 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
showConversation,
|
||||
showSpoiler,
|
||||
status,
|
||||
text,
|
||||
|
||||
textAttachment,
|
||||
} = this.props;
|
||||
const { metadataWidth } = this.state;
|
||||
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
const contents = deletedForEveryone
|
||||
? i18n('icu:message--deletedForEveryone')
|
||||
: direction === 'incoming' && status === 'error'
|
||||
? i18n('icu:incomingError')
|
||||
: text;
|
||||
const contents = this.getContents();
|
||||
|
||||
if (!contents) {
|
||||
return null;
|
||||
|
@ -2296,7 +2371,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public renderContents(): JSX.Element | null {
|
||||
const { giftBadge, isTapToView, deletedForEveryone } = this.props;
|
||||
const { deletedForEveryone, giftBadge, isTapToView } = this.props;
|
||||
|
||||
if (deletedForEveryone) {
|
||||
return (
|
||||
|
@ -2326,6 +2401,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
{this.renderStoryReplyContext()}
|
||||
{this.renderAttachment()}
|
||||
{this.renderPreview()}
|
||||
{this.renderAttachmentTooBig()}
|
||||
{this.renderPayment()}
|
||||
{this.renderEmbeddedContact()}
|
||||
{this.renderText()}
|
||||
|
@ -2534,6 +2610,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
public renderContainer(): JSX.Element {
|
||||
const {
|
||||
attachments,
|
||||
attachmentDroppedDueToSize,
|
||||
conversationColor,
|
||||
customColor,
|
||||
deletedForEveryone,
|
||||
|
@ -2597,7 +2674,12 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
const containerStyles = {
|
||||
width: shouldUseWidth ? width : undefined,
|
||||
};
|
||||
if (!isStickerLike && !deletedForEveryone && direction === 'outgoing') {
|
||||
if (
|
||||
!isStickerLike &&
|
||||
!deletedForEveryone &&
|
||||
!(attachmentDroppedDueToSize && !text) &&
|
||||
direction === 'outgoing'
|
||||
) {
|
||||
Object.assign(containerStyles, getCustomColorStyle(customColor));
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ type PropsType = {
|
|||
id: string;
|
||||
isEditedMessage?: boolean;
|
||||
isInline?: boolean;
|
||||
isOutlineOnlyBubble?: boolean;
|
||||
isShowingImage: boolean;
|
||||
isSticker?: boolean;
|
||||
isTapToViewExpired?: boolean;
|
||||
|
@ -55,6 +56,7 @@ export const MessageMetadata = forwardRef<HTMLDivElement, Readonly<PropsType>>(
|
|||
i18n,
|
||||
id,
|
||||
isEditedMessage,
|
||||
isOutlineOnlyBubble,
|
||||
isInline,
|
||||
isShowingImage,
|
||||
isSticker,
|
||||
|
@ -136,8 +138,8 @@ export const MessageMetadata = forwardRef<HTMLDivElement, Readonly<PropsType>>(
|
|||
className={classNames({
|
||||
'module-message__metadata__date': true,
|
||||
'module-message__metadata__date--with-sticker': isSticker,
|
||||
'module-message__metadata__date--deleted-for-everyone':
|
||||
deletedForEveryone,
|
||||
'module-message__metadata__date--outline-only-bubble':
|
||||
isOutlineOnlyBubble,
|
||||
[`module-message__metadata__date--${direction}`]: !isSticker,
|
||||
'module-message__metadata__date--with-image-no-caption':
|
||||
withImageNoCaption,
|
||||
|
@ -149,9 +151,9 @@ export const MessageMetadata = forwardRef<HTMLDivElement, Readonly<PropsType>>(
|
|||
} else {
|
||||
timestampNode = (
|
||||
<MessageTimestamp
|
||||
deletedForEveryone={deletedForEveryone}
|
||||
direction={metadataDirection}
|
||||
i18n={i18n}
|
||||
isOutlineOnlyBubble={isOutlineOnlyBubble}
|
||||
module="module-message__metadata__date"
|
||||
timestamp={timestamp}
|
||||
withImageNoCaption={withImageNoCaption}
|
||||
|
@ -195,7 +197,7 @@ export const MessageMetadata = forwardRef<HTMLDivElement, Readonly<PropsType>>(
|
|||
'module-message__metadata',
|
||||
isInline && 'module-message__metadata--inline',
|
||||
withImageNoCaption && 'module-message__metadata--with-image-no-caption',
|
||||
deletedForEveryone && 'module-message__metadata--deleted-for-everyone'
|
||||
isOutlineOnlyBubble && 'module-message__metadata--outline-only-bubble'
|
||||
);
|
||||
const children = (
|
||||
<>
|
||||
|
@ -212,7 +214,7 @@ export const MessageMetadata = forwardRef<HTMLDivElement, Readonly<PropsType>>(
|
|||
{expirationLength ? (
|
||||
<ExpireTimer
|
||||
direction={metadataDirection}
|
||||
deletedForEveryone={deletedForEveryone}
|
||||
isOutlineOnlyBubble={isOutlineOnlyBubble}
|
||||
expirationLength={expirationLength}
|
||||
expirationTimestamp={expirationTimestamp}
|
||||
withImageNoCaption={withImageNoCaption}
|
||||
|
@ -240,8 +242,8 @@ export const MessageMetadata = forwardRef<HTMLDivElement, Readonly<PropsType>>(
|
|||
withImageNoCaption
|
||||
? 'module-message__metadata__status-icon--with-image-no-caption'
|
||||
: null,
|
||||
deletedForEveryone
|
||||
? 'module-message__metadata__status-icon--deleted-for-everyone'
|
||||
isOutlineOnlyBubble
|
||||
? 'module-message__metadata__status-icon--outline-only-bubble'
|
||||
: null,
|
||||
isTapToViewExpired
|
||||
? 'module-message__metadata__status-icon--with-tap-to-view-expired'
|
||||
|
|
|
@ -12,9 +12,9 @@ import { Time } from '../Time';
|
|||
import { useNowThatUpdatesEveryMinute } from '../../hooks/useNowThatUpdatesEveryMinute';
|
||||
|
||||
export type Props = {
|
||||
deletedForEveryone?: boolean;
|
||||
direction?: 'incoming' | 'outgoing';
|
||||
i18n: LocalizerType;
|
||||
isOutlineOnlyBubble?: boolean;
|
||||
isRelativeTime?: boolean;
|
||||
module?: string;
|
||||
timestamp: number;
|
||||
|
@ -24,10 +24,10 @@ export type Props = {
|
|||
};
|
||||
|
||||
export function MessageTimestamp({
|
||||
deletedForEveryone,
|
||||
direction,
|
||||
i18n,
|
||||
isRelativeTime,
|
||||
isOutlineOnlyBubble,
|
||||
module,
|
||||
timestamp,
|
||||
withImageNoCaption,
|
||||
|
@ -47,7 +47,7 @@ export function MessageTimestamp({
|
|||
: null,
|
||||
withImageNoCaption ? `${moduleName}--with-image-no-caption` : null,
|
||||
withSticker ? `${moduleName}--with-sticker` : null,
|
||||
deletedForEveryone ? `${moduleName}--deleted-for-everyone` : null
|
||||
isOutlineOnlyBubble ? `${moduleName}--ouline-only-bubble` : null
|
||||
)}
|
||||
timestamp={timestamp}
|
||||
>
|
||||
|
|
|
@ -244,6 +244,7 @@ const renderAudioAttachment: Props['renderAudioAttachment'] = props => (
|
|||
|
||||
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||
attachments: overrideProps.attachments,
|
||||
attachmentDroppedDueToSize: overrideProps.attachmentDroppedDueToSize || false,
|
||||
author: overrideProps.author || getDefaultConversation(),
|
||||
bodyRanges: overrideProps.bodyRanges,
|
||||
canCopy: true,
|
||||
|
@ -835,6 +836,25 @@ CanDeleteForEveryone.args = {
|
|||
direction: 'outgoing',
|
||||
};
|
||||
|
||||
export function AttachmentTooBig(): JSX.Element {
|
||||
const propsSent = createProps({
|
||||
conversationType: 'direct',
|
||||
attachmentDroppedDueToSize: true,
|
||||
});
|
||||
|
||||
return <>{renderBothDirections(propsSent)}</>;
|
||||
}
|
||||
|
||||
export function AttachmentTooBigWithText(): JSX.Element {
|
||||
const propsSent = createProps({
|
||||
conversationType: 'direct',
|
||||
attachmentDroppedDueToSize: true,
|
||||
text: 'Check out this file!',
|
||||
});
|
||||
|
||||
return <>{renderBothDirections(propsSent)}</>;
|
||||
}
|
||||
|
||||
export const Error = Template.bind({});
|
||||
Error.args = {
|
||||
status: 'error',
|
||||
|
@ -1233,6 +1253,51 @@ MultipleImages5.args = {
|
|||
status: 'sent',
|
||||
};
|
||||
|
||||
export const MultipleImagesWithOneTooBig = Template.bind({});
|
||||
MultipleImagesWithOneTooBig.args = {
|
||||
attachments: [
|
||||
fakeAttachment({
|
||||
url: pngUrl,
|
||||
fileName: 'the-sax.png',
|
||||
contentType: IMAGE_PNG,
|
||||
height: 240,
|
||||
width: 320,
|
||||
}),
|
||||
fakeAttachment({
|
||||
url: pngUrl,
|
||||
fileName: 'the-sax.png',
|
||||
contentType: IMAGE_PNG,
|
||||
height: 240,
|
||||
width: 320,
|
||||
}),
|
||||
],
|
||||
attachmentDroppedDueToSize: true,
|
||||
status: 'sent',
|
||||
};
|
||||
|
||||
export const MultipleImagesWithBodyTextOneTooBig = Template.bind({});
|
||||
MultipleImagesWithBodyTextOneTooBig.args = {
|
||||
attachments: [
|
||||
fakeAttachment({
|
||||
url: pngUrl,
|
||||
fileName: 'the-sax.png',
|
||||
contentType: IMAGE_PNG,
|
||||
height: 240,
|
||||
width: 320,
|
||||
}),
|
||||
fakeAttachment({
|
||||
url: pngUrl,
|
||||
fileName: 'the-sax.png',
|
||||
contentType: IMAGE_PNG,
|
||||
height: 240,
|
||||
width: 320,
|
||||
}),
|
||||
],
|
||||
attachmentDroppedDueToSize: true,
|
||||
text: 'Hey, check out these images!',
|
||||
status: 'sent',
|
||||
};
|
||||
|
||||
export const ImageWithCaption = Template.bind({});
|
||||
ImageWithCaption.args = {
|
||||
attachments: [
|
||||
|
@ -1968,6 +2033,7 @@ PaymentNotification.args = {
|
|||
|
||||
function MultiSelectMessage() {
|
||||
const [selected, setSelected] = React.useState(false);
|
||||
|
||||
return (
|
||||
<TimelineMessage
|
||||
{...createProps({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue