Link previews: show full size image less often
This commit is contained in:
parent
92a35649da
commit
8c25ffd6f5
7 changed files with 220 additions and 53 deletions
|
@ -26,6 +26,8 @@ import {
|
|||
import { Props as ReactionPickerProps } from './ReactionPicker';
|
||||
import { Emoji } from '../emoji/Emoji';
|
||||
import { LinkPreviewDate } from './LinkPreviewDate';
|
||||
import { LinkPreviewType } from '../../types/message/LinkPreviews';
|
||||
import { shouldUseFullSizeLinkPreviewImage } from '../../linkPreviews/shouldUseFullSizeLinkPreviewImage';
|
||||
|
||||
import {
|
||||
AttachmentType,
|
||||
|
@ -54,22 +56,10 @@ interface Trigger {
|
|||
handleContextClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
|
||||
// Same as MIN_WIDTH in ImageGrid.tsx
|
||||
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
|
||||
const STICKER_SIZE = 200;
|
||||
const SELECTED_TIMEOUT = 1000;
|
||||
const THREE_HOURS = 3 * 60 * 60 * 1000;
|
||||
|
||||
interface LinkPreviewType {
|
||||
title: string;
|
||||
description?: string;
|
||||
domain: string;
|
||||
url: string;
|
||||
isStickerPack: boolean;
|
||||
image?: AttachmentType;
|
||||
date?: number;
|
||||
}
|
||||
|
||||
export const MessageStatuses = [
|
||||
'delivered',
|
||||
'error',
|
||||
|
@ -850,12 +840,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
Boolean(quote) ||
|
||||
(conversationType === 'group' && direction === 'incoming');
|
||||
|
||||
const previewHasImage = first.image && isImageAttachment(first.image);
|
||||
const width = first.image && first.image.width;
|
||||
const isFullSizeImage =
|
||||
!first.isStickerPack &&
|
||||
width &&
|
||||
width >= MINIMUM_LINK_PREVIEW_IMAGE_WIDTH;
|
||||
const previewHasImage = isImageAttachment(first.image);
|
||||
const isFullSizeImage = shouldUseFullSizeLinkPreviewImage(first);
|
||||
|
||||
const linkPreviewDate = first.date || null;
|
||||
|
||||
|
@ -1498,25 +1484,16 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
if (previews && previews.length) {
|
||||
const first = previews[0];
|
||||
|
||||
if (!first || !first.image) {
|
||||
return undefined;
|
||||
}
|
||||
const { width } = first.image;
|
||||
|
||||
if (
|
||||
!first.isStickerPack &&
|
||||
isImageAttachment(first.image) &&
|
||||
width &&
|
||||
width >= MINIMUM_LINK_PREVIEW_IMAGE_WIDTH
|
||||
) {
|
||||
const dimensions = getImageDimensions(first.image);
|
||||
if (dimensions) {
|
||||
// Add two for 1px border
|
||||
return dimensions.width + 2;
|
||||
}
|
||||
const firstLinkPreview = (previews || [])[0];
|
||||
if (
|
||||
firstLinkPreview &&
|
||||
firstLinkPreview.image &&
|
||||
shouldUseFullSizeLinkPreviewImage(firstLinkPreview)
|
||||
) {
|
||||
const dimensions = getImageDimensions(firstLinkPreview.image);
|
||||
if (dimensions) {
|
||||
// Add two for 1px border
|
||||
return dimensions.width + 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1547,10 +1524,6 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
const first = previews[0];
|
||||
const { image } = first;
|
||||
|
||||
if (!image) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isImageAttachment(image);
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ export const StagedLinkPreview: React.FC<Props> = ({
|
|||
date,
|
||||
domain,
|
||||
}: Props) => {
|
||||
const isImage = image && isImageAttachment(image);
|
||||
const isImage = isImageAttachment(image);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
34
ts/linkPreviews/shouldUseFullSizeLinkPreviewImage.ts
Normal file
34
ts/linkPreviews/shouldUseFullSizeLinkPreviewImage.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { LinkPreviewType } from '../types/message/LinkPreviews';
|
||||
import { isImageAttachment } from '../types/Attachment';
|
||||
|
||||
const MINIMUM_FULL_SIZE_DIMENSION = 200;
|
||||
|
||||
export function shouldUseFullSizeLinkPreviewImage({
|
||||
isStickerPack,
|
||||
image,
|
||||
}: Readonly<LinkPreviewType>): boolean {
|
||||
if (isStickerPack || !isImageAttachment(image)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { width, height } = image;
|
||||
|
||||
return (
|
||||
isDimensionFullSize(width) &&
|
||||
isDimensionFullSize(height) &&
|
||||
!isRoughlySquare(width, height)
|
||||
);
|
||||
}
|
||||
|
||||
function isDimensionFullSize(dimension: unknown): dimension is number {
|
||||
return (
|
||||
typeof dimension === 'number' && dimension >= MINIMUM_FULL_SIZE_DIMENSION
|
||||
);
|
||||
}
|
||||
|
||||
function isRoughlySquare(width: number, height: number): boolean {
|
||||
return Math.abs(1 - width / height) < 0.05;
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { IMAGE_JPEG, VIDEO_MP4 } from '../../types/MIME';
|
||||
import { AttachmentType } from '../../types/Attachment';
|
||||
|
||||
import { shouldUseFullSizeLinkPreviewImage } from '../../linkPreviews/shouldUseFullSizeLinkPreviewImage';
|
||||
|
||||
describe('shouldUseFullSizeLinkPreviewImage', () => {
|
||||
const baseLinkPreview = {
|
||||
title: 'Foo Bar',
|
||||
domain: 'example.com',
|
||||
url: 'https://example.com/foo.html',
|
||||
isStickerPack: false,
|
||||
};
|
||||
|
||||
const fakeAttachment = (
|
||||
overrides: Partial<AttachmentType> = {}
|
||||
): AttachmentType => ({
|
||||
contentType: IMAGE_JPEG,
|
||||
fileName: 'foo.jpg',
|
||||
url: '/tmp/foo.jpg',
|
||||
width: 800,
|
||||
height: 600,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
it('returns false if there is no image', () => {
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false is the preview is a sticker pack', () => {
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
isStickerPack: true,
|
||||
image: fakeAttachment(),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false if either of the image's dimensions are missing", () => {
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ width: undefined }),
|
||||
})
|
||||
);
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ height: undefined }),
|
||||
})
|
||||
);
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ width: undefined, height: undefined }),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false if either of the image's dimensions are <200px", () => {
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ width: 199 }),
|
||||
})
|
||||
);
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ height: 199 }),
|
||||
})
|
||||
);
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ width: 150, height: 199 }),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false if the image is square', () => {
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ width: 200, height: 200 }),
|
||||
})
|
||||
);
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ width: 500, height: 500 }),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false if the image is roughly square', () => {
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ width: 200, height: 201 }),
|
||||
})
|
||||
);
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ width: 497, height: 501 }),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false for large attachments that aren't images", () => {
|
||||
assert.isFalse(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({
|
||||
contentType: VIDEO_MP4,
|
||||
fileName: 'foo.mp4',
|
||||
url: '/tmp/foo.mp4',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns true for larger images', () => {
|
||||
assert.isTrue(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment({ width: 200, height: 500 }),
|
||||
})
|
||||
);
|
||||
assert.isTrue(
|
||||
shouldUseFullSizeLinkPreviewImage({
|
||||
...baseLinkPreview,
|
||||
image: fakeAttachment(),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
|
@ -133,12 +133,12 @@ export function isImage(
|
|||
}
|
||||
|
||||
export function isImageAttachment(
|
||||
attachment: AttachmentType
|
||||
): boolean | undefined {
|
||||
return (
|
||||
attachment?: AttachmentType
|
||||
): attachment is AttachmentType {
|
||||
return Boolean(
|
||||
attachment &&
|
||||
attachment.contentType &&
|
||||
isImageTypeSupported(attachment.contentType)
|
||||
attachment.contentType &&
|
||||
isImageTypeSupported(attachment.contentType)
|
||||
);
|
||||
}
|
||||
export function hasImage(
|
||||
|
|
14
ts/types/message/LinkPreviews.ts
Normal file
14
ts/types/message/LinkPreviews.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { AttachmentType } from '../Attachment';
|
||||
|
||||
export interface LinkPreviewType {
|
||||
title: string;
|
||||
description?: string;
|
||||
domain: string;
|
||||
url: string;
|
||||
isStickerPack: boolean;
|
||||
image?: AttachmentType;
|
||||
date?: number;
|
||||
}
|
|
@ -14782,7 +14782,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.js",
|
||||
"line": " this.audioRef = react_1.default.createRef();",
|
||||
"lineNumber": 62,
|
||||
"lineNumber": 61,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-28T16:12:19.904Z"
|
||||
},
|
||||
|
@ -14790,7 +14790,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.js",
|
||||
"line": " this.focusRef = react_1.default.createRef();",
|
||||
"lineNumber": 63,
|
||||
"lineNumber": 62,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-11T17:24:56.124Z",
|
||||
"reasonDetail": "Used for managing focus only"
|
||||
|
@ -14799,7 +14799,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.js",
|
||||
"line": " this.reactionsContainerRef = react_1.default.createRef();",
|
||||
"lineNumber": 64,
|
||||
"lineNumber": 63,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-28T16:12:19.904Z",
|
||||
"reasonDetail": "Used for detecting clicks outside reaction viewer"
|
||||
|
@ -14808,7 +14808,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.tsx",
|
||||
"line": " public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();",
|
||||
"lineNumber": 226,
|
||||
"lineNumber": 216,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-08T20:19:01.913Z"
|
||||
},
|
||||
|
@ -14816,7 +14816,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.tsx",
|
||||
"line": " public focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
|
||||
"lineNumber": 228,
|
||||
"lineNumber": 218,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-08T20:19:01.913Z"
|
||||
},
|
||||
|
@ -14824,7 +14824,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.tsx",
|
||||
"line": " > = React.createRef();",
|
||||
"lineNumber": 232,
|
||||
"lineNumber": 222,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-28T19:36:40.817Z"
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue