From b3a56caa58af618e9ce2c459dc8148cb0a87228b Mon Sep 17 00:00:00 2001 From: automated-signal <37887102+automated-signal@users.noreply.github.com> Date: Thu, 10 Jul 2025 12:48:34 -0500 Subject: [PATCH] Message.tsx: Show expiring metadata when rendering generic attachments Co-authored-by: Scott Nonnenberg --- ts/components/conversation/Message.tsx | 9 +- .../conversation/TimelineMessage.stories.tsx | 235 ++++++++++++++++++ 2 files changed, 241 insertions(+), 3 deletions(-) diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 18e7d402ee..ccbb646067 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -1408,6 +1408,9 @@ export class Message extends React.PureComponent { ); }; + const willShowMetadata = + expirationLength || expirationTimestamp || !shouldHideMetadata; + // Note: this has to be interactive for the case where text comes along with the // attachment. But we don't want the user to tab here unless that text exists. const tabIndex = text ? 0 : -1; @@ -1485,7 +1488,7 @@ export class Message extends React.PureComponent { {formatFileSize(size)} )} - {text || shouldHideMetadata ? undefined : ( + {text || !willShowMetadata ? undefined : (
{ ) { return ( attachments?.length && - (!isImage(attachments) || imageBroken) && - (!isVideo(attachments) || imageBroken) && + (!isImage(attachments) || !canDisplayImage(attachments) || imageBroken) && + (!isVideo(attachments) || !canDisplayImage(attachments) || imageBroken) && !isAudio(attachments) ); } diff --git a/ts/components/conversation/TimelineMessage.stories.tsx b/ts/components/conversation/TimelineMessage.stories.tsx index eaa180b81b..51c560ab12 100644 --- a/ts/components/conversation/TimelineMessage.stories.tsx +++ b/ts/components/conversation/TimelineMessage.stories.tsx @@ -1390,6 +1390,226 @@ export function Image(): JSX.Element { ); } +export function BrokenImage(): JSX.Element { + const darkImageProps = createProps({ + attachments: [ + fakeAttachment({ + url: 'nonexistent.jpg', + fileName: 'tina-rolf-269345-unsplash.jpg', + contentType: IMAGE_JPEG, + width: 128, + height: 128, + }), + ], + status: 'sent', + }); + const lightImageProps = createProps({ + attachments: [ + fakeAttachment({ + url: 'nonexistent.jpg', + fileName: 'the-sax.png', + contentType: IMAGE_PNG, + height: 240, + width: 320, + }), + ], + status: 'sent', + }); + + return ( + <> + {renderBothDirections(darkImageProps)} + {renderBothDirections(lightImageProps)} + + ); +} + +export function BrokenImageWithExpirationTimer(): JSX.Element { + const darkImageProps = createProps({ + attachments: [ + fakeAttachment({ + url: 'nonexistent.jpg', + fileName: 'tina-rolf-269345-unsplash.jpg', + contentType: IMAGE_JPEG, + width: 128, + height: 128, + }), + ], + expirationLength: 30 * 1000, + expirationTimestamp: Date.now() + 30 * 1000, + status: 'sent', + }); + const lightImageProps = createProps({ + attachments: [ + fakeAttachment({ + url: 'nonexistent.jpg', + fileName: 'the-sax.png', + contentType: IMAGE_PNG, + height: 240, + width: 320, + }), + ], + expirationLength: 30 * 1000, + expirationTimestamp: Date.now() + 30 * 1000, + status: 'sent', + }); + + return ( + <> + {renderBothDirections(darkImageProps)} + {renderBothDirections(lightImageProps)} + + ); +} + +export function Video(): JSX.Element { + const darkImageProps = createProps({ + attachments: [ + fakeAttachment({ + url: 'nonexistent.mp4', + screenshot: { + url: '/fixtures/tina-rolf-269345-unsplash.jpg', + size: 100000, + width: 3000, + height: 1680, + contentType: IMAGE_JPEG, + }, + fileName: 'tina-rolf-269345-unsplash.jpg', + contentType: VIDEO_MP4, + width: 128, + height: 128, + }), + ], + status: 'sent', + }); + const lightImageProps = createProps({ + attachments: [ + fakeAttachment({ + url: 'nonexistent.mp4', + screenshot: { + url: pngUrl, + width: 800, + height: 1200, + size: 100000, + contentType: IMAGE_PNG, + }, + fileName: 'the-sax.png', + contentType: VIDEO_MP4, + height: 240, + width: 320, + }), + ], + status: 'sent', + }); + + return ( + <> + {renderBothDirections(darkImageProps)} + {renderBothDirections(lightImageProps)} + + ); +} + +export function BrokenVideo(): JSX.Element { + const darkImageProps = createProps({ + attachments: [ + fakeAttachment({ + url: 'nonexistent.mp4', + screenshot: { + url: '/fixtures/tina-rolf-269345-unsplash.jpg', + size: 100000, + width: 7680, + height: 3200, + contentType: IMAGE_JPEG, + }, + fileName: 'tina-rolf-269345-unsplash.jpg', + contentType: VIDEO_MP4, + height: 3200, + width: 7680, + }), + ], + status: 'sent', + }); + const lightImageProps = createProps({ + attachments: [ + fakeAttachment({ + url: 'nonexistent.mp4', + screenshot: { + url: pngUrl, + width: 7680, + height: 3200, + size: 100000, + contentType: IMAGE_PNG, + }, + fileName: 'the-sax.png', + contentType: VIDEO_MP4, + height: 3200, + width: 7680, + }), + ], + status: 'sent', + }); + + return ( + <> + {renderBothDirections(darkImageProps)} + {renderBothDirections(lightImageProps)} + + ); +} + +export function BrokenVideoWithExpirationTimer(): JSX.Element { + const darkImageProps = createProps({ + attachments: [ + fakeAttachment({ + url: 'nonexistent.mp4', + screenshot: { + url: '/fixtures/tina-rolf-269345-unsplash.jpg', + size: 100000, + width: 7680, + height: 3200, + contentType: IMAGE_JPEG, + }, + fileName: 'tina-rolf-269345-unsplash.jpg', + contentType: VIDEO_MP4, + height: 3200, + width: 7680, + }), + ], + expirationLength: 30 * 1000, + expirationTimestamp: Date.now() + 30 * 1000, + status: 'sent', + }); + const lightImageProps = createProps({ + attachments: [ + fakeAttachment({ + url: 'nonexistent.mp4', + screenshot: { + url: pngUrl, + width: 7680, + height: 3200, + size: 100000, + contentType: IMAGE_PNG, + }, + fileName: 'the-sax.png', + contentType: VIDEO_MP4, + height: 3200, + width: 7680, + }), + ], + expirationLength: 30 * 1000, + expirationTimestamp: Date.now() + 30 * 1000, + status: 'sent', + }); + + return ( + <> + {renderBothDirections(darkImageProps)} + {renderBothDirections(lightImageProps)} + + ); +} + export const MultipleImages2 = Template.bind({}); MultipleImages2.args = { attachments: [ @@ -1788,6 +2008,21 @@ OtherFileType.args = { status: 'sent', }; +export const OtherFileTypeWithExpirationTimer = Template.bind({}); +OtherFileTypeWithExpirationTimer.args = { + attachments: [ + fakeAttachment({ + contentType: stringToMIMEType('text/plain'), + fileName: 'things.zip', + url: 'things.zip', + size: 10200000, + }), + ], + expirationLength: 30 * 1000, + expirationTimestamp: Date.now() + 30 * 1000, + status: 'sent', +}; + export const OtherFileTypeFourChar = Template.bind({}); OtherFileTypeFourChar.args = { attachments: [